;       IBM/Microsoft Confidential
;
;       Copyright (c) IBM Corporation 1987, 1989
;       Copyright (c) Microsoft Corporation 1987, 1989
;
;       All Rights Reserved

        PAGE    78, 132
        TITLE   DHTMR, DosHlp Tmr Handling

;***    DHTMR.ASM
;
;       Programmable Interval Timer (PIT -- Intel 8254) Ctr 0 device
;       driver (Tmr).  This module contains the code for handling the
;       machine specific interface to the Tmr device driver.
;
;       NOTES   This module is compiled as a 32-bit code.

.XLIST
        INCLUDE basemaca.inc    ;Base macros
        INCLUDE osmaca.inc      ;Os macros
        INCLUDE mi.inc
        INCLUDE doshlp.inc      ;DosHlp macros
        INCLUDE int.inc         ;Interrupt manager structures
        INCLUDE sysinvar.inc    ;Definition of DOSHLP_DATASEL
        INCLUDE timer.inc       ;Physical Timer 0 (Tmr) defs.
        INCLUDE pvwxport.inc
        INCLUDE iodelay.inc
.LIST
        INCLUDE segments.inc    ;DosHlp segment definitions


        CPUMode 386
        ModName dhtmr


DosHlpData      SEGMENT                 ;Non-discardable 16-bit data.

;       Rollover count (initialized in Clock driver).
;               Low  DWORD = Current rollover count.
;               High DWORD = Next rollover count.

        qwTmrRollover   qw_s    <0,0>   ;Rollover count.

;       Tmr value (updated at every Tmr rollover in Clock driver).
;       This is initialized here to 0.

        qwTmr           qw_s    <0,0>

DosHlpData      ENDS



_TEXT           SEGMENT                 ;Discardable 16-bit code.

;***EP  DHRegisterTmrDD
;
;       Send the Tmr Device Driver pointers to the Tmr value and the Tmr
;       rollover count in DosHlp address space AND the Tmr frequency.
;
;       Invoke: From assembly language (16-bit code) --
;               call DHF.DosHlpRegisterTmrDD
;
;       ENTRY   This function is ONLY called at Tmr Device Driver
;               Initialization and can thus be discarded.
;
;       EXIT    (DI:BX) -> qwTmrRollover (16:16) in DosHlp address space.
;               (DI:CX) -> qwTmr (16:16) in DosHlp address space.
;               (EAX) = Tmr (PIT Ctr 0) frequency (in hz)
;
;       USES    EAX, BX, CX, DI, EFlags
;
;   FS and GS must be preserved (DevHelp requirement)

Procedure DHRegisterTmrDD,far
        ASSUME CS:DGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING

        mov     di,DOSHLP_DATASEL
        mov     bx,OFFSET DosHlpData:qwTmrRollover
        mov     cx,OFFSET DosHlpData:qwTmr

        mov     eax,TIMERFREQ           ;(EAX) = Tmr Freq.

        RETC

EndProc DHRegisterTmrDD

_TEXT   ENDS



DosHlpCode      SEGMENT                 ;Non-discardable 16-bit code.

;***EP  DHTmr16QueryTime
;
;       Provide snapshot of IRQ0 high resolution timer (Programmable Interval
;       Timer (PIT) -- Intel 8254)
;
;       Invoke: From Assembly language (16-bit code) --
;               call DHF.DosHlpTmr16QueryTime
;
;       EXIT    QWORD (EDX:EAX) -- 64-bit time, where:
;               edx = high order 4 bytes of time
;               eax = low order 4 bytes of time
;
;       USES    EAX, EDX, EFlags
;
;       NOTES   If the state of the Programmable Interrupt Controller (PIC --
;               Intel 8259) Interrupt Request Register 0 (IRR0) changes while
;               executing this function, the PIT count is "assumed" to be
;               nearly 0.
;
;               This function ASSUMES that the PIC IRQ0 interrupt is being
;               serviced at at least the rollover frequency of the PIT Ctr 0.
;               Therefore, if IRR0 is detected as "set" upon entry to this
;               function, it is assumed that the PIT Ctr 0 will not rollover
;               before the PIT is read.
;
;               DO NOT call this routine in the Tmr device driver interrupt
;               handler between the increment of culTmrOv and the subsequent
;               EOI (end of IRQ0); a false time will result because the
;               ISR0 will still be set, but culTmrOv will have been updated.
;
;               DO NOT call this routine in the Tmr device driver interrupt
;               handler between the update of qwTmr and the subsequent
;               EOI (end of IRQ0); a false time will result because the
;               ISR0 will still be set, but qwTmr will have been updated.

Procedure DHTmr16QueryTime,far
        ASSUME CS:DGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING

        extrn DosHlpDelay: near

        push    ds
        push    cx

        mov     dx,DOSHLP_DATASEL
        mov     ds,dx
        ASSUME DS:DosHlpData

        pushf
        cli

