From e611b132f9b8abe35b362e5870b74bce94a1e58e Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 16 May 2020 20:51:50 -0700 Subject: initial commit --- private/mvdm/wow16/timer/api.asm | 288 ++++++++ private/mvdm/wow16/timer/libinit.asm | 374 +++++++++++ private/mvdm/wow16/timer/local.asm | 701 ++++++++++++++++++++ private/mvdm/wow16/timer/makefile | 138 ++++ private/mvdm/wow16/timer/math.asm | 349 ++++++++++ private/mvdm/wow16/timer/messages/usa/timer.rc | 8 + private/mvdm/wow16/timer/messages/usa/timer.rcv | 12 + private/mvdm/wow16/timer/njumps.mac | 36 + private/mvdm/wow16/timer/startend.asm | 285 ++++++++ private/mvdm/wow16/timer/sysinfo.inc | 53 ++ private/mvdm/wow16/timer/timer.asm | 842 ++++++++++++++++++++++++ private/mvdm/wow16/timer/timer.def | 34 + private/mvdm/wow16/timer/timer.inc | 267 ++++++++ 13 files changed, 3387 insertions(+) create mode 100644 private/mvdm/wow16/timer/api.asm create mode 100644 private/mvdm/wow16/timer/libinit.asm create mode 100644 private/mvdm/wow16/timer/local.asm create mode 100644 private/mvdm/wow16/timer/makefile create mode 100644 private/mvdm/wow16/timer/math.asm create mode 100644 private/mvdm/wow16/timer/messages/usa/timer.rc create mode 100644 private/mvdm/wow16/timer/messages/usa/timer.rcv create mode 100644 private/mvdm/wow16/timer/njumps.mac create mode 100644 private/mvdm/wow16/timer/startend.asm create mode 100644 private/mvdm/wow16/timer/sysinfo.inc create mode 100644 private/mvdm/wow16/timer/timer.asm create mode 100644 private/mvdm/wow16/timer/timer.def create mode 100644 private/mvdm/wow16/timer/timer.inc (limited to 'private/mvdm/wow16/timer') 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 + errnz + errnz + errnz + errnz + errnz + + 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 ; these had better be the same! +errnz ; ProcTbl *must* follow MsgTbl + +cProc DriverProc + 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, ; enable driver + jmp short msg_makelong + +msg_DRV_DISABLE: + cCall Disable, + 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 + +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,,<> +; parmW silly_param +cBegin nogen + + errn$ Disable + +cEnd nogen + +cProc Disable,,<> +; 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,,<> +; 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, ; 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 <> + LocalV aszErrorTitle, 32 + LocalV aszErrorMsg, 256 +cBegin + lea ax, WORD PTR aszErrorTitle + cCall LoadString, + lea ax, WORD PTR aszErrorMsg + cCall LoadString, + lea ax, WORD PTR aszErrorTitle + lea bx, WORD PTR aszErrorMsg + cCall MessageBox, +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 + + ; + ; 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, ; get it low in memory + cCall GlobalPageLock, ; 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 tddEvent 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 +; DriverCallback 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, , <> +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,, + 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, +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 +#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 + +#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 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, + +nj&q ¯o 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 ¯o 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, ;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 + +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, ; code segment -- requires a data alias + mov es,ax + pop ax + mov es:[RModeDataSegment],ax + cCall FreeSelector, ; 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 + + ; 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 tddSetTimerEvent. This is so that events +; can be uniquely identified in tddKillTimerEvent. +; +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 tddBeginMinPeriod is called with +; a timer period, the appropriate entry is incremented, and each time +; tddEndMinPeriod 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 <> + 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 + 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 tddBeginMinPeriod 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 <> + 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 + 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 <> +cBegin + call tddSetInterruptPeriod +cEnd + +cProc tddSetInterruptPeriod <> + +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 + + 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 + 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 + + ; 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 tddSetTimerEvent +; 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 + 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 <> + +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 <> + +cBegin + call GetTickCount +cEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +;@doc INTERNAL TIMER +; +;@api DWORD | tddGetDevCaps | +; Fills in TIMECAPS structure. +; +;@parm LPTIMECAPS | 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 TIMECAPS structure this module was compiled with. +; +;@rdesc Returns 0 on success, or TIMERR_NOCANDO on failure. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + assumes ds,nothing + assumes es,nothing + +cProc tddGetDevCaps + 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 + +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 <> + +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 -- cgit v1.2.3