summaryrefslogtreecommitdiffstats
path: root/private/mvdm/wow16/timer
diff options
context:
space:
mode:
authorAdam <you@example.com>2020-05-17 05:51:50 +0200
committerAdam <you@example.com>2020-05-17 05:51:50 +0200
commite611b132f9b8abe35b362e5870b74bce94a1e58e (patch)
treea5781d2ec0e085eeca33cf350cf878f2efea6fe5 /private/mvdm/wow16/timer
downloadNT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.gz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.bz2
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.lz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.xz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.zst
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.zip
Diffstat (limited to 'private/mvdm/wow16/timer')
-rw-r--r--private/mvdm/wow16/timer/api.asm288
-rw-r--r--private/mvdm/wow16/timer/libinit.asm374
-rw-r--r--private/mvdm/wow16/timer/local.asm701
-rw-r--r--private/mvdm/wow16/timer/makefile138
-rw-r--r--private/mvdm/wow16/timer/math.asm349
-rw-r--r--private/mvdm/wow16/timer/messages/usa/timer.rc8
-rw-r--r--private/mvdm/wow16/timer/messages/usa/timer.rcv12
-rw-r--r--private/mvdm/wow16/timer/njumps.mac36
-rw-r--r--private/mvdm/wow16/timer/startend.asm285
-rw-r--r--private/mvdm/wow16/timer/sysinfo.inc53
-rw-r--r--private/mvdm/wow16/timer/timer.asm842
-rw-r--r--private/mvdm/wow16/timer/timer.def34
-rw-r--r--private/mvdm/wow16/timer/timer.inc267
13 files changed, 3387 insertions, 0 deletions
diff --git a/private/mvdm/wow16/timer/api.asm b/private/mvdm/wow16/timer/api.asm
new file mode 100644
index 000000000..d4c883f98
--- /dev/null
+++ b/private/mvdm/wow16/timer/api.asm
@@ -0,0 +1,288 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; API.ASM
+;
+; Copyright (c) Microsoft Corporation 1989, 1990. All rights reserved.
+;
+; Contains the routine tddMessage which communicates to either
+; the 386 timer API's of the 286 timer API's depending on the
+; WinFlags settings WF_WIN286,WF_WIN386.
+;
+;
+; Revision history:
+;
+; 2/12/90 First created by w-glenns
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+?PLM=1 ; pascal call convention
+?WIN=0 ; Windows prolog/epilog code
+?DF=1
+PMODE=1
+
+.xlist
+include cmacros.inc
+include windows.inc
+include mmsystem.inc
+include mmddk.inc
+include timer.inc
+.list
+
+ .286p
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; External functions
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+externFP Enable
+externFP Disable
+
+ifdef DEBUG
+externFP tddGetTickCount
+endif
+
+externFP tddSetTimerEvent
+externFP tddKillTimerEvent
+externFP tddGetSystemTime
+externFP tddGetDevCaps
+externFP tddBeginMinPeriod
+externFP tddEndMinPeriod
+
+;externFP vtdSetTimerEvent
+;externFP vtdKillTimerEvent
+;externFP vtdGetSystemTime
+;externFP vtdGetDevCaps
+;externFP vtdBeginMinPeriod
+;externFP vtdEndMinPeriod
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Local data segment
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+externA WinFlags
+
+sBegin Data
+
+ externW wEnabled
+
+ errnz <TDD_KILLTIMEREVENT-DRV_RESERVED>
+ errnz <TDD_SETTIMEREVENT-4-DRV_RESERVED>
+ errnz <TDD_GETSYSTEMTIME-8-DRV_RESERVED>
+ errnz <TDD_GETDEVCAPS-12-DRV_RESERVED>
+ errnz <TDD_BEGINMINPERIOD-16-DRV_RESERVED>
+ errnz <TDD_ENDMINPERIOD-20-DRV_RESERVED>
+
+ tblCall286 dd tddKillTimerEvent,tddSetTimerEvent,tddGetSystemTime,tddGetDevCaps,tddBeginMinPeriod, tddEndMinPeriod
+ tblCall386 dd tddKillTimerEvent,tddSetTimerEvent,tddGetSystemTime,tddGetDevCaps,tddBeginMinPeriod, tddEndMinPeriod
+; tblCall386 dd vtdKillTimerEvent,vtdSetTimerEvent,vtdGetSystemTime,vtdGetDevCaps,vtdBeginMinPeriod, vtdEndMinPeriod
+ tblCallLen equ ($-tblCall286)/2
+
+ifdef DEBUG
+ externD RModeIntCount
+ externD PModeIntCount
+endif
+
+sEnd Data
+
+sBegin CodeFixed
+ assumes cs,CodeFixed
+ assumes ds,Data
+ assumes es,nothing
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; @doc INTERNAL
+;
+; @api DWORD | DriverProc | Pass messages to functions that really do work
+;
+; @parm DWORD | nDevice | The id of the device to get the message.
+;
+; @parm WORD | msg | The message.
+;
+; @parm LONG | lParam1 | Parameter 1.
+;
+; @parm LONG | lParam2 | Parameter 2.
+;
+; @rdesc The return value depends on the message being sent.
+;
+; @comm Devices not supporting a message should return 0.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;
+; driver message handler table
+;
+; These two tables define which routine handles which driver message.
+;
+; NOTE WARNING: ProcTbl must IMMEDIATELY follow MsgTbl.
+;
+MsgTbl dw TDD_GETSYSTEMTIME
+ dw TDD_BEGINMINPERIOD
+ dw TDD_ENDMINPERIOD
+ dw TDD_KILLTIMEREVENT
+ dw TDD_SETTIMEREVENT
+ dw TDD_GETDEVCAPS
+
+ dw DRV_LOAD
+ dw DRV_OPEN
+ dw DRV_CLOSE
+ dw DRV_ENABLE
+ dw DRV_DISABLE
+ dw DRV_QUERYCONFIGURE
+ dw DRV_INSTALL
+ifdef DEBUG
+ dw TDD_GETTICK
+ dw TDD_GETRINTCOUNT
+ dw TDD_GETPINTCOUNT
+endif
+ dw -1
+
+MsgLen equ $-MsgTbl
+
+ProcTbl dw msg_TDD_GETSYSTEMTIME ; TDD_GETSYSTEMTIME
+ dw msg_TDD_BEGINMINPERIOD ; TDD_BEGINMINPERIOD
+ dw msg_TDD_ENDMINPERIOD ; TDD_ENDMINPERIOD
+ dw msg_TDD_KILLTIMEREVENT ; TDD_KILLTIMEREVENT
+ dw msg_TDD_SETTIMEREVENT ; TDD_SETTIMEREVENT
+ dw msg_TDD_GETDEVCAPS ; TDD_GETDEVCAPS
+ ;
+ dw msg_DRV_LOAD ; DRV_OPEN
+ dw msg_DRV_OPEN ; DRV_OPEN
+ dw msg_DRV_CLOSE ; DRV_CLOSE
+ dw msg_DRV_ENABLE ; DRV_ENABLE
+ dw msg_DRV_DISABLE ; DRV_DISABLE
+ dw msg_DRV_QUERYCONFIGURE ; DRV_QUERYCONFIGURE
+ dw msg_DRV_INSTALL ; DRV_INSTALL
+ifdef DEBUG
+ dw msg_TDD_GETTICK ; TDD_GETTICK
+ dw msg_TDD_GETRINTCOUNT ; TDD_GETRINTCOUNT
+ dw msg_TDD_GETPINTCOUNT ; TDD_GETPINTCOUNT
+endif
+ dw msg_fail ; default
+
+ProcLen equ $-ProcTbl
+
+errnz <ProcLen-MsgLen> ; these had better be the same!
+errnz <ProcTbl-MsgTbl-MsgLen> ; ProcTbl *must* follow MsgTbl
+
+cProc DriverProc <PUBLIC,FAR,LOADDS> <di>
+ ParmD id
+ ParmW hDriver
+ ParmW msg
+ ParmD lParam1
+ ParmD lParam2
+cBegin
+ mov ax,cs ; es == Code
+ mov es,ax
+ assumes es,CodeFixed
+
+ mov ax,msg ; AX = Message number
+ cmp ax,DRV_RESERVED ; messages below DRV_RESERVED dont
+ jl msg_dispatch ; ...need driver to be enabled
+
+ cmp wEnabled,0 ; must be enabled for msgs > DRV_RESERVED
+ jz msg_error
+
+msg_dispatch:
+ mov di,CodeFixedOFFSET MsgTbl
+ mov cx,MsgLen/2
+ cld
+ repnz scasw
+ lea bx,[di+MsgLen-2]
+ jmp cs:[bx]
+ assumes es,nothing
+
+msg_error:
+ mov ax, TIMERR_NOCANDO
+ jmp short msg_makelong
+
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+; handle std. installable driver messages.
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+msg_DRV_ENABLE:
+ cCall Enable, <ax> ; enable driver
+ jmp short msg_makelong
+
+msg_DRV_DISABLE:
+ cCall Disable, <ax>
+ jmp short msg_makelong
+
+msg_DRV_LOAD:
+msg_DRV_OPEN:
+msg_DRV_CLOSE:
+msg_success:
+ mov ax,1 ; return 1 for all others
+ jmp short msg_makelong
+
+msg_fail:
+msg_DRV_QUERYCONFIGURE:
+ xor ax, ax ; no - return 0
+ jmp short msg_makelong
+
+msg_DRV_INSTALL:
+ mov ax, DRVCNF_RESTART ; restart after install
+ errn$ msg_makelong
+
+msg_makelong:
+ cwd ; make sure high word (dx) is set
+ jmp short msg_done
+
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+; handle timer driver specific massages
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+ifdef DEBUG
+msg_TDD_GETTICK:
+ cCall tddGetTickCount
+ jmp short msg_done
+
+msg_TDD_GETRINTCOUNT:
+ mov ax,RModeIntCount.lo
+ mov dx,RModeIntCount.hi
+ jmp short msg_done
+
+msg_TDD_GETPINTCOUNT:
+ mov ax,PModeIntCount.lo
+ mov dx,PModeIntCount.hi
+ jmp short msg_done
+endif
+
+msg_TDD_GETDEVCAPS:
+ push lParam1.hi
+ push lParam1.lo
+ push lParam2.lo
+ jmp short msg_call
+
+msg_TDD_SETTIMEREVENT:
+ push lParam1.hi
+
+msg_TDD_BEGINMINPERIOD:
+msg_TDD_ENDMINPERIOD:
+msg_TDD_KILLTIMEREVENT:
+ push lParam1.lo
+
+msg_TDD_GETSYSTEMTIME:
+ errn$ msg_call
+
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+msg_call:
+ sub ax,DRV_RESERVED ; map msg into table index
+ mov di,offset DGROUP:tblCall286
+ add di,ax
+ mov ax,WinFlags
+ test ax,WF_WIN386
+ jz @f ; jump if not win386
+ add di,tblCallLen
+@@: call dword ptr [di] ; index into table
+ errn$ msg_done
+
+msg_done:
+cEnd
+
+sEnd
+
+end
diff --git a/private/mvdm/wow16/timer/libinit.asm b/private/mvdm/wow16/timer/libinit.asm
new file mode 100644
index 000000000..ff2373e4b
--- /dev/null
+++ b/private/mvdm/wow16/timer/libinit.asm
@@ -0,0 +1,374 @@
+;
+; LibInit.asm library stub to do local init for a Dynamic linked library
+;
+; NOTE!!!! link this MODULE first or you will be sorry!!!!
+;
+?PLM=1 ; pascal call convention
+?WIN=0 ; Windows prolog/epilog code
+?DF=1
+PMODE=1
+
+.286
+.xlist
+include cmacros.inc
+include windows.inc
+include sysinfo.inc
+include mmddk.inc
+include mmsystem.inc
+include timer.inc
+;include vtdapi.inc
+.list
+
+.list
+
+sBegin Data
+;
+; Stuff needed to avoid the C runtime coming in
+;
+; also known as "MAGIC THAT SAVED ME" - Glenn Steffler 2/7/90
+;
+; Do not remove under penalty of sex change operation!!
+;
+ DD 0 ; So null pointers get 0
+maxRsrvPtrs = 5
+ DW maxRsrvPtrs
+usedRsrvPtrs = 0
+labelDP <PUBLIC,rsrvptrs>
+
+DefRsrvPtr MACRO name
+globalW name,0
+usedRsrvPtrs = usedRsrvPtrs + 1
+ENDM
+
+DefRsrvPtr pLocalHeap ; Local heap pointer
+DefRsrvPtr pAtomTable ; Atom table pointer
+DefRsrvPtr pStackTop ; top of stack
+DefRsrvPtr pStackMin ; minimum value of SP
+DefRsrvPtr pStackBot ; bottom of stack
+
+if maxRsrvPtrs-usedRsrvPtrs
+ DW maxRsrvPtrs-usedRsrvPtrs DUP (0)
+endif
+
+public __acrtused
+ __acrtused = 1
+
+sEnd Data
+
+;
+;
+; END of nasty shit wierdness stuff that made my life a living hell...
+;
+
+externA WinFlags
+externFP LocalInit
+externFP Disable286
+externFP Enable286
+externW wMaxResolution
+externW wMinPeriod
+
+; here lies the global data
+
+sBegin Data
+
+public wEnabled
+wEnabled dw 0 ; enable = 1 ;disable = 0
+
+public PS2_MCA
+PS2_MCA db ? ; Micro Channel Flag
+
+sEnd Data
+
+ assumes es,nothing
+
+sBegin CodeInit
+ assumes cs,CodeInit
+ assumes ds,Data
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Library unload function
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+; Disable routine is same as WEP
+
+cProc WEP,<FAR,PUBLIC>,<>
+; parmW silly_param
+cBegin nogen
+
+ errn$ Disable
+
+cEnd nogen
+
+cProc Disable,<FAR,PUBLIC>,<>
+; parmW silly_param
+cBegin nogen
+ push ds
+ mov ax,DGROUP ; set up DS==DGROUP for exported funcs
+ mov ds,ax
+ assumes ds,Data
+
+ xor ax,ax ; return value = no error
+
+ cmp wEnabled,ax ; Q: enabled ?
+ jz dis_done ; N: exit
+
+ mov wEnabled,ax ; disabled now
+
+ mov ax,WinFlags
+ test ax,WF_WIN386
+ jnz dis_386
+
+ ; running under win286
+dis_286:
+ call Disable286
+ jmp dis_done
+
+ ; running under win386
+dis_386:
+ call Disable286
+
+dis_done:
+ pop ds
+ ret 2
+
+cEnd nogen
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Library Enable function
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+cProc Enable,<FAR,PUBLIC>,<>
+; parmW silly_param
+cBegin nogen
+ mov ax,wEnabled
+ or ax,ax ; Q: already enabled ?
+ jnz enable_done ; Y: exit
+
+ inc wEnabled ; mark as being enabled
+
+ mov ax,WinFlags
+ test ax,WF_WIN386
+ jnz enable_386
+
+ ; running under win286
+enable_286:
+ call Enable286
+ jmp enable_done
+
+ ; running under win386
+enable_386:
+ call Enable286
+
+enable_done:
+ ret 2
+
+cEnd nogen
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Library entry point
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+public LibInit
+LibInit proc far
+
+ ; CX = size of heap
+ ; DI = module handle
+ ; DS = automatic data segment
+ ; ES:SI = address of command line (not used)
+
+ jcxz lib_heapok ; heap size zero? jump over unneeded LocalInit call
+
+ cCall LocalInit,<ds,ax,cx> ; dataseg, 0, heapsize
+ or ax,ax
+ jnz lib_heapok ; if heap set continue on
+
+lib_error:
+ xor ax,ax
+ ret ; return FALSE (ax = 0) -- couldn't init
+
+lib_heapok:
+ mov ax,WinFlags
+ test ax,WF_WIN386
+ jnz lib_386
+
+ ; running under win286
+lib_286:
+ call Lib286Init
+ jmp lib_realdone ; win 286 will enable timer on first event request
+
+ ; running under win386
+lib_386:
+ call Lib286Init
+
+lib_realdone:
+ ret
+
+LibInit endp
+
+sEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Win 386 timer VTD code for initialization, and removal
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ externFP GetVersion ; in KERNEL
+ externFP MessageBox ; in USER
+ externFP LoadString ; in USER
+
+sBegin CodeInit
+assumes cs,CodeInit
+assumes ds,Data
+
+;externNP VTDAPI_GetEntryPt
+
+; Assumes DI contains module handle
+cProc WarningMessage <NEAR,PASCAL> <>
+ LocalV aszErrorTitle, 32
+ LocalV aszErrorMsg, 256
+cBegin
+ lea ax, WORD PTR aszErrorTitle
+ cCall LoadString, <di, IDS_ERRORTITLE, ss, ax, 32>
+ lea ax, WORD PTR aszErrorMsg
+ cCall LoadString, <di, IDS_ERRORTEXT, ss, ax, 256>
+ lea ax, WORD PTR aszErrorTitle
+ lea bx, WORD PTR aszErrorMsg
+ cCall MessageBox, <NULL, ss, bx, ss, ax, MB_SYSTEMMODAL+MB_OK+MB_ICONHAND>
+cEnd
+
+if 0
+Lib386Init proc near
+
+ call VTDAPI_GetEntryPt ; this will return 0 if the VxD is not loaded
+
+ or ax,ax
+ jnz Lib386InitOk
+
+ DOUT <TIMER: *** unable to find vtdapi.386 ***>
+
+ ;
+ ; warn the USER that we can't find our VxD, under windows 3.0
+ ; we can't bring up a message box, so only do this in win 3.1
+ ;
+
+ cCall GetVersion
+ xchg al,ah
+ cmp ax,030Ah
+ jb Lib386InitFail
+
+ cCall WarningMessage,<>
+
+Lib386InitFail:
+ xor ax,ax
+
+Lib386InitOk:
+
+ ret
+
+Lib386Init endp
+endif
+
+Disable386 proc near
+
+ errn$ Enable386 ; fall through
+
+Disable386 endp
+
+Enable386 proc near
+
+ mov ax,1 ; nothing to do
+ ret
+
+Enable386 endp
+
+sEnd Code386
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Win 286 timer drv code for initialization, and removal
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ externW Events
+ externFP tddISR ; in local.asm
+
+ externFP GlobalWire ; in KERNEL
+ externFP GlobalPageLock ; in KERNEL
+
+sBegin CodeInit
+ assumes cs,CodeInit
+ assumes ds,Data
+
+Lib286Init proc near
+ ; get the system configuration
+
+ ;
+ ; the FIXED_286 segment is not loaded, load it and pagelock it.
+ ;
+ mov dx,seg tddISR ; get the 286 code segment
+ mov es,dx
+ mov ax,es:[0] ; load it!
+ cCall GlobalWire, <dx> ; get it low in memory
+ cCall GlobalPageLock, <dx> ; and nail it down!
+
+ mov PS2_MCA,0 ; Initialize PS2_MCA = FALSE
+ stc ; Set this in case BIOS doesn't
+ mov ah,GetSystemConfig
+ int 15h
+ jc Lib286Init_NoMicroChannel ; Successful call?
+ or ah,ah ; Valid return?
+ jnz Lib286Init_NoMicroChannel
+ test es:[bx.SD_feature1],SF1_MicroChnPresent
+ jz Lib286Init_NoMicroChannel
+ inc PS2_MCA ; PS2_MCA = TRUE
+Lib286Init_NoMicroChannel:
+
+ push di
+
+ push ds
+ pop es
+ mov di,DataOFFSET Events ; ES:DI --> Events
+ xor ax,ax
+ mov cx,(MAXEVENTS * SizeEvent)/2
+ rep stosw ; zero out event structures.
+
+ ; set up one event as the standard call-back routine for the
+ ; BIOS timer service
+ ;
+ xor bx,bx ; BX:CX = 64k
+ xor cx,cx
+ inc bx
+
+ mov di,DataOFFSET Events ; DS:DI --> Events
+
+ mov [di].evTime.lo,cx ; Program next at ~= 55ms
+ mov [di].evTime.hi,bx ; standard 18.2 times per second event
+ mov [di].evDelay.lo,cx ; First event will be set off
+ mov [di].evDelay.hi,bx ; at 55ms (65536 ticks)
+ mov [di].evResolution,TDD_MINRESOLUTION ; Allow 55ms either way
+ mov [di].evFlags,TIME_BIOSEVENT+TIME_PERIODIC
+
+ mov ax,WinFlags
+ test ax,WF_CPU286
+ jz @f
+ mov wMaxResolution,TDD_MAX286RESOLUTION
+ mov wMinPeriod,TDD_MIN286PERIOD
+@@:
+ mov ax,bx ; Return TRUE
+ mov [di].evID,ax ; enable event
+
+ pop di
+ ret
+
+Lib286Init endp
+
+sEnd
+
+ end LibInit
diff --git a/private/mvdm/wow16/timer/local.asm b/private/mvdm/wow16/timer/local.asm
new file mode 100644
index 000000000..c409e9608
--- /dev/null
+++ b/private/mvdm/wow16/timer/local.asm
@@ -0,0 +1,701 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; LOCAL.ASM
+;
+; Copyright (c) Microsoft Corporation 1989, 1990. All rights reserved.
+;
+; This module contains the routines which interface with the
+; timer counter hardware itself.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+?PLM=1 ; pascal call convention
+?WIN=0 ; Windows prolog/epilog code
+?DF=1
+PMODE=1
+
+.xlist
+include cmacros.inc
+include windows.inc
+include mmddk.inc
+include mmsystem.inc
+include timer.inc
+.list
+
+ externFP DriverCallback ; in MMSYSTEM.DLL
+ externFP StackEnter ; in MMSYSTEM.DLL
+ externFP StackLeave ; in MMSYSTEM.DLL
+ externFP tddEndMinPeriod ; timer.asm
+ externA __WinFlags ; Somewhere in Kernel ?
+
+ .286p
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Local data segment
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+externW Events
+externD lpOLDISR
+externB PS2_MCA
+
+sBegin Data
+
+; Current Time
+public CurTime
+CurTime dw 3 dup(0) ; 48 bit current tick count.
+
+public wProgTime
+wProgTime dw 0 ; Time currently programmed into timer chip
+ ; ...NOTE 0=64k !!!
+public wNextTime
+wNextTime dw 0 ; Time next programmed into timer chip
+
+public nInt8Count
+nInt8Count dw 0 ; # times int8 handler re-entered
+
+ifdef DEBUG
+public RModeIntCount, PModeIntCount
+RModeIntCount dd 0
+PModeIntCount dd 0
+endif
+
+public IntCount
+IntCount dw 0
+fBIOSCall dw 0 ; Bios callback needed: TRUE or FALSE
+fIntsOn dw 0 ; Interrupts have already been turned back on
+ifdef RMODE_INT
+dRModeTicks dd ? ; Temporary storage for Rmode ticks
+endif
+
+public dTickUpdate
+dTickUpdate dd 0 ; Amount to actually update times with
+
+sEnd Data
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Code segment
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+sBegin Code286
+ assumes cs,Code286
+ assumes ds,data
+ assumes es,nothing
+
+CodeFixWinFlags dw __WinFlags
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Local (private) functions
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; @doc INTERNAL
+;
+; @asm tddRModeISR | Service routine for timer interrupts on IRQ 0.
+; when in REAL mode
+;
+; @comm
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ifdef RMODE_INT
+ assumes ds,nothing
+ assumes es,nothing
+
+externD RModeOldISR
+
+public RModeDataSegment
+RModeDataSegment dw 0
+
+public tddRmodeISR
+tddRmodeISR proc far
+ push ds
+ push ax
+ push bx
+
+ mov ax,cs:[RModeDataSegment]
+ mov ds,ax
+ assumes ds,Data
+
+ inc [IntCount]
+
+ifdef DEBUG
+ add [RModeIntCount].lo,1
+ adc [RModeIntCount].hi,0
+endif
+
+ mov ax,[wNextTime] ; Next time programmed into timer chip
+ xchg ax,[wProgTime] ; Update current time if it was reset
+
+ xor bx,bx
+ dec ax ; convert 0 -> 64k
+ add ax,1
+ adc bx,bx
+
+ cmp [nInt8Count],1 ; Do not allow multiple re-entrancy
+ jge tddRmodeISRNormalExit
+
+ cld
+ push di
+ push cx
+ mov di,DataOFFSET Events ; DS:DI --> first event
+ mov cx,MAXEVENTS
+
+tddRmodeISRLoop:
+ cmp [di].evID,0 ; is this event active?
+ jz tddRmodeISRNext
+ cmp [di].evDestroy,EVENT_DESTROYING
+ je tddRmodeISRNext
+ test [di].evFlags,TIME_BIOSEVENT
+ jz tddRmodeISRNext
+
+ mov dRModeTicks.lo,ax
+ mov dRModeTicks.hi,bx
+ add ax,[dTickUpdate.lo]
+ adc bx,[dTickUpdate.hi]
+ cmp [di].evTime.hi,bx
+ jg @f
+ jl tddRmodeISRChain
+ cmp [di].evTime.lo,ax
+ jle tddRmodeISRChain
+
+@@:
+ mov ax,dRModeTicks.lo
+ mov bx,dRModeTicks.hi
+ jmp tddRmodeISRSearchExit
+
+tddRmodeISRChain:
+ pop cx
+ pop di
+ pop bx
+ pop ax
+ push [RModeOldISR.hi]
+ push [RModeOldISR.lo]
+
+ push bp ; Restore DS from stack
+ mov bp,sp
+ mov ds,[bp+6] ; stack: [ds] [RModeOldISR.hi] [RModeOldISR.lo] [bp]
+ assumes ds,nothing
+ pop bp
+
+ retf 2
+
+tddRmodeISRNext:
+ assumes ds,Data
+ add di,SizeEvent ; Increment to next event slot
+ loop tddRmodeISRLoop
+
+tddRmodeISRSearchExit:
+ pop cx
+ pop di
+
+tddRmodeISRNormalExit:
+ add CurTime[0],ax
+ adc CurTime[2],bx
+ adc CurTime[4],0
+
+ add [dTickUpdate.lo],ax ; Update total needed to be added
+ adc [dTickUpdate.hi],bx
+
+ cmp PS2_MCA,0 ; Check for a PS/2 Micro Channel
+ jz @f
+ in al,PS2_SysCtrlPortB ; Get current System Control Port status
+ or al,PS2_LatchBit ; Set latch clear bit
+ IO_Delay
+ out PS2_SysCtrlPortB,al ; Set new System Control Port status
+@@:
+ mov al,SPECIFIC_EOI ; specific EOI for IRQ 0 interrupt line
+ out PICDATA,al ; send End-Of-Interrupt to PIC DATA port
+
+ pop bx
+ pop ax
+ pop ds
+ assumes ds,nothing
+ iret
+
+tddRmodeISR endp
+endif
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@asm tddISR |
+; Service routine for timer interrupts on IRQ 0.
+;
+; The ISR runs through all the event slots available, looking for
+; slots that are currently in used, and are not currently being
+; destroyed. For each valid event found the callback time is updated.
+; After all times have been updated, the table is run through again,
+; calling all events that are due, and removing any due events that are
+; oneshots. By updating all the events first, any new events that are
+; created during a callback will not be accidentally called too early.
+;
+; Note that interrupts are not immediately restored, as this causes even
+; more problems with slow machines. Also, the EOI is not sent to the
+; PIC, as the BIOS interrupt handler does a non-specific EOI, and this
+; would in turn EOI the last outstanding interrupt.
+;
+; First there is a special check for the presence of a Micro Channel,
+; in which case, the System Control Port B must have bit 7 set in order
+; to have the IRQ 0 latch released. This flag is aquired during Enable
+; time with an int 15h service C0h, requesting machine information, which
+; includes the presence of a Micro Channel.
+;
+; The ISR then updates the tick count based on the count that was in
+; the timer's CE register. While retrieving that previously programmed
+; time, it updates it to the new time that is contained in the timer's
+; CR register, in case these to items are different. Note that the
+; maximum CE value of 0 is converted to 65536 through the decrement and
+; adding with carry.
+;
+; Next, the ISR must determine if it is re-entering itself. If this is
+; so, callbacks are not performed, and only a "missed ticks" count is
+; updated, indicating how many additional ticks should be subtracted
+; from each event due time. This allows the ISR to finish immediately
+; if a timer interrupt is currently being serviced. This is important
+; for both speed in general, and for slow machines that might generate
+; mouse events during timer events. Note that only 6 bytes have been
+; pushed onto the stack for this case, and that everything but DS must
+; be removed before jumping to the exit label. In this case, the
+; function can safely EOI the PIC, as the BIOS call will not be
+; performed, then the function will just return.
+;
+; In the normal case, the ISR is not being re-entered, and timer event
+; due times are updated, and callbacks are made. In this case, the
+; number of "missed ticks" is added to the CE tick count, bringing the
+; total up to the number of ticks passed since the last time the event
+; times were updated. This global counter is then zeroed for the next
+; time re-entrancy occurs. Note that interrupts are still turned off
+; at this point, and there is no need to fear bad things happening.
+;
+; When checking for a valid event ID, the Destroy flag must be checked
+; in case the interrupt occured during a kill timer function call after
+; the Destroy flag was grabbed the second time, but before the actual ID
+; could be reset.
+;
+; When a valid ID is found, its due time is updated with the CE value,
+; plus the amount of ticks that were missed because of re-entrancy, if
+; any.
+;
+; After updating times, the event list is checked again, this time to
+; perform any of the callbacks that are due. To make things easy, a
+; global flag is used to determine if interrupts have been turned back
+; on, and thus stacks have been switched.
+;
+; If a valid event is found that is also due, meaning that the callback
+; time is <= 0, the fIntsOn flag is checked to determine if the stack
+; has already been switched and interrupts are already on. If not, then
+; just that occurs. The <f>tddEvent<d> function is then called to
+; service the event.
+;
+; After all events have been called, interrupts are turned back off if
+; needed, and the original stack restored. If no callback actually
+; occurred, then the stack is never switched. The function then either
+; exits as a normal ISR would, or it chains to the BIOS ISR. This is
+; done if the BIOS event was up for being called, and the fBIOSCall flag
+; was set because of that. Since the flag cannot be set when this ISR
+; is being pre-entered, as callbacks are not performed, there is no need
+; to do a test and set proceedure on the fBIOSCall flag, just a simple
+; compare will do. Note though that the nInt8Count re-entrancy count is
+; not decremented until after interrupts are turned off.
+;
+; Interrupts are also cleared to ensure that the BIOS ISR is not
+; re-entered itself, since there is no re-entrancy control after this
+; function chains to BIOS. Notice that DS was the first register pushed
+; onto the stack, and therefore the last item to get rid of, which is
+; done with the "retf 2". DS itself is restored from stack before
+; chaining so that lpOLDISR (BIOS) can be accessed and pushed onto stack
+; as the return address.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,nothing
+ assumes es,nothing
+
+public tddISR
+tddISR proc far
+
+ push ds ; This is pushed first for the case of BIOS
+
+;----------------------------------------------------------------------------
+;If we are on a 386 save all registers.
+;----------------------------------------------------------------------------
+ test cs:[CodeFixWinFlags],WF_WIN286
+ jnz @F
+.386
+ pushad
+ push fs
+ push gs
+.286p
+@@:
+
+ push ax
+ push bx
+
+ mov ax,DGROUP ; set up local DS
+ mov ds,ax
+ assumes ds,Data
+
+ cmp PS2_MCA,0 ; Check for a PS/2 Micro Channel
+ jz @f
+ in al,PS2_SysCtrlPortB ; Get current System Control Port status
+ or al,PS2_LatchBit ; Set latch clear bit
+ IO_Delay
+ out PS2_SysCtrlPortB,al ; Set new System Control Port status
+
+@@:
+ inc [IntCount] ; Ever-increasing Int counter
+ inc [nInt8Count] ; Number of times int 8 re-entered
+
+ mov ax,[wNextTime] ; Next time programmed into timer chip
+ xchg ax,[wProgTime] ; Update current time if it was reset
+
+ xor bx,bx
+ dec ax ; convert 0 -> 64k
+ add ax,1 ; Force carry flag
+ adc bx,bx ; Set bx:ax == current tick count
+
+ add CurTime[0],ax ; Add tick count to total ticks
+ adc CurTime[2],bx
+ adc CurTime[4],0
+
+ifdef DEBUG
+; cmp [nInt8Count],1 ; Re-entrancy counter
+; je @f
+; add [RModeIntCount].lo,1
+; adc [RModeIntCount].hi,0
+;@@:
+ add [PModeIntCount].lo,1 ; For debug Pmode count message
+ adc [PModeIntCount].hi,0
+endif
+ cmp [nInt8Count],1 ; Do not allow multiple re-entrancy
+ je tddISRCheckCallbacks
+ add [dTickUpdate.lo],ax ; Update total needed to be added
+ adc [dTickUpdate.hi],bx
+ pop bx
+ jmp tddISREOIExit ; EOI before exiting
+
+tddISRCheckCallbacks:
+ add ax,[dTickUpdate.lo] ; Add any extra ticks from re-entrancy
+ adc bx,[dTickUpdate.hi]
+ push cx
+ xor cx,cx
+ mov [dTickUpdate.lo],cx ; Reset tick re-entrant counter
+ mov [dTickUpdate.hi],cx
+
+ cld ; never assume the value of this in an ISR!
+ push di
+ mov di,DataOFFSET Events ; DS:DI --> first event
+ mov cx,MAXEVENTS
+
+tddISRUpdateTimeLoop:
+ cmp [di].evID,0 ; is this event active?
+ jz tddISRUpdateTimeNext
+ sub [di].evTime.lo,ax ; Subtract the amount of ticks gone by
+ sbb [di].evTime.hi,bx
+
+tddISRUpdateTimeNext:
+ add di,SizeEvent ; Increment to next event slot
+ loop tddISRUpdateTimeLoop
+
+ mov fIntsOn,0 ; Initialize interrupts set flag
+ mov di,DataOFFSET Events ; DS:DI --> first event
+ mov cx,MAXEVENTS
+
+tddISRCallLoop:
+ cmp [di].evID,0 ; is this event active?
+ jz tddISRNextEvent
+ cmp [di].evDestroy,EVENT_DESTROYING
+ je tddISRNextEvent
+ cmp [di].evTime.hi,0 ; Is it time to call the event?
+ jg tddISRNextEvent ; evTime <= 0
+ jl tddISREvent
+ cmp [di].evTime.lo,0
+ jg tddISRNextEvent
+
+tddISREvent:
+ test [di].evFlags,TIME_BIOSEVENT
+ jnz tddISRCallEvent ; No need to switch, as no call will be made.
+ cmp fIntsOn,0 ; Have interrupts been turned on already?
+ jnz tddISRCallEvent
+ inc fIntsOn ; fIntsOn == TRUE
+ cCall StackEnter ; Switch to a new stack
+ sti ; Can be re-entered now with new stack
+
+; A timer callback needs to be called, but first before calling it,
+; we need to check to determine if the original timer interrupt function
+; is to be called during this interrupt. The reason is that a timer
+; callback could take a long time, and the PIC should be EOI'ed as soon
+; as possible.
+; It is not possible to just do a specific EOI, as the BIOS timer
+; interrupt performs a non-specific EOI, which would turn back on some
+; other random interrupt. So if the the BIOS needs to be called, it
+; is done now, else the EOI is performed now. This assumes that the
+; BIOS callback is the first item in the list of callbacks.
+; If the BIOS callback occurs now, then the fBIOSCall flag is reset,
+; as there is no need to chain to it at the end of this interrupt. So
+; if no other callbacks are to be performed, the BIOS interrupt is
+; chained to, else it is just called before the first timer callback
+; is performed.
+
+ cmp [fBIOSCall],0 ; Does BIOS need to be called?
+ je tddISREOI
+ mov [fBIOSCall],0 ; No need to call BIOS again at the end
+ pushf ; Simulate an interrupt call
+ call lpOLDISR ; Call original timer interrupt
+ jmp tddISRCallEvent ; Do actual timer callback
+
+; No BIOS interrupt call is to be performed, so do EOI.
+tddISREOI:
+ mov al,SPECIFIC_EOI ; specific EOI for IRQ 0 interrupt line
+ out PICDATA,al ; send End-Of-Interrupt to PIC DATA port
+tddISRCallEvent:
+ call tddEvent ; handle the event
+
+tddISRNextEvent:
+ add di,SizeEvent ; Increment to next event slot
+ loop tddISRCallLoop
+
+ cmp fIntsOn,0 ; Where interrupts turned back on?
+ jz @f
+ cli ; Interrupts were turned on, so remove them
+ cCall StackLeave ; Switch back to old stack
+
+@@:
+ pop di ; Restore everything except DS
+ pop cx
+ pop bx
+ cmp [fBIOSCall],0 ; Does BIOS need to be called?
+ je tddISREOIExit
+ pop ax
+ mov [fBIOSCall],0
+
+;----------------------------------------------------------------------------
+;If we are on a 386 restore all registers.
+;----------------------------------------------------------------------------
+ test cs:[CodeFixWinFlags],WF_WIN286
+ jnz @F
+.386
+ pop gs
+ pop fs
+ popad
+.286p
+@@:
+ push [lpOLDISR.hi] ; Push return address
+ push [lpOLDISR.lo]
+ dec [nInt8Count] ; exiting, decrement entry count
+
+ push bp ; Restore DS from stack
+ mov bp,sp
+ mov ds,[bp+6] ; stack: [ds] [lpOLDISR.hi] [lpOLDISR.lo] [bp]
+ assumes ds,nothing
+ pop bp
+
+ retf 2 ; Chain to BIOS ISR, removing DS from stack
+
+tddISREOIExit:
+ mov al,SPECIFIC_EOI ; specific EOI for IRQ 0 interrupt line
+ out PICDATA,al ; send End-Of-Interrupt to PIC DATA port
+ pop ax
+ assumes ds,Data
+ dec [nInt8Count] ; exiting, decrement entry count
+
+;----------------------------------------------------------------------------
+;If we are on a 386 restore all registers.
+;----------------------------------------------------------------------------
+ test cs:[CodeFixWinFlags],WF_WIN286
+ jnz @F
+.386
+ pop gs
+ pop fs
+ popad
+.286p
+@@:
+ pop ds
+ assumes ds,nothing
+
+ iret
+
+tddISR endp
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@asm tddEvent |
+; Handle an event when it is due.
+;
+; For a valid event, the ID is saved in case the slot needs to be zeroed
+; and the type of event is checked. If this is a oneshot event
+; timer, the entry is freed. Note that at this point, as in the kill
+; event function, the Destroy flag must be checked to determine if the
+; slot is currently being checked. If so, the EVENT_DESTROYED flag must
+; be set instead of resetting the flag so that the function that was
+; interrupted can determine that the entry was killed while being
+; checked.
+;
+; After saving the event handle, the function checks to see if the event
+; is a One Shot, in which case it is destroyed, and the event's
+; resolution is removed from resolution the table.
+;
+; If on the other hand the event is a periodic one, the next calling
+; time is updated with the delay period. Note that if the event is far
+; behind, or the last minimum resolution was very large, many delay
+; periods are added to the next call time.
+;
+; If this is a BIOS event, then the fBIOSCall flag is set so that the
+; ISR chains to the old BIOS ISR instead of returning normally. If this
+; is a normal event, the parameters are pushed, and the driver callback
+; function is called using the DCB_FUNCTION flag.
+;
+; After returning from the callback, the return value from
+; <f>DriverCallback<d> is checked to determine if the callback succeeded.
+; If it did not, then the timer event needs to be removed. The timer
+; event however may have been a oneshot, in which case it was already
+; been removed before the call was made, and the EVENT_DESTROYED flag
+; may have been set, so it is just left alone. If the event is still
+; present however, it is destroyed after doing the checking to see if
+; this interrupt came while the event was being destroyed. Note that
+; there is no check to see if the event IDs are the same before destroying
+; the event. This is because if the callback failed, then the timer
+; structure cannot have changed, and no check is needed.
+;
+;@parm DS:DI |
+; Points to the event slot.
+;
+;@comm Uses AX,BX.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes es,nothing
+ assumes ds,Data
+
+cProc tddEvent, <NEAR, PUBLIC>, <>
+cBegin
+ push dx
+
+ mov dx,[di].evID
+ test [di].evFlags,TIME_PERIODIC
+ jnz tddEventPeriodic
+
+tddEventKillOneShot:
+ xor ax,ax
+ mov [di].evID,ax ; Invalidate slot
+ cmp [di].evDestroy,EVENT_CHECKING ; Did this interrupt a Kill?
+ jne @f
+ mov al,EVENT_DESTROYED ; Let the interrupted Kill know
+@@:
+ mov [di].evDestroy,al
+ mov [di].evCreate,ah ; pEvent->evCreate = FALSE
+ push dx
+ push cx
+ cCall tddEndMinPeriod,<[di].evResolution>
+ pop cx
+ pop dx
+ jmp tddEventCallback
+
+tddEventPeriodic:
+ mov ax,[di].evDelay.lo
+ mov bx,[di].evDelay.hi
+@@:
+ add [di].evTime.lo,ax
+ adc [di].evTime.hi,bx
+ jl @b
+
+tddEventCallback:
+ test [di].evFlags,TIME_BIOSEVENT
+ jz tddEventDriverCallback
+ inc [fBIOSCall]
+ jmp tddEventExit
+
+tddEventDriverCallback:
+ push cx
+ push es
+ ;
+ ; call DriverCallback() in MMSYSTEM
+ ;
+ push [di].evCallback.hi ; execute callback function
+ push [di].evCallback.lo
+ push DCB_FUNCTION or DCB_NOSWITCH; callback flags
+ push dx ; idTimer
+ xor dx,dx
+ push dx ; msg = 0
+ push [di].evUser.hi ; dwUser
+ push [di].evUser.lo
+ push dx ; dw1 = 0
+ push dx
+ push dx ; dw2 = 0
+ push dx
+ call DriverCallback ; execute callback function
+ pop es
+ or ax,ax ; Check for a successful return
+ jnz tddEventSucceed ; If callback succeeded, just continue
+ cmp [di].evID,ax ; If the timer was already destroyed,
+ jz tddEventSucceed ; just leave
+ mov [di].evID,ax ; Else destroy the event
+ cmp [di].evDestroy,EVENT_CHECKING ; Did this interrupt a Kill?
+ jne @f
+ mov al,EVENT_DESTROYED ; Let the interrupted Kill know
+@@:
+ mov [di].evDestroy,al
+ mov [di].evCreate,ah ; pEvent->evCreate = FALSE
+ cCall tddEndMinPeriod,<[di].evResolution>
+
+tddEventSucceed:
+ pop cx
+
+tddEventExit:
+ pop dx
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; @doc INTERNAL
+;
+; @asm GetCounterElement | Low level routine which loads the tick count
+; from the timer counter device, and returns the number of ticks that
+; have already passed.
+;
+; @rdesc Returns the tick count in AX.
+;
+; @comm All registers preserved.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+public GetCounterElement
+GetCounterElement proc near
+
+ ; Get rid of any latched count if this is called during interrupt time
+ cmp [nInt8Count],1
+ jb @f
+ in al,TMR_CNTR_0
+ IO_Delay
+ in al,TMR_CNTR_0
+
+@@:
+ ; read counter first time
+ xor ax,ax ; LATCH counter 0 command
+ out TMR_CTRL_REG,al ; send command
+
+ in al,TMR_CNTR_0 ; read low byte
+ mov ah,al
+ in al,TMR_CNTR_0 ; read high byte
+ xchg al,ah
+ sub ax,wProgTime ; Convert to number of ticks already past
+ neg ax
+
+ ret
+
+GetCounterElement endp
+
+sEnd
+
+end
diff --git a/private/mvdm/wow16/timer/makefile b/private/mvdm/wow16/timer/makefile
new file mode 100644
index 000000000..fb815d1f9
--- /dev/null
+++ b/private/mvdm/wow16/timer/makefile
@@ -0,0 +1,138 @@
+#
+# constructs timer.drv
+#
+# Defines:
+# DEBUG - Enable debug code
+# STRICT - Build a version with STRICT enabled
+#
+
+NAME =timer
+EXT =drv
+OBJFIRST=$Zlibinit.obj
+OBJ1 =$Ztimer.obj $Zlocal.obj $Zstartend.obj $Zapi.obj $Zmath.obj
+
+OBJ =$(OBJ1)
+LIBS =..\lib\libw ..\lib\mdllcew ..\lib\mmsystem
+INCS = -I. -I..\inc -I..\..\inc
+
+OPT = -Oxws
+
+########## Path definition so we find 16 bit tools ##########
+# Also works around stupid bug in RC 3.1 that doesn't allow rcpp.err to be
+# in a directory that is greater than 128 chars down the path, even if
+# rc 3.1 is running as an OS/2 app.
+
+PATH = $(_NTBINDIR)\private\mvdm\tools16;$(PATH)
+
+#
+# build a retail build
+#
+!if "$(NTDEBUG)" == "" || "$(NTDEBUG)" == "retail" && "$(NTDEBUG)" != "ntsdnodbg"
+
+CLOPT =-I..\inc -I.\rinc -I..\..\inc
+MASMOPT =-I..\inc -I..\..\inc
+LINKOPT =
+RC =rc16 -i..\inc
+OBJD =
+Z = .\retail^\
+MMDEBUG =
+
+#
+# build a full debug build
+#
+!else
+CDEBUG =-Zd -Odi
+ADEBUG =-Zd
+LDEBUG =/LI
+
+CLOPT =$(CDEBUG) -DDEBUG -I..\inc -I.\rinc -I..\..\inc
+MASMOPT =$(ADEBUG) -DDEBUG -I..\inc -I..\..\inc
+LINKOPT =$(LDEBUG)
+RC =rc16 -DDEBUG -i..\inc -i..\mmsystem\rinc
+OBJD =
+Z = .\debug^\
+MMDEBUG = DEBUG=1
+
+!endif
+
+
+!if "$(STRICT)" == "YES"
+TYPES =-DSTRICT
+!else
+TYPES =
+!endif
+
+#
+# NOTE
+#
+# this code is compiled *without* windows prolog/epilog (no -Gw)
+# thus all exported routines, must have _loadds
+#
+
+CC = cl16 -c -Alnw -G2s -Zp -W3 $(CLOPT) $(OPT) $(TYPES)
+ASM = masm -Mx -t -D?QUIET $(MASMOPT)
+LINK = link16 /NOD/NOE/MAP/ALIGN:16 $(LINKOPT)
+
+.c{$Z}.obj:
+ $(CC) -Fo$*.obj $(@B).c
+
+.asm{$Z}.obj:
+ $(ASM) -DSEGNAME=_TEXT $(@B).asm, $*.obj;
+
+
+###################################
+
+all: $(NAME).$(EXT) $(NAME).sym
+
+$(NAME).$(EXT): $(OBJFIRST) $(OBJ) $(NAME).def $(NAME).res
+ $(LINK) @<<
+$(OBJFIRST) +
+$(OBJ1),
+$(NAME).$(EXT),
+$(NAME),
+$(LIBS),
+$(NAME).def
+<<
+ $(RC) -t $(NAME).res $(NAME).$(EXT)
+ @mapsym /n $*.map
+ -binplace timer.drv timer.map timer.sym
+
+RES_DIR =.\messages\usa
+
+$(NAME).rc: $(RES_DIR)\$(NAME).rc
+ @copy $(RES_DIR)\$(NAME).rc
+
+$(NAME).rcv: $(RES_DIR)\$(NAME).rcv
+ @copy $(RES_DIR)\$(NAME).rcv
+
+$(NAME).res: $(NAME).rc $(NAME).rcv ..\inc\common.ver
+ $(RC) -r $(NAME).rc
+
+
+
+############## clean ##############
+clean: cleanup all
+
+cleanup:
+ -@del $(NAME).$(EXT)
+ -@del $(NAME).res
+ -@del *.sym
+ -@del *.map
+ -@del *.lib
+ -@del $Z*.cod
+ -@del $Z*.obj
+ -@del *.rcv
+ -@del *.rc
+
+# START Dependencies
+api.obj: api.asm timer.inc
+
+libinit.obj: libinit.asm timer.inc
+
+local.obj: local.asm timer.inc
+
+startend.obj: startend.asm timer.inc
+
+timer.obj: timer.asm timer.inc
+
+# END Dependencies
diff --git a/private/mvdm/wow16/timer/math.asm b/private/mvdm/wow16/timer/math.asm
new file mode 100644
index 000000000..0a08b3c75
--- /dev/null
+++ b/private/mvdm/wow16/timer/math.asm
@@ -0,0 +1,349 @@
+ page ,132
+;---------------------------Module-Header-------------------------------;
+; Module Name: MATH.ASM
+;
+; Contains FIXED point math routines.
+;
+; Created: Sun 30-Aug-1987 19:28:30
+; Author: Charles Whitmer [chuckwh]
+;
+; Copyright (c) 1987 Microsoft Corporation
+;-----------------------------------------------------------------------;
+
+?WIN = 0
+?PLM = 1
+?NODATA = 0
+?DF=1
+PMODE=1
+
+ .xlist
+ include cmacros.inc
+; include windows.inc
+ include timer.inc
+ .list
+
+UQUAD struc
+uq0 dw ?
+uq1 dw ?
+uq2 dw ?
+uq3 dw ?
+UQUAD ends
+
+; The following structure should be used to access high and low
+; words of a DWORD. This means that "word ptr foo[2]" -> "foo.hi".
+
+LONG struc
+lo dw ?
+hi dw ?
+LONG ends
+
+sBegin Code286
+ assumes cs,Code286
+ assumes ds,nothing
+ assumes es,nothing
+
+;---------------------------Public-Routine------------------------------;
+; qdiv
+;
+; This is an extended precision divide routine which is intended to
+; emulate the 80386 64 bit/32 bit DIV instruction. We don't have the
+; 32 bit registers to work with, but we pack the arguments and results
+; into what registers we do have. We will divide two unsigned numbers
+; and return the quotient and remainder. We will do INT 0 for overflow,
+; just like the 80386 microcode. This should ease conversion later.
+;
+; Entry:
+; DX:CX:BX:AX = UQUAD Numerator
+; SI:DI = ULONG Denominator
+; Returns:
+; DX:AX = quotient
+; CX:BX = remainder
+; Registers Destroyed:
+; none
+; History:
+; Tue 26-Jan-1988 00:02:09 -by- Charles Whitmer [chuckwh]
+; Wrote it.
+;-----------------------------------------------------------------------;
+ assumes ds,nothing
+ assumes es,nothing
+
+cProc qdiv,<PUBLIC,NEAR>,<si,di>
+ localQ uqNumerator
+ localD ulDenominator
+ localD ulQuotient
+ localW cShift
+cBegin
+
+; stuff the quad word into local memory
+
+ mov uqNumerator.uq0,ax
+ mov uqNumerator.uq1,bx
+ mov uqNumerator.uq2,cx
+ mov uqNumerator.uq3,dx
+
+
+; check for a zero Numerator
+
+ or ax,bx
+ or ax,cx
+ or ax,dx
+ jz qdiv_exit_relay ; quotient = remainder = 0
+
+; handle the special case when the denominator lives in the low word
+
+ or si,si
+ jnz not_that_special
+
+; calculate (DX=0):CX:BX:uqNumerator.uq0 / (SI=0):DI
+
+ cmp di,1 ; separate out the trivial case
+ jz div_by_one
+ xchg dx,cx ; CX = remainder.hi = 0
+ mov ax,bx
+ div di
+ mov bx,ax ; BX = quotient.hi
+ mov ax,uqNumerator.uq0
+ div di ; AX = quotient.lo
+ xchg bx,dx ; DX = quotient.hi, BX = remainder.lo
+ifdef WIMP
+ or ax,ax ; clear OF
+endif
+qdiv_exit_relay:
+ jmp qdiv_exit
+
+; calculate (DX=0):(CX=0):BX:uqNumerator.uq0 / (SI=0):(DI=1)
+
+div_by_one:
+ xchg dx,bx ; DX = quotient.hi, BX = remainder.lo = 0
+ mov ax,uqNumerator.uq0 ; AX = quotient.lo
+ jmp qdiv_exit
+not_that_special:
+
+; handle the special case when the denominator lives in the high word
+
+ or di,di
+ jnz not_this_special_either
+
+; calculate DX:CX:BX:uqNumerator.uq0 / SI:(DI=0)
+
+ cmp si,1 ; separate out the trivial case
+ jz div_by_10000h
+ mov ax,cx
+ div si
+ mov cx,ax ; CX = quotient.hi
+ mov ax,bx
+ div si ; AX = quotient.lo
+ xchg cx,dx ; DX = quotient.hi, CX = remainder.hi
+ mov bx,uqNumerator.uq0 ; BX = remainder.lo
+ifdef WIMP
+ or ax,ax ; clear OF
+endif
+ jmp qdiv_exit
+
+; calculate (DX=0):CX:BX:uqNumerator.uq0 / (SI=1):(DI=0)
+
+div_by_10000h:
+ xchg cx,dx ; DX = quotient.hi, CX = remainder.hi = 0
+ mov ax,bx ; AX = quotient.lo
+ mov bx,uqNumerator.uq0 ; BX = remainder.lo
+ jmp qdiv_exit
+not_this_special_either:
+
+; normalize the denominator
+
+ mov dx,si
+ mov ax,di
+ call ulNormalize ; DX:AX = normalized denominator
+ mov cShift,cx ; CX < 16
+ mov ulDenominator.lo,ax
+ mov ulDenominator.hi,dx
+
+
+; shift the Numerator by the same amount
+
+ jcxz numerator_is_shifted
+ mov si,-1
+ shl si,cl
+ not si ; SI = mask
+ mov bx,uqNumerator.uq3
+ shl bx,cl
+ mov ax,uqNumerator.uq2
+ rol ax,cl
+ mov di,si
+ and di,ax
+ or bx,di
+ mov uqNumerator.uq3,bx
+ xor ax,di
+ mov bx,uqNumerator.uq1
+ rol bx,cl
+ mov di,si
+ and di,bx
+ or ax,di
+ mov uqNumerator.uq2,ax
+ xor bx,di
+ mov ax,uqNumerator.uq0
+ rol ax,cl
+ mov di,si
+ and di,ax
+ or bx,di
+ mov uqNumerator.uq1,bx
+ xor ax,di
+ mov uqNumerator.uq0,ax
+numerator_is_shifted:
+
+; set up registers for division
+
+ mov dx,uqNumerator.uq3
+ mov ax,uqNumerator.uq2
+ mov di,uqNumerator.uq1
+ mov cx,ulDenominator.hi
+ mov bx,ulDenominator.lo
+
+; check for case when Denominator has only 16 bits
+
+ or bx,bx
+ jnz must_do_long_division
+ div cx
+ mov si,ax
+ mov ax,uqNumerator.uq1
+ div cx
+ xchg si,dx ; DX:AX = quotient
+ mov di,uqNumerator.uq0 ; SI:DI = remainder (shifted)
+ jmp short unshift_remainder
+must_do_long_division:
+
+; do the long division, part IZ@NL@%
+
+ cmp dx,cx ; we only know that DX:AX < CX:BX!
+ jb first_division_is_safe
+ mov ulQuotient.hi,0 ; i.e. 10000h, our guess is too big
+ mov si,ax
+ sub si,bx ; ... remainder is negative
+ jmp short first_adjuster
+first_division_is_safe:
+ div cx
+ mov ulQuotient.hi,ax
+ mov si,dx
+ mul bx ; fix remainder for low order term
+ sub di,ax
+ sbb si,dx
+ jnc first_adjuster_done ; The remainder is UNSIGNED! We have
+first_adjuster: ; to use the carry flag to keep track
+ dec ulQuotient.hi ; of the sign. The adjuster loop
+ add di,bx ; watches for a change to the carry
+ adc si,cx ; flag which would indicate a sign
+ jnc first_adjuster ; change IF we had more bits to keep
+first_adjuster_done: ; a sign in.
+
+; do the long division, part II
+
+ mov dx,si
+ mov ax,di
+ mov di,uqNumerator.uq0
+ cmp dx,cx ; we only know that DX:AX < CX:BX!
+ jb second_division_is_safe
+ mov ulQuotient.lo,0 ; i.e. 10000h, our guess is too big
+ mov si,ax
+ sub si,bx ; ... remainder is negative
+ jmp short second_adjuster
+second_division_is_safe:
+ div cx
+ mov ulQuotient.lo,ax
+ mov si,dx
+ mul bx ; fix remainder for low order term
+ sub di,ax
+ sbb si,dx
+ jnc second_adjuster_done
+second_adjuster:
+ dec ulQuotient.lo
+ add di,bx
+ adc si,cx
+ jnc second_adjuster
+second_adjuster_done:
+ mov ax,ulQuotient.lo
+ mov dx,ulQuotient.hi
+
+; unshift the remainder in SI:DI
+
+unshift_remainder:
+ mov cx,cShift
+ jcxz remainder_unshifted
+ mov bx,-1
+ shr bx,cl
+ not bx
+ shr di,cl
+ ror si,cl
+ and bx,si
+ or di,bx
+ xor si,bx
+remainder_unshifted:
+ mov cx,si
+ mov bx,di
+ifdef WIMP
+ or ax,ax ; clear OF
+endif
+qdiv_exit:
+cEnd
+
+;---------------------------Public-Routine------------------------------;
+; ulNormalize
+;
+; Normalizes a ULONG so that the highest order bit is 1. Returns the
+; number of shifts done. Also returns ZF=1 if the ULONG was zero.
+;
+; Entry:
+; DX:AX = ULONG
+; Returns:
+; DX:AX = normalized ULONG
+; CX = shift count
+; ZF = 1 if the ULONG is zero, 0 otherwise
+; Registers Destroyed:
+; none
+; History:
+; Mon 25-Jan-1988 22:07:03 -by- Charles Whitmer [chuckwh]
+; Wrote it.
+;-----------------------------------------------------------------------;
+ assumes ds,nothing
+ assumes es,nothing
+
+cProc ulNormalize,<PUBLIC,NEAR>
+cBegin
+
+; shift by words
+
+ xor cx,cx
+ or dx,dx
+ js ulNormalize_exit
+ jnz top_word_ok
+ xchg ax,dx
+ or dx,dx
+ jz ulNormalize_exit ; the zero exit
+ mov cl,16
+ js ulNormalize_exit
+top_word_ok:
+
+; shift by bytes
+
+ or dh,dh
+ jnz top_byte_ok
+ xchg dh,dl
+ xchg dl,ah
+ xchg ah,al
+ add cl,8
+ or dh,dh
+ js ulNormalize_exit
+top_byte_ok:
+
+; do the rest by bits
+
+next_byte:
+ inc cx
+ add ax,ax
+ adc dx,dx
+ jns next_byte
+ulNormalize_exit:
+cEnd
+
+sEnd
+
+ end
diff --git a/private/mvdm/wow16/timer/messages/usa/timer.rc b/private/mvdm/wow16/timer/messages/usa/timer.rc
new file mode 100644
index 000000000..94c191c88
--- /dev/null
+++ b/private/mvdm/wow16/timer/messages/usa/timer.rc
@@ -0,0 +1,8 @@
+#include <windows.h>
+#include "timer.rcv"
+
+STRINGTABLE LOADONCALL MOVEABLE DISCARDABLE
+BEGIN
+ 1, "Timer Driver"
+ 2, "Cannot find the VTDAPI.386 file. The multimedia timers are not available.\nMake sure VTDAPI.386 is in your Windows SYSTEM directory and the\ndevice=VTDAPI.386 line is included in the [386Enh] section of the SYSTEM.INI file."
+END
diff --git a/private/mvdm/wow16/timer/messages/usa/timer.rcv b/private/mvdm/wow16/timer/messages/usa/timer.rcv
new file mode 100644
index 000000000..4e43776c0
--- /dev/null
+++ b/private/mvdm/wow16/timer/messages/usa/timer.rcv
@@ -0,0 +1,12 @@
+/********************************************************************/
+/* TIMER.RCV */
+/********************************************************************/
+#include <version.h>
+
+#define VER_FILETYPE VFT_DRV
+#define VER_FILESUBTYPE VFT2_DRV_INSTALLABLE
+#define VER_FILEDESCRIPTION_STR "Timer driver for PC compatibles"
+#define VER_INTERNALNAME_STR "timer.drv"
+#define VER_ORIGINALFILENAME_STR "timer.drv"
+
+#include <common.ver>
diff --git a/private/mvdm/wow16/timer/njumps.mac b/private/mvdm/wow16/timer/njumps.mac
new file mode 100644
index 000000000..7a5e26fbf
--- /dev/null
+++ b/private/mvdm/wow16/timer/njumps.mac
@@ -0,0 +1,36 @@
+ .xlist
+
+ irp q,<c,a,ae,b,be,z,e,s>
+
+nj&q &macro dest
+ local jump
+ jn&q jump
+ if2
+ if dest ge ($-126-2)
+ if dest le ($+127+3)
+ %out nj&q dest :: can be replaced with j&q dest
+ endif
+ endif
+ endif
+ jmp dest
+jump label near
+ &endm
+
+njn&q &macro dest
+ local jump
+ j&q jump
+ if2
+ if dest ge ($-126-2)
+ if dest le ($+127+3)
+ %out njn&q dest :: can be replaced with jn&q dest
+ endif
+ endif
+ endif
+ jmp dest
+jump label near
+ &endm
+
+ endm
+
+ .list
+ \ No newline at end of file
diff --git a/private/mvdm/wow16/timer/startend.asm b/private/mvdm/wow16/timer/startend.asm
new file mode 100644
index 000000000..045a2f801
--- /dev/null
+++ b/private/mvdm/wow16/timer/startend.asm
@@ -0,0 +1,285 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; STARTEND.ASM
+;
+; Copyright (c) Microsoft Corporation 1989, 1990. All rights reserved.
+;
+; This module contains the routines which initialize, and clean
+; up the driver after Libentry/WEP/Enable/Diable called by windows.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+?PLM=1 ; pascal call convention
+?WIN=0 ; Windows prolog/epilog code
+?DF=1
+
+ PMODE=1
+ .xlist
+ include cmacros.inc
+ include int31.inc
+ include windows.inc
+ include mmddk.inc
+ include mmsystem.inc
+ include timer.inc
+ .list
+
+ externA __WinFlags
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Local data segment
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+sBegin DATA
+
+ ; ISR support
+
+ public lpOLDISR
+ lpOldISR dd ?
+
+ifdef RMODE_INT
+ public RModeOldISR
+ RModeOldISR dd 0
+
+ public RModeCodeSegment
+ RModeCodeSegment dw ?
+
+endif ;RMODE_INT
+
+ externW Events
+ externW wNextTime
+
+sEnd DATA
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Code segment
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ externFP tddISR ; in local.asm
+ externFP tddSetInterruptPeriodFar ; in timer.asm
+
+ifdef RMODE_INT
+ externW RmodeDataSegment ; in local.asm
+ externFP tddRModeISR ; in local.asm
+endif
+ externFP GetSelectorBase ; kernel
+ externFP AllocCStoDSAlias ; kernel
+ externFP FreeSelector ; kernel
+
+sBegin CodeInit
+ assumes cs,CodeInit
+ assumes ds,Data
+ assumes es,nothing
+
+;----------------------------Private-Routine----------------------------;
+; SegmentFromSelector
+;
+; Converts a selector to a segment...note that this routine assumes
+; the memory pointed to by the selector is below the 1Meg line!
+;
+; Params:
+; AX = selector to convert to segment
+;
+; Returns:
+; AX = segment of selector given
+;
+; Error Returns:
+; None
+;
+; Registers Destroyed:
+; none
+;
+;-----------------------------------------------------------------------;
+
+assumes ds,Data
+assumes es,nothing
+
+SegmentFromSelector proc near
+
+ cCall GetSelectorBase,<ax> ;DX:AX = base of selector
+rept 4
+ shr dx,1
+ rcr ax,1
+endm
+ ;AX now points to *segment* (iff selector is based below 1Mb)
+
+ ret
+
+SegmentFromSelector endp
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; @doc INTERNAL
+;
+; @api WORD | Enable | This function enables the driver. It
+; will hook interrupts and validate the hardware.
+;
+; @rdesc Returns 1 if successfull, and 0 otherwise.
+;
+; @comm This function is automatically invoked when the library is
+; first loaded. It is included so that win386 could call it
+; when it switches VMs.
+;
+; @xref Disable
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+cProc Enable286 <FAR, PUBLIC> <si, di>
+
+cBegin
+ ; make sure clock interrupts are disabled until after
+ ; service routine has been initialized!!
+ AssertSLI
+ cli
+
+ ; get the currently owned timer interrupt vector
+
+ ; get interrupt vector, and specify timer interrupt number
+; mov ax,03500H + TIMERINTERRUPT
+; push es
+; int 21h ; get the current vector in ES:BX
+; mov lpOldISR.Sel,es
+; mov lpOldISR.Off,bx ; save the old vector
+; pop es
+;
+; ; set vector to our isr
+;
+; ; set interrupt vector function, and specify the timer interrupt number
+; mov ax,02500h + TIMERINTERRUPT
+; push ds
+; mov dx,seg tddISR
+; mov ds,dx
+; assumes ds,nothing
+; mov dx,offset tddISR
+; int 21h ; set the new vector
+; pop ds
+; assumes ds,DATA
+;
+; mov ax,[wNextTime]
+; not ax
+; mov [wNextTime],ax ; force set of period
+; call tddSetInterruptPeriodFar
+
+ifdef RMODE_INT
+ ;
+ ; if running under DOSX set the RMODE interrupt too
+ ;
+ mov ax,__WinFlags
+ test ax,WF_PMODE
+ jz enable_no_dosx
+
+ mov ax,seg tddRModeISR
+ call SegmentFromSelector
+
+ or dx,dx ; ACK! above 1Mb
+ jnz enable_no_dosx
+
+ mov [RModeCodeSegment],ax ; save the segment of the code segment
+
+ mov ax,ds ; get SEGMENT of our data segment
+ call SegmentFromSelector
+ push ax ; save on stack
+
+ mov ax,seg tddRModeISR ; write data SEGMENT into _INTERRUPT
+ cCall AllocCStoDSAlias,<ax> ; code segment -- requires a data alias
+ mov es,ax
+ pop ax
+ mov es:[RModeDataSegment],ax
+ cCall FreeSelector,<es> ; don't need CS alias any longer
+
+ mov ax,Get_RM_IntVector ; get the real mode IRQ0 vector
+ mov bl,DOSX_IRQ + TIMERINTERRUPT
+ int 31h ; DOSX get real mode vector in CX:DX
+
+ mov RModeOldISR.lo,dx ; save old ISR
+ mov RModeOldISR.hi,cx
+
+ mov cx,RModeCodeSegment ; CX:DX --> real mode ISR
+ mov dx,offset tddRModeISR
+
+ mov ax,Set_RM_IntVector ; DOSX Set Vector Function
+ int 31h ; Set the DOS vector real mode
+
+enable_no_dosx:
+endif
+ sti
+
+ mov ax,1
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; @doc INTERNAL
+;
+; @api WORD | Disable | This function disables the driver.
+; It disables the hardware, unhooks interrupts and removes
+; all time events from the queue.
+;
+; @rdesc Returns 1 if successfull, and 0 otherwise.
+;
+; @comm This function is called automatically when Windows unloads
+; the library and invokes the WEP() function. It is included
+; here so that WIN386 can use it when switching VMs.
+;
+; @xref Enable
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+cProc Disable286 <FAR, PUBLIC> <si, di>
+
+ ; note that all this is in the reverse order to Enable
+
+cBegin
+ AssertSLI
+ cli
+ ; set timer back to 55ms BIOS service
+ xor cx,cx ; 65536 ticks per period
+
+ mov al,TMR_MODE3_RW ; Read/Write counter 0 mode 3 (two bytes)
+ out TMR_CTRL_REG,al
+
+ mov al,cl
+ out TMR_CNTR_0,al ; write low byte
+
+ mov al,ch
+ out TMR_CNTR_0,al ; write high byte
+
+ifdef RMODE_INT
+ ;
+ ; check for a REAL mode int handler and un-hook it.
+ ;
+ mov dx,RModeOldISR.lo
+ mov cx,RModeOldISR.hi
+ jcxz disable_no_dosx
+
+ mov bl,DOSX_IRQ + TIMERINTERRUPT
+ mov ax,Set_RM_IntVector ;DOSX Set Vector Function
+ int 31h ;Set the DOS vector real mode
+
+disable_no_dosx:
+endif
+
+ ; restore the old interrupt vector
+
+ mov ax,02500h + TIMERINTERRUPT
+ ; set interrupt vector function, and specify the timer interrupt number
+
+ push ds
+ lds dx,lpOldISR
+ assumes ds,nothing
+ int 21h ; reset the old vector
+ pop ds
+ assumes ds,DATA
+
+ sti
+ mov ax,1
+cEnd
+
+sEnd
+
+end
diff --git a/private/mvdm/wow16/timer/sysinfo.inc b/private/mvdm/wow16/timer/sysinfo.inc
new file mode 100644
index 000000000..31d999879
--- /dev/null
+++ b/private/mvdm/wow16/timer/sysinfo.inc
@@ -0,0 +1,53 @@
+;******************************************************************************
+;
+; (C) Copyright MICROSOFT Corp., 1989-1990
+;
+; Title: sysinfo.inc - structure & equates for INT 15h service 0C0h
+;
+; Version: 1.00
+;
+; Date: 28-Mar-1989
+;
+; Author: RAP
+;
+;------------------------------------------------------------------------------
+;
+; Change log:
+;
+; DATE REV DESCRIPTION
+; ----------- --- -----------------------------------------------------------
+; 28-Mar-1989 RAP
+;
+;==============================================================================
+
+; System Descriptor Structure returned from INT 15h, service C0h
+
+SysDescStruc STRUC
+SD_len dw ?
+SD_model db ?
+SD_submodel db ?
+SD_ROM_rev db ?
+SD_feature1 db ?
+SD_feature2 db ?
+SD_feature3 db ?
+SD_feature4 db ?
+SD_feature5 db ?
+SysDescStruc ENDS
+
+
+; Feature byte 1 bits assignments:
+
+SF1_FD_uses_DMA3 = 10000000b
+SF1_FD_uses_DMA3_bit = 7
+SF1_PIC_2_present = 01000000b
+SF1_PIC_2_present_bit = 6
+SF1_RealTimeClock = 00100000b
+SF1_RealTimeClock_bit = 5
+SF1_INT15s_called = 00010000b
+SF1_INT15s_called_bit = 4
+SF1_ExtEventWait = 00001000b
+SF1_ExtEventWait_bit = 3
+SF1_EBIOS_allocated = 00000100b
+SF1_EBIOS_allocated_bit = 2
+SF1_MicroChnPresent = 00000010b
+SF1_MicroChnPresent_bit = 1
diff --git a/private/mvdm/wow16/timer/timer.asm b/private/mvdm/wow16/timer/timer.asm
new file mode 100644
index 000000000..decab4ae9
--- /dev/null
+++ b/private/mvdm/wow16/timer/timer.asm
@@ -0,0 +1,842 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; TIMER.ASM
+;
+; Copyright (c) Microsoft Corporation 1991. All rights reserved.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+?PLM=1 ; pascal call convention
+?WIN=0 ; Windows prolog/epilog code
+?DF=1
+PMODE=1
+
+.xlist
+include cmacros.inc
+include windows.inc
+include mmddk.inc
+include mmsystem.inc
+include timer.inc
+.list
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; External functions
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+externNP GetCounterElement
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Local data segment
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+sBegin DATA
+
+externW CurTime
+externW nInt8Count
+externW wProgTime
+externW wNextTime
+externW IntCount
+externD dTickUpdate
+
+public Events,wNextID
+
+; This structure is used in keeping track of all the current events,
+; including any BIOS event.
+;
+Events EventStruct MAXEVENTS DUP (<>)
+
+; This value is used as an ever incrementing counter that is OR'ed into
+; handles returned from <f>tddSetTimerEvent<d>. This is so that events
+; can be uniquely identified in <f>tddKillTimerEvent<d>.
+;
+wNextID dw 0
+
+;
+; The following is a table of timer resolution byte counters. Each entry
+; N represents an interest in having the timer resolution set to N+1 MS.
+; Thus there are TDD_MAX386RESOLUTION to TDD_MINRESOLUTION entries to
+; represent 1..55 MS. Each time <f>tddBeginMinPeriod<d> is called with
+; a timer period, the appropriate entry is incremented, and each time
+; <f>tddEndMinPeriod<d> is called with a timer period, that entry is
+; decremented. Presumably there is a one to one match on the Begin and
+; End minimum period calls.
+;
+; This is of course all a workaround for the fact that the timer chip
+; cannot be immediately reprogrammed the way it is wired in PCs in the
+; mode in which it needs to be run, thus a separate resolution table
+; must be kept in order to allow applications to set up a minimum
+; resolution before actually setting any events.
+;
+tddIntPeriodTable db TDD_MINRESOLUTION dup (0)
+
+public wMaxResolution,wMinPeriod
+wMaxResolution dw TDD_MAX386RESOLUTION
+wMinPeriod dw TDD_MIN386PERIOD
+
+sEnd DATA
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Code segment
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+sBegin Code286
+ assumes cs,Code286
+ assumes ds,data
+ assumes es,nothing
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Public exported functions
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddBeginMinPeriod |
+; Increments sets the specified resolution in the table of period
+; resolutions. This optionally programming the timer for a new
+; higher resolution if the parameter passed is a new minimum.
+;
+;@parm WORD | wPeriod |
+; Contains a resolution period from wMaxResolution through 55
+; milliseconds.
+;
+;@rdesc Returns 0 for success, else TIMERR_NOCANDO if the resolution period
+; passed was out of range.
+;
+;@uses ax,bx,dx.
+;
+;@xref tddEndMinPeriod,tddSetInterruptPeriod.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes es,nothing
+ assumes ds,Data
+
+cProc tddBeginMinPeriod <PUBLIC,FAR> <>
+ parmW wPeriod
+cBegin
+ mov ax,TIMERR_NOCANDO ; Initialize return to error return
+
+ mov bx,wPeriod
+ cmp bx,[wMaxResolution]
+ jb tddBeginMinPeriodExit ; Return TIMERR_NOCANDO
+ cmp bx,TDD_MINRESOLUTION
+ ja tddBeginMinPeriodExit ; Return TIMERR_NOCANDO
+ dec bx ; Zero based resolution slot entries
+ cmp tddIntPeriodTable[bx],0FFh
+ifdef DEBUG
+ jne tddBeginMinPeriodInRange
+ inc bx ; Show correct period in error
+ DOUT <tddBeginMinPeriod(#bx) overflow>
+ jmp tddBeginMinPeriodExit ; Return TIMERR_NOCANDO
+tddBeginMinPeriodInRange:
+else
+ je tddBeginMinPeriodExit ; Return TIMERR_NOCANDO
+endif
+
+ inc tddIntPeriodTable[bx] ; Increment resolution[entry - 1]
+ cmp tddIntPeriodTable[bx],1 ; Don't set period if entry is >1
+ jne @f
+ call tddSetInterruptPeriod
+@@:
+ xor ax,ax ; Return ok (FALSE)
+
+tddBeginMinPeriodExit:
+ cwd ; Set to zero
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddEndMinPeriod |
+; Decrements the specified resolution in the table of period resolutions
+; that was presumably set previously with a <f>tddBeginMinPeriod<d> call.
+; This optionally programming the timer for a new lower resolution if
+; the parameter passed removed the current minimum.
+;
+;@parm WORD | wPeriod |
+; Contains a resolution period from 1 through 55 milliseconds.
+;
+;@rdesc Returns 0 for success, else TIMERR_NOCANDO if the resolution period
+; passed was out of range.
+;
+;@uses ax,bx,dx.
+;
+;@xref tddBeginMinPeriod,tddSetInterruptPeriod.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes es,nothing
+ assumes ds,Data
+
+cProc tddEndMinPeriod <PUBLIC,FAR> <>
+ parmW wPeriod
+cBegin
+ mov ax,TIMERR_NOCANDO ; Initialize return to error return
+
+ mov bx,wPeriod
+ cmp bx,[wMaxResolution]
+ jb tddEndMinPeriodExit ; Return TIMERR_NOCANDO
+ cmp bx,TDD_MINRESOLUTION
+ ja tddEndMinPeriodExit ; Return TIMERR_NOCANDO
+ dec bx ; Zero based resolution slot entries
+ cmp tddIntPeriodTable[bx],0
+ifdef DEBUG
+ jne tddEndMinPeriodInRange
+ inc bx ; Show correct period in error
+ DOUT <tddEndMinPeriod(#bx) underflow>
+ jmp tddEndMinPeriodExit ; Return TIMERR_NOCANDO
+tddEndMinPeriodInRange:
+else
+ je tddEndMinPeriodExit ; Return TIMERR_NOCANDO
+endif
+
+ dec tddIntPeriodTable[bx] ; Decrement resolution[entry - 1]
+ jnz @f ; No need to set interrupt period
+ call tddSetInterruptPeriod
+@@:
+ xor ax,ax ; Return ok (FALSE)
+
+tddEndMinPeriodExit:
+ cwd ; Set to zero
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@func void | tddSetInterruptPeriod |
+; This function optionally programs the timer with a new interrupt
+; period if the maximum resolution in the resolution table has changed.
+if 0 ; !!!
+;
+; If the function is being called outside of interrupt time, the function
+; must first turn off interrupts so that the resolution table is not
+; changed between the time the the function finds a resolution to set,
+; and the time it starts to program the timer. Once the timer begins to
+; be programmed, it won't send any more interrupts until programming is
+; finished. The documentation does not specify that, but it was verified
+; through testing the timer. If however the function is being called
+; during a timer interrupt, there is no need to turn off interrupts, as
+; the resolution table will not be changed at that time.
+;
+endif
+; In any case, the resolution table is searched, looking for the first
+; non-zero entry, which is taken as the maximum resolution the timer
+; should currently be programmed to. If nothing is set in the table,
+; then the programming defaults to the minimum resolution of 55 MS.
+;
+; Once an entry is found, it is compared to the previous programmed
+; time, not the currently programmed time. This is in case an interrupt
+; has not occurred since the last time the timer was programmed using
+; this function. Note that in converting to clock ticks, any period
+; that overflows a single word is taken to be 65536 ticks, which is the
+; maximum number allowable in the timer, and is equal to almost 55 MS.
+;
+; If a new time must be programmed, the new resolution is sent out to
+; the timer, and eventually interrupts are set again.
+;
+;@rdesc Nothing.
+;
+;@uses ax,bx,dx.
+;
+;@xref tddBeginMinPeriod,tddEndMinPeriod.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes es,nothing
+ assumes ds,Data
+
+cProc tddSetInterruptPeriodFar <PUBLIC,FAR> <>
+cBegin
+ call tddSetInterruptPeriod
+cEnd
+
+cProc tddSetInterruptPeriod <PUBLIC,NEAR> <>
+
+cBegin
+ xor bx, bx ; Start at the beginning of the table
+
+ EnterCrit ; !!!
+
+tdd_sip_loop:
+ cmp bx,TDD_MINRESOLUTION ; Has the last entry been passed up?
+ je tdd_sip_Set_This_Period ; Jump out using TDD_MINRESOLUTION
+ inc bx
+ cmp tddIntPeriodTable[bx-1],0
+ je tdd_sip_loop
+
+tdd_sip_Set_This_Period:
+ mov ax,bx
+ call tddMsToTicks
+
+ or dx,dx ; Check for overflow of WORD
+ jz tdd_sip_period_ok
+ xor ax,ax ; Set to 64k instead.
+tdd_sip_period_ok:
+
+ cmp ax,[wNextTime] ; Compare with last programmed time
+ je tdd_sip_exit ; No need to reprogram
+
+ DOUT <tddSetInterruptPeriod: ms=#bx ticks=#ax>
+
+ mov bx,ax ; Save this value
+ mov [wNextTime],bx ; This is now the last programmed time
+
+ mov al, TMR_MODE2_RW ; Set counter 0 to mode 2
+ out TMR_CTRL_REG, al
+
+ mov al, bl
+ out TMR_CNTR_0, al
+ mov al, bh
+ out TMR_CNTR_0, al
+
+tdd_sip_exit:
+ LeaveCrit ; !!!
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddSetTimerEvent |
+; Adds a timer event, possibly periodic to the event queue.
+;
+; A timer event is set by first looking through the table of external
+; event slots, trying to locate a currently vacant slot that is not
+; currently being checked or deleted. If one is found, the Create flag
+; is test-and-set in order to try and grab the slot.
+;
+; If this succeeds, the slot can be set up with the information, and the
+; resolution entered into the event resolution table. The very last
+; thing that occurs is setting the ID element of the slot. This is so
+; that an inturrupt will not try to execute this event until all the
+; parameters are set. This means that the event could be executed
+; immediately after the ID is set, but before this function actually
+; returns to the caller.
+;
+; If the function fails to grab the event slot, it means that either an
+; interrupt occurred, and another event was created in this slot, or that
+; this function is running during an interrupt that occurred while a new
+; event was being created. In any case, the slot must be passed by.
+;
+; If an interrupt had occurred during this function, it also means that
+; some other event could have been freed, but already passed by, so the
+; function misses it. The function cannot go back though, because it
+; might actually be processing during an interrupt, and the slot being
+; passed by would continue in its present state, and thus cause an
+; infinite loop to occur.
+;
+; When checking for a free event slot, not only is the ID checked, but
+; also the state of the Destroy flag. This flag is used during the kill
+; event function to indicate that an event slot is currently being
+; checked or destroyed, or was destroyed during an interrupt while the
+; slot was being checked. In either case, it indicates that this
+; function is being called during interrupt time, and the slot cannot be
+; re-used until the flag is removed by the kill event function. This
+; means that during the kill event function, there is one less event
+; slot that can be used than normal.
+;
+; Once the ID of the event slot is set, the event can be called. Note
+; that the event may then be called before this function even returns.
+;
+;@rdesc Returns a handle which identifies the timer event, or NULL if the
+; requested event is invalid, or the event queue is full.
+;
+;@xref tddKillTimerEvent
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,Data
+ assumes es,nothing
+
+cProc tddSetTimerEvent <PUBLIC,FAR> <si,di,es>
+ parmD pTIMEREVENT
+ localW wResolution
+ localW wEventID
+cBegin
+ les si,pTIMEREVENT ; timer event structure
+ mov ax,es
+ or ax,si
+ ; (pTIMEREVENT != NULL)
+ jz SetEventError ; NULL pointer, exit
+
+ mov bx,es:[si].te_wDelay
+
+ ; ((te_wDelay >= wMinPeriod) && (te_wDelay <= TDD_MAXPERIOD))
+ cmp bx,[wMinPeriod] ; delay less than min period?
+ jb SetEventError ; Yes, error
+
+ cmp bx,TDD_MAXPERIOD ; delay greater than max period?
+ ja SetEventError ; Yes, error
+
+ ; (!te_wResolution)
+ mov ax,es:[si].te_wResolution
+ or ax,ax ; resolution not set?
+ jz SetDefaultResolution ; Yes, set default resolution
+
+ ; ((te_wResolution >= TDD_MINRESOLUTION) && (te_wResolution <= wMaxResolution))
+ cmp ax,TDD_MINRESOLUTION ; resolution less than min resolution?
+ jb @f ; No, skip to next check
+ mov ax,TDD_MINRESOLUTION
+
+@@:
+ cmp ax,[wMaxResolution] ; resolution greater than max resolution?
+ ja @f ; No, skip to next check
+ mov ax,[wMaxResolution]
+
+@@:
+ ; (te_wResolution > te_wDelay)
+ cmp bx,ax ; delay less than resolution?
+ jb SetDefaultResolution ; Yes, set default resolution
+
+ jmp short SetEventValidParms
+
+SetEventError:
+ xor ax,ax ; Return NULL
+ jmp SetEventExit
+
+SetDefaultResolution:
+ ; te_wResolution = min(TDD_MINRESOLUTION, te_wDelay)
+ mov ax,TDD_MINRESOLUTION
+ cmp bx,ax ; delay less than min resolution?
+ ja SetEventValidParms ; No, just use min resolution then
+ mov ax,bx ; Yes, use the period as the resolution
+
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
+SetEventValidParms:
+ mov wResolution,ax ; save calculated resolution
+
+ lea di,Events ; DS:DI --> events
+ xor ax,ax ; event slot = 0
+
+SetEventFindLoop:
+ ; if (!pEvent->evID && !pEvent->evDestroy)
+ cmp [di].evID,0
+ jne SetEventFindLoopNext
+ cmp BYTE PTR [di].evDestroy,0
+ jne SetEventFindLoopNext
+ mov bl,1
+ xchg BYTE PTR [di].evCreate,bl ; Test and set Create flag
+ or bl,bl
+ jz SetEventFindLoopFound
+
+SetEventFindLoopNext:
+ ; pEvent++, wEventID++
+ add di,SizeEvent
+ inc ax
+ ; wEventID < MAXEVENTS
+ cmp ax,MAXEVENTS
+ jb SetEventFindLoop
+
+ ; Return NULL
+ xor ax,ax ; Slot not found, return NULL
+ jmp SetEventExit
+
+SetEventFindLoopFound:
+ ;
+ ; combine the slot index and wNextID to produce a unique id to
+ ; return to the caller
+ ;
+ add [wNextID],MASKINCREMENT
+ jz SetEventFindLoopFound ; Ensure a non-zero mask
+ or ax,[wNextID] ; Add in the mask
+ mov wEventID,ax ; Save the event
+ errnz MAXEVENTS-16
+
+ ; tddBeginMinPeriod(pEvent->evResolution)
+ mov ax,wResolution
+ mov [di].evResolution,ax
+ cCall tddBeginMinPeriod <ax>
+
+ ; pEvent->evDelay = tddMsToTicks(pTIMEREVENT->te_wDelay)
+ mov ax,es:[si].te_wDelay
+ call tddMsToTicks
+ mov [di].evDelay.lo,ax
+ mov [di].evDelay.hi,dx
+
+ ; pEvent->evCallback = pTIMEREVENT->te_lpFunction
+ mov ax,es:[si].te_lpFunction.lo
+ mov dx,es:[si].te_lpFunction.hi
+ mov [di].evCallback.lo,ax
+ mov [di].evCallback.hi,dx
+
+ ; pEvent->evUser = pTIMEREVENT->te_dwUser
+ mov ax,es:[si].te_dwUser.lo
+ mov dx,es:[si].te_dwUser.hi
+ mov [di].evUser.lo,ax
+ mov [di].evUser.hi,dx
+
+ ; pEvent->evFlags = pTIMEREVENT->te_wFlags
+ mov ax,es:[si].te_wFlags
+ mov [di].evFlags,ax
+
+@@:
+ mov bx,[IntCount] ; check for interrupt occurring
+ call GetCounterElement ; Get number of ticks passed
+ xor cx,cx
+ add ax,dTickUpdate.lo ; Add extra currently skipped.
+ adc cx,dTickUpdate.hi
+ cmp bx,[IntCount]
+ jne @b ; If interrupt occurred try again
+
+ ; pEvent->evTime = pEvent->evDelay + GetCounterElement + dTickUpdate
+ mov bx,[di].evDelay.lo
+ mov dx,[di].evDelay.hi
+ add bx,ax
+ adc dx,cx
+ mov [di].evTime.lo,bx
+ mov [di].evTime.hi,dx
+
+ ; pEvent->evID = wEventID
+ mov ax,wEventID
+ mov [di].evID,ax
+ ; Return wEventID
+
+SetEventExit:
+ xor dx,dx
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddKillTimerEvent |
+; Removes a timer event from the event queue. If the event was periodic,
+; this is the only way to discontinue operation. Otherwise, this may be
+; used to remove an unwanted one shot event in case of application
+; termination.
+;
+; A timer event it killed by trying to grab the Destroy flag in a two
+; step process, which succeeds only if the function was able to grab
+; the slot before any interrupt destroyed the event.
+;
+; After verifying that the event handle is valid, the function checks the
+; Destroy flag to determine if this function is being called during
+; interrupt time, and interrupted another process killing the same
+; timer. If this is so, the function just aborts before wasting time
+; doing any other flag setting.
+;
+; The function then sets the Destroy flag to a EVENT_CHECKING state,
+; grabbing the current state of the flag in order to use when setting
+; the final state of the Destroy flag if the function succeeds.
+;
+; If the event handles match, the Destroy flag is set to a
+; EVENT_DESTROYING state. At this point, the Destroy flag is either in
+; the state in which this function left it, or an interrupt occurred, and
+; the flag was set to a EVENT_DESTROYED state durring interrupt time. If
+; an interrupt ended up destroying the event out from under this call,
+; the function is exited after clearing the Destroy flag so that the
+; event slot can be used. Note that the event slot cannot be used until
+; the function exits so that the EVENT_DESTROYED flag is not disturbed.
+;
+; If the flag is grabbed, no other call can destroy the event, and the
+; event will not be executed during interrupt time. As was previously
+; mentioned, the Destroy flag is either reset, or if this function was
+; called during interrupt time while the event was being checked, the
+; flag is set to EVENT_DESTROYED.
+;
+; The resolution entered into the event resolution table is removed.
+; The very last thing to occur is resetting the Create flag. At that
+; point the event slot could be re-used if the Destroy flag was reset.
+;
+; Note that if the event handles do not match, the Destroyed flag is also
+; reset so that it can be used in creating a new event when this event
+; is destroyed, which may have happened while checking the handles.
+;
+;@parm WORD | wID | The event handle returned by the <f>tddSetTimerEvent<d>
+; function which identifies the event to destroy.
+;
+;@rdesc Returns 0 if timer event destroyed, or TIMERR_NOCANDO if the
+; event was not registered in the system event queue.
+;
+;@xref tddSetTimerEvent
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,Data
+ assumes es,nothing
+
+cProc tddKillTimerEvent <PUBLIC,FAR> <si,di>
+ parmW wID
+cBegin
+ mov ax,wID
+ and ax,MASKFILTER ; Remove ID mask first
+ errnz MAXEVENTS-16
+
+ imul ax,SizeEvent ; Retrieve slot address
+ lea di,Events
+ add di,ax
+
+ ; if (pEvent->evDestroy == EVENT_DESTROYING)
+ cmp BYTE PTR [di].evDestroy,EVENT_DESTROYING ; If interrupting a destroy,
+ je KillEventError ; Leave with error
+
+ mov bl,EVENT_CHECKING
+ xchg BYTE PTR [di].evDestroy,bl ; Test and set Destroy check
+
+ ; if (pEvent->evID == wID)
+ mov ax,wID
+ cmp [di].evID,ax
+ jne KillEventRelease ; Wrong ID
+
+ mov bh,EVENT_DESTROYING
+ xchg BYTE PTR [di].evDestroy,bh ; Test and set Destroying
+
+ cmp bh,EVENT_CHECKING ; Was destroy interrupted?
+ jne KillEventRelease ; Slot has already been deleted
+
+ mov [di].evID,0 ; Invalidate ID
+
+ cmp bl,EVENT_CHECKING ; Did this interrupt a destroy?
+ jne @f ; No, was already ZERO
+ mov bl,EVENT_DESTROYED ; Let the interrupted destroy know
+@@:
+ mov BYTE PTR [di].evDestroy,bl
+ cCall tddEndMinPeriod,<[di].evResolution>
+
+ ; pEvent->evCreate = FALSE
+ mov BYTE PTR [di].evCreate,0 ; Free up slot
+ xor ax,ax ; Return 0
+ jmp KillEventExit
+
+KillEventRelease:
+ ; Free up checking flag
+ mov BYTE PTR [di].evDestroy,0
+
+KillEventError:
+ ; Invalid ID or was deleted during interrupt time (test and set failed)
+ mov ax,TIMERR_NOCANDO
+
+KillEventExit:
+ cwd ; Set to zero
+cEnd
+
+ assumes ds,Data
+ assumes es,nothing
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+public GetTickCount
+GetTickCount proc near
+
+@@:
+ mov cx,[IntCount] ; Save current interrupt count
+ call GetCounterElement ; Get number of ticks passed
+
+ xor dx,dx
+ xor bx,bx
+ add ax,CurTime[0] ; Add total tick count to current number past
+ adc dx,CurTime[2]
+ adc bx,CurTime[4]
+
+ cmp cx,[IntCount] ; Interrupt occurred while getting count
+ jne @b ; Get the count again
+ ret
+GetTickCount endp
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddGetSystemTime |
+; Returns a system time in milliseconds.
+;
+;@rdesc Returns a 32 bit value in dx:ax representing the number of milliseconds
+; since the timer driver was started.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,Data
+ assumes es,nothing
+
+cProc tddGetSystemTime <PUBLIC,FAR> <>
+
+cBegin
+ call GetTickCount
+ call tddTicksToMs
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@asm tddGetTickCount |
+; Returns a system time in clock ticks.
+;
+;@rdesc Returns a 48 bit value in bx:dx:ax representing the number of clock
+; ticks since the timer driver was started. A C interface would only
+; be able to access the lower 32 bits of this value.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,Data
+ assumes es,nothing
+
+cProc tddGetTickCount <PUBLIC,FAR> <>
+
+cBegin
+ call GetTickCount
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddGetDevCaps |
+; Fills in TIMECAPS structure.
+;
+;@parm <t>LPTIMECAPS<d> | lpTIMECAPS |
+; Points to the structure to fill.
+;
+;@parm WORD | wSize |
+; Indicates the size of the structure passed. Normally this should be
+; the size of the <t>TIMECAPS<d> structure this module was compiled with.
+;
+;@rdesc Returns 0 on success, or TIMERR_NOCANDO on failure.
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,nothing
+ assumes es,nothing
+
+cProc tddGetDevCaps <PUBLIC,FAR> <si,ds>
+ parmD lpTIMECAPS
+ parmW wSize
+cBegin
+ mov ax,TIMERR_NOCANDO ; Initialize return to an error state
+
+ cmp wSize,(SIZE TIMECAPS) ; Check the size of the structure passed
+ jne Caps_Exit
+
+ lds si,lpTIMECAPS ; timer event structure
+
+ push ds
+ mov ax,DGROUP
+ mov ds,ax
+ assumes ds,Data
+ mov ax,[wMinPeriod] ; Fill in the structure
+ pop ds
+ assumes ds,nothing
+
+ mov dx,TDD_MAXPERIOD
+ mov [si].tc_wPeriodMin,ax
+ mov [si].tc_wPeriodMax,dx
+ xor ax,ax ; Return success
+
+Caps_Exit:
+ cwd ; Set to zero
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddTicksToMs |
+; Convert clock ticks (1.19318 MHz) to milliseconds (1000 Hz)
+;
+;@parm BX:DX:AX |
+; Tick count to convert to milliseconds.
+;
+;@rdesc DX:AX |
+; Converted millisecond count.
+;
+;@comm There is a 0.0000005% positive error in the approximation of
+; 1193.18 ticks per millisecond by the process to avoid floating point
+; arithmetic, which effectively divides by 1193.179993 instead.
+;
+; time `Ms' = clock ticks `T' / 1193.18
+;
+; In order to be able to use fixed point, the math actually done is:
+;
+; Ms = (T * 10000h) / (DWORD)(1193.18 * 10000h)
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,Data
+ assumes es,nothing
+
+cProc tddTicksToMs <PUBLIC,NEAR> <si,di>
+
+cBegin
+ externNP qdiv ; In math.asm
+
+; qdiv
+;
+; Entry:
+; DX:CX:BX:AX = QUAD Numerator
+; SI:DI = LONG Denominator
+; Returns:
+; DX:AX = quotient
+; CX:BX = remainder
+
+ ; multiply BX:DX:AX by 10000h and place result in DX:CX:BX:AX for qdiv
+ mov cx,dx
+ mov dx,bx
+ mov bx,ax
+ xor ax,ax
+
+ ; SI:DI = 1193.18 * 10000h (essentially in 16.16 fixed notation)
+ mov si,1193 ; 1193 * 10000h
+ mov di,11796 ; 0.18 * 10000h = 11796.48
+
+ call qdiv ; (T * 10000h) / (DWORD)(1193.18 * 10000h)
+cEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+;@doc INTERNAL TIMER
+;
+;@api DWORD | tddMsToTicks |
+; Convert milliseconds (1000 Hz) to clock ticks (1.193 MHz).
+;
+;@parm AX |
+; Millisecond count to convert to clock ticks
+;
+;@rdesc DX:AX |
+; Converted clock tick count.
+;
+;@comm There is a slight error in the approximation of 1193.18 ticks per
+; millisecond by the process to avoid floating point arithmetic, which
+; effectively multiplies by 1193.1875 instead.
+;
+; clock ticks `T' = time `Ms' * 1193.18
+;
+; In order to be able to use fixed point, the math actually done is
+;
+; T = (Ms * (WORD)(1193.18 * 20h)) / 20h
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ assumes ds,Data
+ assumes es,nothing
+
+cProc tddMsToTicks <PUBLIC,NEAR> <>
+
+cBegin
+ mov dx,38182 ; 1193.18 * 20h = 38181.76
+ mul dx ; Ms * (WORD)(1193.18 * 20h)
+ shr ax,5 ; Divide the result by 20h
+ mov cx,dx ; Save original first
+ shl cx,11 ; Keep only the bottom part
+ shr dx,5 ; Shift top part of return
+ or ax,cx ; Put two halves of bottom part together
+cEnd
+
+sEnd Code286
+
+end
diff --git a/private/mvdm/wow16/timer/timer.def b/private/mvdm/wow16/timer/timer.def
new file mode 100644
index 000000000..4b10a73d0
--- /dev/null
+++ b/private/mvdm/wow16/timer/timer.def
@@ -0,0 +1,34 @@
+LIBRARY TIMER
+
+DESCRIPTION 'timer:Timer'
+
+EXETYPE WINDOWS
+
+PROTMODE
+
+;CODE MOVEABLE DISCARDABLE LOADONCALL SHARED
+DATA PRELOAD FIXED SINGLE
+
+SEGMENTS
+ INIT_CODE PRELOAD MOVEABLE DISCARDABLE SHARED
+ FIXED_TEXT PRELOAD FIXED SHARED
+
+ ; we want the 286 segment to come in only when needed
+ ; what we want is LOADONCALL FIXED (but this does not work,
+ ; it works for boot time drivers but not DLLs)
+ ;
+ ; so we make it LOADONCALL MOVEABLE, and PageLock it in
+ ; place if we need it, see libinit.asm!Lib286Init
+ ;
+ ; FIXED_286 LOADONCALL FIXED SHARED
+
+ FIXED_286 MOVEABLE DISCARDABLE LOADONCALL SHARED
+
+HEAPSIZE 0
+
+EXPORTS
+ WEP @1 RESIDENTNAME
+ DriverProc @2 RESIDENTNAME
+
+IMPORTS
+ WINFLAGS = KERNEL.178
diff --git a/private/mvdm/wow16/timer/timer.inc b/private/mvdm/wow16/timer/timer.inc
new file mode 100644
index 000000000..cd4da3679
--- /dev/null
+++ b/private/mvdm/wow16/timer/timer.inc
@@ -0,0 +1,267 @@
+;
+; timer.inc
+;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; segments
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+createSeg FIXED_TEXT,Code386, word, public, CODE
+createSeg FIXED_TEXT,CodeFixed, word, public, CODE
+createSeg FIXED_286, Code286, word, public, CODE
+createSeg INIT_CODE, CodeInit, word, public, CODE
+
+createSeg _DATA,Data,word,public,DATA,DGROUP
+defgrp DGROUP,Data
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Equates and structure definitions
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+IDS_ERRORTITLE equ 1
+IDS_ERRORTEXT equ 2
+
+;RMODE_INT equ 1
+
+ifdef DEBUG
+ TDD_GETTICK equ 42
+ TDD_GETRINTCOUNT equ 43
+ TDD_GETPINTCOUNT equ 44
+endif
+
+TDD_MINRESOLUTION equ 55 ; minimum resolution. (ms)
+TDD_MAX386RESOLUTION equ 1 ; maximum resolution. (ms)
+TDD_MAX286RESOLUTION equ 2 ; maximum resolution. (ms)
+
+TDD_MAXPERIOD equ 0FFFFh ; maximum ms period.
+TDD_MIN386PERIOD equ 01h ; minimum ms period.
+TDD_MIN286PERIOD equ 02h ; minimum ms period.
+
+TMR_CNTR_0 equ 040h ; counter 0 - programmable system interrupt
+TMR_CTRL_REG equ 043h ; timer control word register
+
+TMR_MODE2_RW equ 00110100b ; Read/Write counter 0 mode 2 (two bytes)
+ ; (countdown mode)
+
+TMR_MODE3_RW equ 00110110b ; Read/Write counter 0 mode 3 (two bytes)
+ ; (square wave mode)
+
+
+PS2_SysCtrlPortB equ 61h ; IBM PS2 System Control Port B
+PS2_LatchBit equ 80h ; Latch clear bit for PS2
+PICDATA equ 020h ; Programmable interrupt controller port
+SPECIFIC_EOI equ 01100000b ; IRQ 0 end-of-interrupt PIC command
+EOI_STATUS equ 00001011b ; Status of pending EOIs
+
+TIME_BIOSEVENT equ 8000h ; special flag for bios event
+
+TIMERINTERRUPT equ 8 ; interrupt number for timer counter
+
+
+; The following defines the maximum number of simultaneous events which
+; can be queued. This value covers event slots 0 to 15. Note that this
+; is 4 bits of data, which is relied upon in the code.
+;
+; The two constants defined after are used to increment and filter the
+; mask added to the event slot IDs to create an event handle to return.
+; They illustrate the dependence upon the MAXEVENTS constant.
+
+MAXEVENTS equ 16
+
+MASKINCREMENT equ 0010h
+MASKFILTER equ 000fh
+
+; The following flags are used during the process of killing an event.
+;
+; The first flag indicates that an event slot is being checked by the
+; kill event function, and that the EVENT_DESTROYED flag should be set
+; if the pevent is killed during interrupt time before the original
+; function completes its check.
+;
+; The second flag indicates that an event is currently being killed, and
+; should not be allowed to execute. This is set in the kill timer
+; function, and either cleared, or replaced with the EVENT_DESTROYED
+; flag when complete.
+;
+; The third flag can be set either in the interrupt handler for oneshot
+; events, or in the kill timer function. This is only set if the timer
+; was currently being checked when an interrupt occurred, and the event
+; was killed by the interrupt. This flag disallows any new event to be
+; created in the event slot until the flag is cleared by the original
+; kill event function exiting.
+
+EVENT_CHECKING equ 1
+EVENT_DESTROYING equ 2
+EVENT_DESTROYED equ 4
+
+EventStruct STRUC
+
+evTime dd ? ; actual time when the event will go off (in ticks)
+
+evDelay dd ? ; event delay time (in ticks)
+
+evCallback dd ? ; call back function
+
+evUser dd ? ; parameter to call-back function
+
+evResolution dw ? ; event resolution (in Ms)
+
+evID dw ? ; timer event id
+
+evFlags dw ? ; bits 1,0 = flags (one-shot/periodic)
+
+evCreate db ? ; Creation flag
+
+evDestroy db ? ; Destroying flag
+
+EventStruct ENDS
+
+ errnz <(SIZE EventStruct) and 1>
+
+ SizeEvent equ <(SIZE EventStruct)>
+
+; Macro to cause a delay in between I/O accesses to the same device.
+
+IO_Delay MACRO
+ jmp $+2
+ENDM
+
+; this macro makes sure interrupts are disabled in debug driver
+AssertCLI MACRO
+ifdef DEBUG
+ push ax
+ pushf
+ pop ax
+ test ah,2
+ jz @f
+ int 3
+@@: pop ax
+endif
+ENDM
+
+; this macro makes sure interrupts are enabled in debug driver
+AssertSLI MACRO
+ifdef DEBUG
+ push ax
+ pushf
+ pop ax
+ test ah,2
+ jnz @f
+ int 3
+@@: pop ax
+endif
+ENDM
+
+DefineInfo MACRO
+ifdef DEBUG
+externNP savedebuginfo
+endif
+ENDM
+
+SaveInfo MACRO value
+ifdef DEBUG
+ifdef savedebuginfo
+ push ax
+ mov ax,value
+ call savedebuginfo
+ pop ax
+else
+ safd
+endif
+endif
+ENDM
+
+; The DOS Extender used for Standard mode Windows remaps the master 8259 from
+; Int vectors 8h-Fh to 50h-57h. In order to speed up com port interrupt
+; response as much as possible, this driver hooks real mode interrupts
+; when running in Standard mode. It currently uses the following adjustment
+; value to hook the real hardware int vector. When time permits, this
+; HARDCODED equate should be changed to be adjustible at run time.
+
+DOSX_IRQ equ (50h - 8h) ; Adjustment for DOSX remapping the
+ ; master 8259 from 8h to 50h
+; WinFlags[0] constants...remove when included in windows.inc
+
+WF_PMODE equ 01h
+WF_CPU286 equ 02h
+WF_CPU386 equ 04h
+WF_CPU486 equ 08h
+WF_WIN286 equ 10h ; WF_STANDARD
+WF_WIN386 equ 20h ; WF_ENHANCED
+WF_CPU086 equ 40h
+WF_CPU186 equ 80h
+
+; Interrupt 31h service call equates
+
+Get_RM_IntVector equ <(Int31_Int_Serv SHL 8 ) OR Int_Get_Real_Vec>
+Set_RM_IntVector equ <(Int31_Int_Serv SHL 8 ) OR Int_Set_Real_Vec>
+
+GetSystemConfig equ 0c0h
+
+;---------------------------------Macro---------------------------------;
+;
+; EnterCrit
+;
+; saves the current state of the interrupt flag on the stack then
+; disables interrupts.
+;
+; Registers Destroyed:
+; BX, FLAGS
+;
+;------------------------------------------------------------------------;
+
+EnterCrit macro
+ local no_cli
+ pushf
+ pushf
+ pop cx
+ test ch,2 ; if interrupts are already off, dont blow
+ jz no_cli ; ... ~300 clocks doing the cli
+ cli
+no_cli:
+endm
+
+;---------------------------------Macro---------------------------------;
+;
+; LeaveCrit
+;
+; restore the interrupt state saved by EnterCrit
+;
+; Registers Destroyed:
+; CX, FLAGS
+;
+;------------------------------------------------------------------------;
+
+LeaveCrit macro reg
+ local no_sti
+ pop cx
+ test ch, 2
+ jz no_sti
+ sti
+no_sti:
+endm
+
+;------------------------------------------------------------------------;
+;------------------------------------------------------------------------;
+
+externFP OutputDebugStr
+
+DOUT macro text
+ local string_buffer
+
+ifdef DEBUG
+
+_DATA segment
+string_buffer label byte
+ db "&text&",13,10,0
+_DATA ends
+
+ push DataBASE
+ push DataOFFSET string_buffer
+ call OutputDebugStr
+endif
+ endm