;       It is necessary to check for ISR0 if IRR0 is not set, since IRR0
;       IRR0 will be reset when IRQ0 is acknowledged (=> ISR0 becomes set);
;       this routine could be called while servicing IRQ0.  For the sake of
;       optimization, it is simply faster to ALWAYS read the ISR0 first,
;       than to set-up the PIC for the IRR read twice (once now, and once
;       following the PIT read.)

        mov     al,OCW3_READ_ISR        ;Setup PIC for ISR Read.
        out     PIC1_PORT0,al
        call    DosHlpDelay
        in      al,PIC1_PORT0           ;Check if servicing IRQ0.
        call    DosHlpDelay
        mov     ah,al                   ;Save ISR0.
        mov     al,OCW3_READ_IRR        ;Setup PIC for IRR Read.
        out     PIC1_PORT0,al
        call    DosHlpDelay
        in      al,PIC1_PORT0           ;Check for pending IRQ0 at PIC.
        or      ah,al                   ;(AH) = [ISR0 | IRR0] (first poll).

        mov     al,(SC_CNT0 OR RW_LATCHCNT)     ;Latch PIT Ctr 0 command.
        out     PORT_CW,al
        call    DosHlpDelay
        in      al,PORT_CNT0            ;Read PIT Ctr 0, LSByte.
        call    DosHlpDelay
        movzx   edx,al                  ;Zero upper bytes of (EDX).
        in      al,PORT_CNT0            ;Read PIT Ctr 0, MSByte.
        mov     dh,al                   ;(DX) = PIT Ctr 0 count.
        in      al,PIC1_PORT0           ;Get IRR0 ASAP after PIT Ctr 0.
        neg     dx                      ;PIT counts down from 0h

;       (AH) = [ISR0 | IRR0] (1st poll); (AL) = IRR0 (2nd poll).
;       (EDX) = PIT Ctr 0 time.

        and     ax,( ((1 SHL TIMERIRQ) SHL 8) OR (1 SHL TIMERIRQ) )
        jnz     short t16qt6            ;Jump if IRQ0 pending.

;       Add in current time (qwTmr).

t16qt4: add     edx,[qwTmr].qw_ulLo
        mov     eax,edx
        mov     edx,[qwTmr].qw_ulHi
        adc     edx,0
        jo      short t16qt8

t16qtx: popf
        pop     cx
        pop     ds
        RETC

;       IRQ0 pending so add current rollover count.  No possibility of carry
;       since (PIT Ctr 0)<10000h and (rollover count)<=10000h.

t16qt6: add     edx,[qwTmrRollover].qw_ulLo
        or      ah,ah
        jnz     short t16qt4            ;Jump if IRR0 (1st poll) = 1.

;       Assume PIT Ctr 0 is approx. 0 since [ISR0 | IRR0] (1st poll) = 0,
;       while IRR0 (2nd poll) = 1 (=> rollover occurred while in this
;       function).

        mov     edx,[qwTmrRollover].qw_ulLo
        jmp     short t16qt4

;       On overflow, peg the time at its maximum value.

t16qt8: mov     eax,QW_MAX_LO
        mov     edx,QW_MAX_HI
        jmp     short t16qtx

        ASSUME DS:NOTHING

EndProc DHTmr16QueryTime

DosHlpCode      ENDS


DosHlp32Code    SEGMENT                 ;Non-discardable 32-bit code.



; 32-bit delay ... call near to avoid expanding the macro inline - kills
;                  code size ! Note, must be non-discardable !

Procedure DosHlp32Delay,near
        assume cs:DosHlp32Code,ds:nothing,es:nothing,ss:nothing
        dhIODelay32 ecx
        RETC
EndProc DosHlp32Delay




;***EP  DHTmrSetRollover
;
;       ENTRY   ECX = Next rollover count (Max. of TMR_MAX_ROLLOVER).
;
;       EXIT
;
;       USES    EAX, EFlags
;
;       NOTES   Rollover count of TMR_MAX_ROLLOVER (10000h) is equivalent to
;               a count of 0 for device => approx. 18.2 hz rollover rate.

Procedure DHTmrSetRollover,near
        ASSUME CS:DGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING

        push    ecx
        push    edx
        mov     edx,ecx
        pushfd
        cli

;BUGBUG 04-Apr-90,DaveBe,<Needs range checking.>

        SaveReg <ds>
        mov     ax,DOSHLP_DATASEL
        mov     ds,ax
        ASSUME DS:DosHlpData
        mov     [qwTmrRollover].qw_ulHi,edx     ;Store next rollover count.
        RestoreReg <ds>
        ASSUME DS:NOTHING

        mov     al,SC_CNT0+RW_LSBMSB+CM_MODE2
        out     PORT_CW,al              ;program count mode of timer 0
        call    DosHlp32Delay
        mov     al,dl
        out     PORT_CNT0,al            ;program timer 0 LSB count
        call    DosHlp32Delay
        mov     al,dh
        out     PORT_CNT0,al            ;program timer 0 MSB count

        popfd
        pop     edx
        pop     ecx
        RETC

EndProc DHTmrSetRollover


;***EP  DHTmr32QueryTime
;
;       Provide snapshot of IRQ0 high resolution timer (Programmable Interval
;       Timer (PIT) -- Intel 8254)
;
;       Invoke: From Assembly language (32-bit code) --
;               call DHF.DosHlpTmr32QueryTime
;
;       EXIT    QWORD (EDX:EAX) -- 64-bit time, where:
;               (EDX) = high order 4 bytes of time
;               (EAX) = low order 4 bytes of time
;
;       USES    EAX, EDX, EFlags.
;
;       NOTES   If the state of the Programmable Interrupt Controller (PIC --
;               Intel 8259) Interrupt Request Register 0 (IRR0) changes while
;               executing this function, the PIT count is "assumed" to be
;               nearly 0.
;
;               This function ASSUMES that the PIC IRQ0 interrupt is being
;               serviced at at least the rollover frequency of the PIT Ctr 0.
;               Therefore, if IRR0 is detected as "set" upon entry to this
;               function, it is assumed that the PIT Ctr 0 will not rollover
;               before the PIT is read.
;

;               DO NOT call this routine in the Tmr device driver interrupt
;               handler between the increment of qwTmr and the subsequent
;               EOI (end of IRQ0); a false time will result because the
;               ISR0 will still be set, but qwTmr will have been updated.

Procedure DHTmr32QueryTime,near
        ASSUME CS:DGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING

        push    ds
        push    ecx

        mov     dx,DOSHLP_DATASEL
        mov     ds,dx
        ASSUME DS:DosHlpData

        pushfd
        cli

;       It is necessary to check for ISR0 if IRR0 is not set, since IRR0
;       IRR0 will be reset when IRQ0 is acknowledged (=> ISR0 becomes set);
;       this routine could be called while servicing IRQ0.  For the sake of
;       optimization, it is simply faster to ALWAYS read the ISR0 first,
;       than to set-up the PIC for the IRR read twice (once now, and once
;       following the PIT read.)

        mov     al,OCW3_READ_ISR        ;Setup PIC for ISR Read.
        out     PIC1_PORT0,al
        call    DosHlp32Delay
        in      al,PIC1_PORT0           ;Check if servicing IRQ0.
        call    DosHlp32Delay
        mov     ah,al                   ;Save ISR0.
        mov     al,OCW3_READ_IRR        ;Setup PIC for IRR Read.
        out     PIC1_PORT0,al
        call    DosHlp32Delay
        in      al,PIC1_PORT0           ;Check for pending IRQ0 at PIC.
        or      ah,al                   ;(AH) = [ISR0 | IRR0] (first poll).

        mov     al,(SC_CNT0 OR RW_LATCHCNT)     ;Latch PIT Ctr 0 command.
        out     PORT_CW,al
        call    DosHlp32Delay
        in      al,PORT_CNT0            ;Read PIT Ctr 0, LSByte.
        call    DosHlp32Delay
        movzx   edx,al                  ;Zero upper bytes of (EDX).
        in      al,PORT_CNT0            ;Read PIT Ctr 0, MSByte.
        mov     dh,al                   ;(DX) = PIT Ctr 0 count.
        in      al,PIC1_PORT0           ;Get IRR0 ASAP after PIT Ctr 0.
        neg     dx                      ;PIT counts down from 0h

;       (AH) = [ISR0 | IRR0] (1st poll); (AL) = IRR0 (2nd poll).
;       (EDX) = PIT Ctr 0 time.

        and     ax,( ((1 SHL TIMERIRQ) SHL 8) OR (1 SHL TIMERIRQ) )
        jnz     short t32qt6            ;Jump if IRQ0 pending.

;       Add in current time (qwTmr).

t32qt4: add     edx,[qwTmr].qw_ulLo
        mov     eax,edx
        mov     edx,[qwTmr].qw_ulHi
        adc     edx,0
        jo      short t32qt8

t32qtx: popfd
        pop     ecx
        pop     ds

        RETC

;       IRQ0 pending so add current rollover count.  No possibility of carry
;       since (PIT Ctr 0)<10000h and (rollover count)<=10000h.

t32qt6: add     edx,[qwTmrRollover].qw_ulLo
        or      ah,ah
        jnz     short t32qt4            ;Jump if IRR0 (1st poll) = 1.

;       Assume PIT Ctr 0 is approx. 0 since [ISR0 | IRR0] (1st poll) = 0,
;       while IRR0 (2nd poll) = 1 (=> rollover occurred while in this
;       function).

        mov     edx,[qwTmrRollover].qw_ulLo
        jmp     short t32qt4

;       On overflow, peg the time at its maximum value.

t32qt8: mov     eax,QW_MAX_LO
        mov     edx,QW_MAX_HI
        jmp     short t32qtx

        ASSUME DS:NOTHING

EndProc DHTmr32QueryTime

DosHlp32Code    ENDS


        END
