;SCCSID = @(#)clock02.asm  6.13 91/10/22
title   clock device driver -- PS/2  [clock02.asm]

; ****************************************************************************
; *                                                                          *
; *                       IBM/Microsoft Confidential                         *
; *                                                                          *
; *                 Copyright (c) IBM Corporation  1987, 1991, 1997          *
; *                 Copyright (c) Microsoft Corp.  1987, 1991                *
; *                           All Rights Reserved                            *
; *                                                                          *
; ****************************************************************************
;
; Change History
;
; 178847 5/27/97 PAT    Fix RTC at 128hz.  Optimize RTCINT codepath.
; 189422 10/28/97 PAT   Write century byte to CMOS if out-of-synch with year.
;                       This is a Year 2000 fix for older systems that boot
;                       to DOS occasionally.
; 182895 11/12/97 WHH   Fixed to use single driver for UNI/SMP.
;                       do NOT build with SMP flag.

PAGE    60,132
;
.386p                                   ; assemble for iAPX 386
.xlist

; define MONITOR and/or MISCSTRICT to get help when debugging, these are
; NOT to be defined for a retail build.
;;MONITOR         EQU     1               ; builds in monitoring code
;;MISCSTRICT      EQU     1               ; turns on extra checking

INCL_ERRORS     EQU     1
        include bseerr.inc              ; error numbers
INCL_MI         EQU     1
        INCLUDE rtc.inc                 ; Real Time Clock info
        INCLUDE mvdm.inc                ; equates and data for MVDM support
        INCLUDE devhlp.inc              ; the devhlp function eqs
        INCLUDE oldmaca.inc
        INCLUDE infoseg.inc             ; definitions of Global Info Segment
        INCLUDE devsym.inc              ; definition of DD Request Packet
        INCLUDE clkabios.inc            ; Structs for calling ABIOS
        INCLUDE clkdata.inc             ; Device driver specific
        INCLUDE pmode.inc
        INCLUDE abios.inc
        INCLUDE timer.inc               ; timer definitions
        INCLUDE clkseg.inc              ; segment definitions
        INCLUDE pvwxport.inc
        INCLUDE vtdptd.inc              ; vtimer/ptimer interface
        INCLUDE devhlpP.inc             ;76711
.list

CPUMode 386

        extrn   GTDAYS:Far
        extrn   FIXDAYOFWEEK:Near
        extrn   ClkVDDProc:near         ; Clk router routine
        extrn   PTBeep:near             ; beep service entry point
        extrn   PTBeepOff:near          ; service to turn speaker off
        extrn   PTVDMProc:near          ; vtimer/ptimer interface entry point
        extrn   PTTimer0:near           ; timer 0 ownership arbitration serv.

ClkData SEGMENT
; data segment externals
        extrn   DevHlp:DWORD
        extrn   SchedClock:DWORD
        extrn   InfSeg:DWORD
        extrn   MonTab:BYTE
        extrn   Update_Flag:BYTE
        extrn   pfnEOIRTC:WORD             ; d196223
        extrn   pfnEOITMR:WORD             ; d196223
        extrn   MS_DeltaPostEOI:WORD
        extrn   Int_Nested:BYTE
        extrn   Int_In_Progress:BYTE
        extrn   ATDataEnd:ABS

        extrn   AccumRTCFreqs:WORD      ; Actual-Requested Countdown Freqs
        extrn   ReqRTCFreq:WORD         ; Requested RTC frequency (by kernel)
        extrn   Hundred_Frac:BYTE
        extrn   MS_Fraction:WORD
        extrn   MS_DeltaRTC:WORD        ; ms since last RTC tick
        extrn   MS_DeltaKcall:WORD      ; ms since last called kernel

        extrn   szClkName:byte          ; device name for PCLK registration
        extrn   szTmrName:byte          ; device name for PTIMER registration
        extrn   fpfnVTProc:FWORD        ; vtimer entry point
        extrn   fsPTFlags:WORD          ; ptimer flags

        extrn   pqwTmrRollover:DWORD
        extrn   qwTmrRollover:DWORD
        extrn   pqwTmr:DWORD
        extrn   qwTmr:BYTE
        extrn   EOISMPRTC:byte             ; d196223
        extrn   EOISMPTMR:byte             ; d196223

;
;   Data area for calling ABIOS
; BUGBUG -- some of this can go away.  Allocate for RTINIT then destroy.
;

ABIOS_Clock_Parms   ABIOS_Parms <>      ; ABIOS Clock Info

ClockLID        DW      ?               ; ABIOS RTC Logical I.D.
TaskPkt         DW      ?               ; These 4 slots are established to
PeriodPkt       DW      ?               ; hold the actual offsets for each
UpdatePkt       DW      ?               ; request block in the AbiosRB area
ExtraPkt        DW      ?               ;
AbiosRB         DB      MAXBRB*4 dup (0)  ; ABIOS Request Block structure
                                          ; Reserve space for 4 RBs here
                                          ; and resize the segment at INIT time
ClkData ENDS

BREAK <Realtime Clock Write Routine>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  RTWRIT
;*
;* DESCRIPTIVE NAME:  Realtime Clock  Write routine
;*
;* FUNCTION:   Supports the Device Driver functions:
;*             - WRITE (function 8)
;*             - WRITE WITH VERIFY (function 9)
;*      by setting the Realtime Clock device with the date/time
;*      information and updating the Global InfoSeg date/time
;*      variables accordingly.
;*
;*      DevHlp_PhysToVirt converts the real address of the data buffer
;*      in the Device Driver Request Block (PktData) to a virtual
;*      address.  DevHlp_UnPysToVirt later restores the physical
;*      memory address.
;*
;* ENTRY POINT: RTWRIT
;*    LINKAGE:  NEAR from RTENTR
;*
;* USES:  AX, DX.  Preserves others. (RU uses ES, DI)
;*
;* INPUT: (PARAMETERS)
;*      ES:BX = pointer to Request Block  (PktData)
;*      (SP+4):(SP+2) = pointer to Data Buffer
;*
;* OUTPUT: (retURNED)
;*      AX = Status word to be stored in request packet status field.
;*
;* EXIT-NORMAL:
;*    retURN CODE:      AX = BUSY and DONE
;*
;* EXIT-ERROR:
;*    retURN CODE:      AX = ERROR and DONE
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  ABIOS Request Block
;*    ROUTINES:         calc_year_sec
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:         DevHlp_PhysToVirt, DevHlp_ABIOSCall,
;*                      DevHlp_UnPhysToVirt
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; if caller does not request 6 bytes
;   goto write_fail
; call DevHlp_PhysToVirt to convert data buffer's physical addr to
;     virtual address in ES:DI
; if DevHlp_PhysToVirt fail
;   goto write_fail
; get days since 1-1-80 from user buffer
; day_count = no of days not in 4 year groups = days % 1461
; year_count = no of years in 4 year groups = ( days / 1461 ) * 4
; if day_count > 366
;   day_count -= 366
;   year_count++
;   while ( day_count > 365 )
;      day_count -= 365
;      year_count++
;   endwhile
; endif
;
; Normalize year_count ( year_count += 80 )
; if year_count in 20xx ( year_count >= 100 )
;   year_count -= 100
;   Century = 20x
; else Century = 19x
;
; if current date in leap year
;   set Feb = 29
; else
;   set Feb = 28
;
; for month = 1 to 12
;    if day_count >= MonTab[ month ]
;       break
;    else
;       day_count -= MonTab[ month ]
; endfor
;
; day_of_week = ( days + 2 ) % 7 + 1
; convert year, month, day_count, & day_of_week to BCD
;
; get seconds, minutes, & hours from user buffer and convert them to BCD
;    save both binary and BCD values on reserved stack
; get hundredths of seconds from user buffer and save on reserved stack
; invoke ABIOSCALL to write seconds, minutes, hours, day_of_week, day_count,
;   month, & year to chip
; if ABIOSCALL fail
;   set return code = DONE+ERROR+GENERAL FAILURE
;   call DevHlp_UnPhysToVirt to get original address mode
;   enable interrupts
;   if DevHlp_UnPhysToVirt fail
;      goto write_fail
;   return
; endif
; get hundredths of seconds from user buffer and save on reserved stack
;
; call DevHlp_UnPhysToVirt to get the original address mode
; if DevHlp_UnPhysToVirt fail
;   goto write_fail
;
; call calc_year_sec to get seconds, minutes, hours, day_of_week, day_count,
;     month, year, & hundredths of seconds from reserved stack to put in
;     SysInfoSeg
; enable interrupts
; set return code = DONE+BUSY
; return
;
; write_fail:
;   set return code = DONE+ERROR+WRITE FAULT
;   return
;
;*********************** END OF PSEUDO-CODE    **********************

ClkSwap SEGMENT

century_year    equ     [bp+8]
year_bin        equ     [bp+7]
month_bin       equ     [bp+6]
day_month_bin   equ     [bp+5]
day_week_bin    equ     [bp+4]
hour_bin        equ     [bp+3]
minute_bin      equ     [bp+2]
second_bin      equ     [bp+1]
hundredth_bin   equ     [bp]


    ASSUME      CS:ClkSwap,DS:ClkData,ES:NOTHING,SS:NOTHING
RTWRIT  PROC NEAR
        public  RTWRIT

        cmp     ES:[BX].IOcount,RW_BYTES        ; requesting 6 bytes ?
        je      short WRITE                     ; yes, continue....
WT_ERR:
        mov     AX,(STDON+STERR+0ah)            ; Else, quit w/ error.
        ret

WRITE:  push    BP
        sub     sp,10                           ; reserved 10 bytes for clock
        mov     BP,SP

;; The addresses passed in the request packets are 32-bit physical addresses.
        mov     CX,6
        mov     AX,word ptr ES:[BX].IOpData+2   ; get hi word of address
        mov     BX,word ptr ES:[BX].IOpData     ; get lo word of address
        mov     DH,1                            ; result in ES:DI
        mov     DL,DevHlp_PhysToVirt            ; call PhysToVirt
        call    [DevHlp]                        ; ES:DI points to buffer
        jnc     short WRITE_OK

WRITE_FAIL:
        add     sp,10                   ; restore stack pointer
        pop     BP
        jmp     short WT_ERR

;   get days since 1-1-80 from user buffer
WRITE_OK:
        mov     AX,ES:[DI]              ; Get days since 1-1-80 from buffer

;   day_count = no of days not in 4 year groups = days % 1461
;   year_count = no of years in 4 year groups = ( days / 1461 ) * 4
        mov     CX,DAYSIN4YR            ; No of days in four year group
        xor     DX,DX                   ; Clear remainder word
        div     CX                      ; [AX] = No of four year groups
        shl     AX,1
        shl     AX,1                    ; [AX] = No of years in 4 year groups
        push    AX                      ; Save (Note: 120 years max/[AH] = 0)

;   if day_count > 366
;      day_count -= 366
;      year_count++
;      while ( day_count > 365 )
;         day_count -= 365
;         year_count++
;      endwhile
;   endif
        xor     CX,CX                   ; [CL] = 0 = extra year counter
        mov     AX,DAYSINYR+1           ; Year zero is a leap year
YRCMP:  cmp     DX,AX                   ; Is date within year zero?
        jc      short INYEAR            ; Yes, don't subtract
        sub     DX,AX                   ; No, sub days for this year
        inc     CL                      ; Increment counter
        mov     AX,DAYSINYR             ; Days in years one and two
        jmp     short YRCMP             ; Loop until date in current year

;   Normalize year_count ( year_count += 80 )
;   if year_count in 20xx ( year_count >= 100 )
;      year_count -= 100
;      Century = 20x
;   else Century = 19x

INYEAR: pop     AX                      ; Restore partial year count
        push    SI                      ; Save SI
        mov     SI,TaskPkt              ; DS:SI points to ABIOS task-time RB
        mov     [SI].Parms.century,19H  ; Set century = 19x

        add     AL,CL                   ; add in 1 to 3 remainder years
        add     AL,80                   ; Normalize for 19xx
        cmp     AL,100                  ; Year in 19xx?
        JB      short IS19XX            ; Yes, AL = actual year (M003)
        sub     AL,100                  ; No, normalize for 20xx
        mov     [SI].Parms.century,20H  ; Set century = 20x

IS19XX: mov     year_bin,al             ; save year in binary
        BN2BCD                          ; Convert year to BCD
        mov     [SI].Parms.year,al      ; Stuff Year in the ABIOS req blk

;   if current date in leap year
;      set Feb = 29
;   else
;      set Feb = 28
        mov     MonTab+1,28             ; Init Feb to 28 days
        OR      CL,CL                   ; Current date in year zero?
        JNZ     short NOINC             ; No, leave at 28
        inc     MonTab+1                ; Yes, add one day for leap year

;   for month = 1 to 12
;       if day_count >= MonTab[ month ]
;          break
;       else
;          day_count -= MonTab[ month ]
;   endfor
NOINC:  mov     CL,1                    ; At least at first month in year
        mov     SI,OFFSET MonTab        ; Table of days in each month
        xor     AH,AH
        CLD                             ; Insure increment
CHECK1: LODSB                           ; Get count of days this month
        cmp     DX,AX                   ; Date within this month?
        jc      short FOUND             ; Yes, month is found
        sub     DX,AX                   ; No, sub this month's days and...
        inc     CX                      ; ...count one more month/year
        jmp     SHORT CHECK1            ; Loop until month is found

FOUND:  mov     AL,CL                   ; Month number to AL
        mov     month_bin,al            ; save month/binary
        BN2BCD                          ; Convert to BCD
        mov     SI,TaskPkt              ; DS:SI points to ABIOS task-time RB
        mov     [SI].Parms.month,al     ; Stuff month in the ABIOS req blk

        inc     DL                      ; Remainder is day of month(1-31)
        mov     AL,DL
        mov     day_month_bin,al        ; save day of month/binary
        BN2BCD                          ; Convert DOM to BCD
        mov     [SI].Parms.day,al       ; Stuff DOM in the ABIOS req blk

        mov     AL,ES:[DI+5]            ; Get seconds value
        mov     second_bin,al           ; save second in binary
        BN2BCD                          ; Convert it
        mov     [SI].Parms.seconds,AL   ; Stuff it in the ABIOS req blk

        mov     AL,ES:[DI+2]            ; Get minutes value
        mov     minute_bin,al           ; save minute in binary
        BN2BCD                          ; Convert it
        mov     [SI].Parms.minutes,AL   ; Stuff it in the ABIOS req blk

        mov     AL,ES:[DI+3]            ; Get hours value
        mov     hour_bin,al             ; save hour in binary
        BN2BCD                          ; Convert it
        mov     [SI].Parms.hours,AL     ; Stuff it in the ABIOS req blk
        mov     [SI].Function,ABWRITTD  ; Function is Write Time/Date
        mov     AX,ClockLID             ; Get LID in AX
        xor     DH,DH                   ; Calling ABIOS Start Entry point

        push    DI
        LEA     DI, DS:ABIOS_Clock_Parms
        CallABIOS
        pop     DI

;      mov     DL,DevHlp_ABIOSCall
;      call    [DevHlp]                 ; call ABIOS to set clock
        pop     SI                      ; Restore SI
        jc      short ABErr1            ; Jump if carry flag set
                                        ; no interrupts during InfoSeg access
        mov     AL,ES:[DI+4]            ; Get hundredths of seconds
        mov     hundredth_bin,al        ; save on reserved stack

        mov     DL,DevHlp_UnPhysToVirt  ; call UnPhysToVirt
        call    [DevHlp]                ; original addr mode restored
        jnc     short WRITE_INFOSEG
        jmp     WRITE_FAIL

write_infoseg:
        call    far ptr calc_year_sec   ; update sysinfoseg(interrupt disable)
                                        ; es:bx=>SysInfoSeg
        mov     al,hundredth_bin        ; get hundredth
        mov     ES:[BX.SIS_HunTime],AL  ; Stuff it in InfoSeg field
        sti                             ; enable interrupts

        mov     AX,(STDON OR STBUI)     ; No errors

WRITE_DONE:
        add     sp,10                   ; restore stack pointer
        pop     BP
        ret

ABErr1:
        mov     AX,(STDON+STERR+ERRGFAIL)  ; set error code for general failure
        mov     DL,DevHlp_UnPhysToVirt  ; call UnPhysToVirt
        call    [DevHlp]                ; original addr mode restored
        jnc     short WRITE_DONE
        jmp     WRITE_FAIL
RTWRIT  ENDP

ClkSwap ENDS


;***LP  CLKFreq(ulFreq) - Set RTC Periodic Frequency reporting rate
;
;       CLKFreq is called by the kernel to request a new frequency at
;       which real time clock periodic "ticks" are reported to the kernel.
;       The RTC is programmed to interrupt at a constant rate of 128hz.
;       For performance, we may call the kernel (RTCINT calls SchedClock)
;       less often than RTC ticks come in.
;
;       ENTRY
;           (TOS) = ulFreq  : desired frequency in Hz.
;                             Only 32 and 128 supported.
;       EXIT
;           SUCCESS
;               (eax) = 0
;           FAILURE
;               (eax) = ERROR_INVALID_FREQUENCY
;
;       CONTEXT
;           Any (called by TomTick; see task\tom.c)
;
;       PSEUDOCODE
;           if ulFreq is 32 or 128
;              record new reporting frequency desired;
;              return rc = no errors;
;           else
;              return rc = invalid frequency
;

ClkCode SEGMENT

        ASSUME  CS:ClkCode,DS:NOTHING,ES:NOTHING,SS:NOTHING
Procedure CLKFreq,far

        .386p

        ?abase  = 8 + 2
        ArgVar  ulFreq,ULONG

        EnterProc
        SaveReg <ds>

        mov     ax,ClkData
        mov     ds,ax
        ASSUME  DS:ClkData

; check if requested frequency is allowed
        cmp     WORD PTR [ulFreq],128   ; is frequency 128Hz?
        je      short clkfreq_valid     ; .. yes,  its valid
        cmp     WORD PTR [ulFreq],32    ; is frequency 32Hz?
        jne     short clkfreq_invalid   ; ...... no, so fail

clkfreq_valid:
        mov     ax,WORD PTR [ulFreq]
        mov     ReqRTCFreq,ax           ; record requested new frequency
        xor     eax,eax                 ; (eax)=ERROR_NONE

clkfreq_exit:
        RestoreReg <ds>
        ASSUME  DS:NOTHING

        LeaveProc
        retfd   ?aframe

clkfreq_invalid:
        mov     eax,ERROR_INVALID_FREQUENCY  ;  errors
        jmp     short clkfreq_exit

EndProc CLKFreq


BREAK <Realtime Clock Interrupt Service Routine>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  RTCINT
;*
;* DESCRIPTIVE NAME:  Interrupt Service routine for RT/CMOS Clock
;*
;* FUNCTION:
;*      This routine services RT/CMOS Clock interrupts, giving
;*      control to CP/DOS.  The entry point is associated with IRQ 8
;*      using the DevHlp SetIRQ function in RTINIT.  A periodic timer
;*      interrupt causes it to be invoked and the routine uses the
;*      DevHelp_SchedClock function first (before EOI) to inform
;*      CP/DOS of the timer tick and again later (after EOI) to allow
;*      SchedClock to perform any lengthy periodic operations.
;*      For performance, SchedClock may be called at a lower
;*      frequency than the RTC interrupt frequency.
;*
;*      On every update-ended interrupt, the clock device is read
;*      and the Global InfoSeg is updated with interrupts disabled.
;*
;* NOTES:
;*      Following issue of DevHlp_EOI to the Interrupt Manager (before
;*      the second SchedClock call) it is possible for this routine
;*      to be reentered.  Stack usage is kept to a minimum. Re-
;*      entrance should not cause problems, since no real processing
;*      is done by this routine after that point.
;*
;* ENTRY POINT: RTCINT
;*    LINKAGE:  FAR call by Interrupt Manager for IRQ Level = 8
;*
;* USES:  All registers.
;*
;* INPUT:  DS, Cs set up by Intmgr on call to RTCINT
;*
;* EXIT-NORMAL:         RET FAR
;*    RETURN CODE:      CF = 0 if my-interrupt
;*                      CF = 1 if not-my-interrupt
;*
;* EXIT-ERROR:
;*    RETURN CODE:
;*    ERROR MESSAGE:
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  ABIOS Request Block
;*    ROUTINES:         FIXISEG
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:         DevHlp_SchedClock, DevHlp_ABIOSCall,
;*                      DevHlp_EOI
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; enable interrupts
; invoke ABIOSCALL with periodic interrupt request block to
;       obtain Register C value for getting interrupt flags
; if update-ended interrupt flag set
;   update_flag = 1
; if periodic interrupt flag not set
;    goto EOI
; endif
;
; disable interrupts to keep hundredths and milliseconds in sync
; increment hundredths by time (in hundredths) since last tick
; increment millseconds by time (in milliseconds) since last tick
; enable interrupts
;
; update_flag = 0
; if previous update_flag != 0
;    call FIXISEG to read clock to update SysInfoSeg
; endif
;
; if need to call into kernel this tick...
;    call SchedClock to do pre-EOI with AX = interval since last called
;    if we are nested (i.e. Int_In_Progress != 0 )
;       increment nest_count
;       goto EOI_RET
;    endif
;    Int_In_Progress = 1
;    call DevHlp to issue EOI
;
;    nest_count = 0
;    call chkINT15 to process int 15 with previous nest_count value
;
;    call SchedClock to do post-EOI with AX = MS_DeltaPostEOI
;    Int_In_Progress = 0
;
;    MS_DeltaPostEOI = 0
;    call timer ticker routine with previous MS_DeltaPostEOI value
;
; clear carry flag
; return
;
; EOI_RET:
;    call DevHlp to issue EOI
;    clear carry flag
;    return
;
;*********************** END OF PSEUDO-CODE    **********************

    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
RTCINT  PROC    FAR
        public  RTCINT
                                ; The int mgr does a pusha
        STI                     ; Enable interrupts where safe
; SERVICE INTERRUPT
        mov     AX,ClockLID
        mov     SI,PeriodPkt    ; use the Periodic Interrupt Request Block
        mov     [SI].returnCode,retCODE_IN
        mov     DH,1
        LEA     DI, ds:ABIOS_Clock_Parms
        CallABIOS
        cmp     [si].returnCode,1       ; IF ABIOS returns "My Interrupt"
        jne     EOI_RET                 ; EXIT, not a clock device interrupt
        mov     AL,[SI].Parms.C_Reg     ; Set up AL with the C Register Value

; if update-ended interrupt flag set
;   update_flag = 1
; if periodic interrupt flag not set
;   goto EOI_RET & return
; endif

; IS THIS AN UPDATE INTERRUPT ?
        test    AL,UIMASK               ; Update interrupt?
        jz      short GotPeriodic       ; NO, go process the Periodic Int.
        mov     Update_Flag, 1          ; Set the Update flag
        test    AL,PIMASK               ; Also a periodic interrupt?
        jz      EOI_RET                 ; .. No, EOI and return...
                                        ; .. Yes, process it...

public GotPeriodic
GotPeriodic:

; disable interrupts to keep hundredths and milliseconds in sync
; increment hundredths by time (in hundredths) since last tick
; increment millseconds by time (in milliseconds) since last tick
; enable interrupts

        cli     ; disable ints during update; keep everything synchronous

; increment hundredths by time (in hundredths) since last tick
        les     BX,[InfSeg]                     ; ES:BX -> Global InfoSeg
        mov     ah,BYTE PTR ES:[BX.SIS_HunTime] ; Get current hundredths
        mov     al,Hundred_Frac                 ; align fraction part we keep
        add     ax,((100 SHL 8)/128)            ; add in hundredths tick inc.
        mov     Hundred_Frac,al                 ; save new fractional part

;** the following is a hack to account for the case where a user sets the
;  hundredths field ahead, such that ensuing ticks will cause the
;  hundredths to exceed 100.  We prevent this condition.  Note that
;  this causes the 100ths to "freeze" at 99 until the next 1 second update
;  interrupt advances the seconds.  Ref: PTM 6670

        cmp     ah,100                          ; over 100?
        jb      short NoOverflow                ; ..no,
        mov     ah,99                           ; ..yes, DO NOT allow it > 100
public NoOverflow
NoOverflow :
        mov     BYTE PTR ES:[BX.SIS_HunTime],ah ; update Hundredths

; increment millseconds by time (in milliseconds) since last tick
        mov     ax,(((1000 SHL 16)/128) AND 0FFFFh) ; get fract of ms incrmnt
        add     MS_Fraction,ax          ; add to running millisecond fraction
        mov     eax,ES:[BX.SIS_MsCount] ; get system's ms count (whole)
        mov     ecx,eax                 ; save to determine actual ms delta
        adc     eax,(1000/128)          ; add in the whole increment part
        mov     ES:[BX.SIS_MsCount],eax ; update the system's ms count
        sub     eax,ecx                 ; compute actual ms delta
        mov     MS_DeltaRTC,ax          ; save delta in word
        add     MS_DeltaKcall,ax        ; ms since kernel last called
        sti                             ; enable, all timer counts updated

;*************** Do not change FIXISEG and post_time sequence *********
; Determine if there was an Update Int that occured previously
;          and needs handling now.

        xor     al,al
        xchg    al,Update_Flag          ; set update_flag = 0
        or      al,al                   ; Was there an update interrupt?
        je      short Continue          ; No.

        push    cx
        mov     cx,1                    ; only read clock once
        call    FIXISEG                 ; Read clock and update Infoseg
        pop     cx
public Continue
Continue:
        mov     ax,MS_DeltaRTC          ; get number of milliseconds transpired
        add     MS_DeltaPostEOI,ax      ; Post = n * Pre
;*************** end of sequence problem ************************
;
; To provide hi-res (8ms) timers (Feature 66511), calling the kernel at
; 128hz may be needed.  If no hi-res timers are pending, 32hz is OK.
; To prevent timing synchronization problems, the RTC frequency is left
; at a constant 128hz.  (Formerly, it was switched 128hz<->32hz)  For
; performance, the kernel may be called at 32hz or 128hz, as needed.
; ReqRTCFreq determines the frequency at which we should call the kernel.
;
; Only call into kernel at frequency kernel needs to see...
; This math works as 128 >= 32 | 128.  Using Freq, not ms, because
; Freq is exact whole number, ms is fraction.

        mov     ax,AccumRTCFreqs        ; Accumulated Freqs
        add     ax,ReqRTCFreq           ; Accumulate Cycles seen (1/ms)
        mov     AccumRTCFreqs,ax        ; save the accumulation
        cmp     ax,128                  ; More processing on this Tick?
        jl      EOI_RET                 ; .. No, exit quickly
        mov     AccumRTCFreqs,0         ; .. Yes, zero modula counter

; Report to SchedClock the number of ms that occurred since last call to
; SchedClock, NOT necessarily ms since last RTC tick.

        xor     ax,ax
        xchg    ax,MS_DeltaKcall        ; ms since kernel last called
        push    DS
        les     BX,SchedClock
        xor     DH,DH                   ; DH=0 to indicate pre-EOI
        call    DWORD PTR ES:[BX]       ; call kernel timer tick routine(s)
        pop     DS

;here we test for nested int or not.  if we are already nested, we issue
;the EOI disabled and return without doing any Post_EOI processing

        mov     al,1
        xchg    Int_In_Progress,al      ;  set to nested
        or      al,al                   ;  are we nested?
        jz      Not_Nest                ;  if ZR, we are not
        inc     Int_Nested              ;  bump nested counter
public EOI_RET
EOI_RET:
        cli                             ; b720746
;       MOV     AL,RTCIRQ               ;  interrupt level = 8   remove 76711
;       MOV     DL,DevHlp_EOI           ;                        remove 76711
;       CALL    [DevHlp]                ;  call the EOI devhlp   remove 76711
;       DevEOI  <RTCIRQ>,DevHlp         ;                        add    76711 removed d182895
        call    [pfnEOIRTC]             ; call EOI function d196223
        clc                             ;  always clear CY
        ret                             ;  leave

;we are here if we are in a first level Int handler, we have to account
;for any nesting that may have occured after the EOI
public Not_Nest
Not_Nest:                               ;  we are first level
;       MOV     AL,RTCIRQ               ;  interrupt level = 8   remove 76711
;       MOV     DL,DevHlp_EOI           ;                        remove 76711
;       CALL    [DevHlp]                ;  call the EOI devhlp   remove 76711
;       DevEOI  <RTCIRQ>,DevHlp         ;                        add    76711 removed d182895
        call    [pfnEOIRTC]             ; call EOI function d196223

; call SchedClock to do post-EOI with AX = post_time
        MOV     DH,01H                  ; DH=1 to indicate post-EOI

        PUSH    DS                      ;  moved this
        LES     BX,SchedClock
        xor     ax,ax
        xchg    ax,MS_DeltaPostEOI      ; zero out for possible nests

; Input: AX = # of ms to catch up on, same as Pre if no nesting
        CALL    DWORD PTR ES:[BX]       ; call timer tick routine
        POP     DS                      ;  and this

; reset all variables used to check for nesting
        cli                             ; b720746
        mov     Int_In_Progress,0       ;  clear nested flag
        CLC
        RET
RTCINT  ENDP


BREAK <Routine to Update the Global InfoSeg>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  FIXISEG
;*
;* DESCRIPTIVE NAME: Routine to read the RT/CMOS clock and then
;*      update the Global InfoSeg date/time variables.
;*
;* FUNCTION:
;*      Performs a read request to the Realtime Clock device
;*      and then updates the global InfoSeg time/date information.
;*
;* NOTES:
;*      This routine is called by three routines of the clock
;*      device driver.  RTINIT (initialization) and RTCINT (interrupt
;*      handler) use this routine to initialize and update InfoSeg
;*      respectively.
;*
;*      Time is reduced to seconds and preserved for later adding
;*      into secs since 1-1-70.
;*
;* ENTRY POINT: FIXISEG
;*    LINKAGE:  Near (from RTINIT, RTCINT)
;*
;* USES:  Preserves all registers except AX
;*
;* INPUT: CX = # OF retRY COUNT
;*
;* OUTPUT: (retURNED)
;*      No interrupt flag modified
;*      Global InfoSeg time/date data is filled in.
;*      AX = status flag is set, CARRY FLAG indicates success
;*
;* EXIT-NORMAL:
;*      CF is cleared
;*
;* EXIT-ERROR:
;*      AX = STATUS word:  DONE, ERROR + DEVICE NOT READY bits set
;*      CF is set
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:  cmos_popf, calc_year_sec
;*
;* EXTERNAL REFERENCES:
;*    DATA STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:  none
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; for try_count = 1 to MAXTRIES
;    call ABIOS to read the clock
;    if ABIOS error
;       set return code DONE+ERROR+GENERAL FAILURE
;       set carry flag on
;       return
; endif
; endfor
;
; if try_count > MAXTRIES
;   set return code DONE+ERROR+DEVICE NOT READY
;   set carry flag on
;   return
; endif
;
; call ABIOS to read seconds, minutes, hours, years,
;     day of month, & months from chip
; convert seconds, minutes, hours, years, day of month, & months to BCD
;
; call calc_year_sec to update sysinfoseg
; hundredth_count_timer = 0
; HunTime = 0
; restore interrupt flag
; return
;
;*********************** END OF PSEUDO-CODE    **********************

    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
FIXISEG PROC    NEAR
        public  FIXISEG
        SaveReg <AX,BX,SI,ES>
                                        ; (begin)
;*  MAXTRIES is declared as 200, constant for ABIOS driver.  Until 387
;  (12 MHz) is available.
;
        mov     SI,TaskPkt              ; DS:SI --> TaskTime ReqBlk
        mov     [SI].Function,ABREADTD  ; Function is Read Time/Date
        xor     DH,DH                   ; Calling ABIOS Start
;      mov     DL,DevHlp_ABIOSCall     ; Read the clock
        LEA     DI, DS:ABIOS_Clock_Parms
        mov     AX,ClockLID
CHKAGN:                                 ; LOOP while abios not ready
        mov     [SI].returnCode,retCODE_IN

        CallABIOS
;      call    [DevHlp]
        jnc     short ABiosOk2          ; IF ABIOS error
        mov     AX,(STDON+STERR+ERRGFAIL) ; set error code for gen failure
        jmp     goback                  ; carry already set

AbiosOk2:                               ; ELSE
        cmp     [SI].returnCode,0       ; IF ABIOS returns 'done'
        JZ      short FIS2              ;  THEN continue
        LOOP    CHKAGN                  ;  ELSE try again

; if try_count > MAXTRIES
;   set return code DONE+ERROR+DEVICE NOT READY
;   set carry flag on
;   return
; endif
;
        STC                             ; IF no success THEN set carry
        mov     AX,(STDON+STERR+ERRDNR) ; set error code for device not ready
        jmp     goback                  ; abort clock

FIS2:   push    bp
        sub     sp,10                   ; reserved for clock information
        mov     bp,sp

; call ABIOS to read seconds, minutes, hours, years,
;     day of month, & months from chip
; convert seconds, minutes, hours, years, day of month, & months to BCD
;*               +-------------+
;*               |  old  BP    |
;*               +-------------+
;*         BP+8->| ???         |
;*               +-------------+
;*         BP+7->| YEAR        |
;*               +-------------+
;*         BP+6->| MONTH       |
;*               +-------------+
;*         BP+5->| DAY OF MONTH|
;*               +-------------+
;*         BP+4->| DAY OF week |
;*               +-------------+
;*         BP+3->| hour        |
;*               +-------------+
;*         BP+2->| minute      |
;*               +-------------+
;*         BP+1->| second      |
;*               +-------------+
;*         BP  ->| hundredth   |
;*               +-------------+

        mov     AL,[SI].Parms.Seconds   ; get seconds
        BCD2BN                          ; Convert it
        mov     second_bin,al           ; Stuff it in table

        mov     AL,[SI].Parms.Minutes   ; get minutes
        BCD2BN
        mov     minute_bin,al           ; Stuff it in table

        mov     AL,[SI].Parms.Hours     ; get hours
        BCD2BN
        mov     hour_bin,al             ; Stuff it in table

        mov     AL,[SI].Parms.Day       ; get day of month
        BCD2BN
        mov     day_month_bin,al

        mov     AL,[SI].Parms.Month     ; get month
        BCD2BN
        mov     month_bin,al

        mov     AL,[SI].Parms.Year      ; get year
        BCD2BN
        mov     year_bin,al

; 189422 --------  BEGIN -----------------
; if year is over 20 century ( year < 80 )
;    insure century byte is 20
        CMP     AL,80                   ; Is it 20xx?  (year rolled over to 0,1,2...)
        JGE     NoY2KFix                ; No, no need to check century byte

        mov     AL,[SI].Parms.Century   ; get Century byte (BCD)
        CMP     AL,20H                  ; Is century byte is 20?
        JE      NoY2KFix                ; Yes, In-synch, no need to change century byte

; Writing the century byte to the CMOS here should occur once and only once: When the
; year rolls over from 1999 to 2000.  This may occur while the machine is running,
; or on the first boot in the year 20XX.

; write correct century byte to CMOS clock...

        MOV     al,20H                  ; Force century byte to BNC 20H.
        mov     [SI].Parms.Century,al   ; store new Century byte (BCD)

        mov     [SI].Function,ABWRITTD  ; Function is Write Time/Date
        mov     [SI].returnCode,retCODE_IN
        mov     AX,ClockLID             ; Get LID in AX
        xor     DH,DH                   ; Calling ABIOS Start Entry point
        LEA     DI, DS:ABIOS_Clock_Parms
        CallABIOS

; Ignore errors.  This will be retried once per second until it succeeds.

; 189422 --------  END -----------------

NoY2KFix:

        pushf                           ; save interrupt flag

;       call    far ptr calc_year_sec   ; AX:DX=seconds from 1-1-1970
        push    cs                      ; do FARCALLTRANSLATION
        call    near ptr calc_year_sec  ; AX:DX=seconds from 1-1-1970

        mov     ES:[BX.SIS_HunTime],00H
        push    cs
        call    cmos_popf               ; restore interrupt flag
        add     sp,10                   ; restore local stack
        pop     bp
        CLC

GOBACK: RestoreReg <ES,SI,BX,AX>
        ret                             ; return AX
FIXISEG ENDP


;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  calc_year_sec
;*
;* DESCRIPTIVE NAME: Calculate total seconds and current year
;*
;* FUNCTION:
;*      Calculate the total seconds from 1-1-1970, and get the
;*      current year in century year, e.g., 1987.
;*
;* NOTES:
;*      This routine is called by three routines of the clock
;*      device driver.  FIXISEG initializes and updates InfoSeg.
;*      The RTWRIT sub-module uses it to update
;*      InfoSeg following a resetting of the Realtime Clock device.
;*
;*      Time is reduced to seconds and preserved for later adding
;*      into secs since 1-1-70.
;*
;* ENTRY POINT: calc_year_sec
;*    LINKAGE:  Far (from FIXISEG, RTWRIT)
;*
;* USES:  Preserves all registers except AX, ES, BX
;*
;* INPUT:
;*               +-------------+
;*               |  old  BP    |
;*               +-------------+
;*         BP+8->| ???         |
;*               +-------------+
;*         BP+7->| YEAR        |
;*               +-------------+
;*         BP+6->| MONTH       |
;*               +-------------+
;*         BP+5->| DAY OF MONTH|
;*               +-------------+
;*         BP+4->| DAY OF week |
;*               +-------------+
;*         BP+3->| hour        |
;*               +-------------+
;*         BP+2->| minute      |
;*               +-------------+
;*         BP+1->| second      |
;*               +-------------+
;*         BP  ->| hundredth   |
;*               +-------------+
;*
;* OUTPUT: (retURNED)
;*      ES:BX = address of SysInfoSeg
;*      Global InfoSeg time/date data is filled in.
;*      Interrupt Disabled.
;*
;* EXIT-NORMAL:
;*
;* EXIT-ERROR:
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:  FixDayOfWeek, GTDAYS
;*
;* EXTERNAL REFERENCES:
;*    DATA STRUCTURES:
;*    ROUTINES:  none
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; if hours > 12
;   factor = 1
;   hours -= 12
; else
;   factor = 0
; total_seconds = seconds + minutes * 60 + hours * 3600
;
; if year > 99 or month > 12 or month = 0 or Day_of_Month = 0 or
;   Day_of_Month > 31
;   set year = 80
;   set date = 1-1
;   set Day_of_Week = 2
; endif
; if year is over 20 centry ( year < 80 )
;   year += 100
; year += 1900
; save year in century_year stack
; call GTDAYS to get DAYS since 1-1-80
; total_time from 1-1-70 =
;     ( (DAYS+# of days from 1-1-70 to 12-31-79) * # of 12 hours in a day +
;      factor ) * seconds in 12 hours + total_seconds
; disable interrupts
; save all the information in SysInfoSeg
; return
;
;*********************** END OF PSEUDO-CODE    **********************

calc_year_sec PROC    FAR
    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING

        SaveReg <CX,DX,SI,DI>
                                        ; (begin)
; if hours > 12
;   factor = 1
;   hours -= 12
; else
;   factor = 0
; total_seconds = seconds + minutes * 60 + hours * 3600
; read Day_of_Month, month, & year from chip
        mov     al,second_bin           ; get seconds
        xor     AH,AH
        mov     SI,AX                   ; Save sec's

        mov     al,minute_bin           ; get minutes
        mov     CL,60                   ; secs/min
        mul     CL
        add     SI,AX                   ; SI = secs in mins and secs

        xor     AH,AH
        mov     AL,hour_bin             ; get hours
        xor     CX,CX                   ; ch=day of week; cl=factor
        cmp     AL,12                   ; >12 hours?
        jbe     short calc10            ; (M003)
        sub     AL,12
        inc     cl                      ; add factor = 1

calc10: push    CX
        mov     CX,3600
        mul     CX                      ; AX = secs in hrs remainder
        pop     CX
        add     SI,AX                   ; SI = secs in 12 hr time

; Here we do a VERY rough bounds check on the Year, Month, Day and DOW
;
; if year > 99 or month > 12 or month = 0 or Day_of_Month = 0 or
;   Day_of_Month > 31
;   set year = 80
;   set date = 1-1
;   set Day_of_Week = 2
;
        mov     dh,month_bin
        mov     dl,day_month_bin
        mov     AL,year_bin             ; get year

        cmp     AL,99                   ; Year > 99?
        ja      short DATERR
        OR      DH,DH                   ; Month = 0?
        JZ      short DATERR
        cmp     DH,12                   ; Month > 12?
        ja      short DATERR
        OR      DL,DL                   ; Day = 0?
        JZ      short DATERR
        cmp     DL,31                   ; Day > 31?
        JLE     short DATEOK

DATERR: mov     AL,80
        mov     DH,1
        mov     DL,1                    ; Setup for 1-1-80 if bad date in RTC
        mov     CH,2                    ; 1-1-80 was a Tuesday (M003)
        mov     month_bin,dh
        mov     day_month_bin,dl
        mov     year_bin,al

DATEOK:
; if year is over 20 centry ( year < 80 )
;   year += 100
        xor     AH,AH                   ; AX = LOWER PART OF YEAR
        cmp     AL,80                   ; Is it 20xx?
        JGE     short calc20            ; No, add only 1900
        add     AX,100                  ; Yes, add extra 100

; year += 1900
; save on stack
calc20: add     AX,1900                 ; AX = Year and century
        mov     century_year,ax         ;
        sub     AX,1980                 ; Get years since 1980

; call GTDAYS to get DAYS since 1-1-80
;* INPUT:     AX = Years since 1-1-80
;*            DH = Current month
;*            DL = Current day
        call    GTDAYS                  ; AX=Get days since 1-1-80

; if Day_of_Week != 2
;   call FixDayOfWeek to get Day_of_Week
;
        OR      CH,CH                   ; Is day_of_week correct?
        JNZ     short CENT20            ; Yes, jump
        call    FixDayOfWeek
        mov     day_week_bin,ch         ; save on stack

CENT20:
; total_time from 1-1-70 =
;     ( (DAYS+# of days from 1-1-70 to 12-31-79) * # of 12 hours in a day +
;      factor ) * seconds in 12 hours + total_seconds
        xor     BX,BX                   ; Clear it
        add     AX,DAYS1                ; add in days 1-1-70 thru 12-31-79
        shl     AX,1                    ; AX = # of 12 hour segments
        rcl     bl,1                    ; Save overflow (carry) in bl
        xor     ch,ch                   ; CX = add factor (0/1)
        add     ax,cx                   ; AX=Low 16 bits of 12 hour segments
        mov     CX,SECP12               ; CX = Seconds in 12 hours
        mul     CX                      ; [DXAX] = Secs in Low 16 bits of segs
        shr     bl,1                    ; Any beyond Low 16 bits?
        jnc     short calc30            ; No, [DXAX] = Total seconds

        add     dx,cx                   ; Yes, fudge bit 17 multiply
calc30: add     ax,si                   ; add in secs in current time
        adc     dx,0                    ; ...and any carry

        les     BX,[InfSeg]             ; ES:BX -> InfoSeg
        cli                             ; disable interrupts
        mov     WORD PTR ES:[BX.SIS_BigTime],AX
        mov     WORD PTR ES:[BX.SIS_BigTime+2],DX
        mov     al,second_bin           ; get seconds
        mov     ES:[BX.SIS_SecTime],AL  ; Stuff it in table
        mov     al,minute_bin           ; get minutes
        mov     ES:[BX.SIS_MinTime],AL
        mov     al,hour_bin             ; get hour
        mov     ES:[BX.SIS_HrsTime],AL  ; InfoSeg time is complete
        mov     al,day_month_bin        ; get day of month
        mov     ES:[BX.SIS_DayDate],al
        mov     al,month_bin            ; get month
        mov     ES:[BX.SIS_MonDate],al
        mov     al,day_week_bin         ; get day of week
        mov     ES:[BX.SIS_DOWDate],al  ; save the day (DOW)
        mov     ax,century_year
        mov     ES:[BX.SIS_YrsDate],AX  ; InfoSeg date is complete

        RestoreReg <DI,SI,DX,CX>
        ret
calc_year_sec ENDP

; restore interrupt flag
CMOS_POPF       PROC
                Iret                    ; RESTORE FLAGS
CMOS_POPF       ENDP


;***LP  PTInt - Timer 0 Interrupt Handler
;
;      ENTRY
;          ds -> ClkData (interrupt manager guarantee this)
;
;      EXIT
;          carry flag clear
;
;      USES
;          All
;
;      CONTEXT
;          Interrupt
;
;      PSEUDOCODE
;          clear IRQ 0 latch;
;          issue EOI to PIC;

        ASSUME  CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
Procedure PTInt,far

;TMR -- Update Rollover count and Tmr value.
        SaveReg <ds>
        mov     dx,WORD PTR pqwTmr
        lds     bx,pqwTmrRollover
        ASSUME DS:NOTHING
.386p
        mov     ecx,DWORD PTR [bx].qw_ulLo      ;(ECX) = current rollover count.
        mov     eax,DWORD PTR [bx].qw_ulHi      ;Set Current rollover count =
        mov     DWORD PTR [bx].qw_ulLo,eax      ; Next rollover count.

        mov     bx,dx                           ;(DS:BX) -> qwTmr.
                                                ;ASSUMES SAME DS.
        add     DWORD PTR [bx].qw_ulLo,ecx
        adc     DWORD PTR [bx].qw_ulHi,0
        jo      short pti40
CPUMode Reset

pti20:  RestoreReg <ds>
        ASSUME DS:ClkData

        test    fsPTFlags,PTF_OWNT0     ;ptimer owns T0?
        jnz     short pti30             ;YES, skip the rest

        test    fsPTFlags,PTF_TICKENABLE;vtimer enables tick?
        jz      short pti30             ;NO, skip the rest

        push    WORD PTR 0
        push    VTDCMD_TICKEVENT        ;(TOS+8)=VTDCMD_TICKEVENT

        .386p
        push    DWORD PTR 0             ;(TOS+4)=f16p1=0

        push    DWORD PTR 0             ;(TOS)=f16p2=0

        call    FWORD PTR fpfnVTProc
        CPUMode Reset

pti30:  in      al,PORT_SYSB            ;(al)=value of system control port B
        or      al,SYSB_RESETIRQ0       ;set clearIRQ0 bit
        out     PORT_SYSB,al            ;clear IRQ0 latch

 ;      mov     al,TIMERIRQ             ;issue EOI to PIC   Remove 76711
 ;      mov     dl,DevHlp_EOI                               Remove 76711
 ;      call    [DevHlp]                                    Remove 76711
 ;      DevEOI  <TIMERIRQ>,DevHlp       ;                   add    76711 removed d182895
        call    [pfnEOITMR]             ; call EOI function d196223

        clc
        ret

.386p
pti40:  mov     DWORD PTR [bx].qw_ulLo,QW_MAX_LO
        mov     DWORD PTR [bx].qw_ulHi,QW_MAX_HI
        jmp     short pti20
CPUMode Reset

EndProc PTInt

;***LP  RTINIT2 - 2nd stage init
;
;      FUNCTION
;          Because RIPL with ETHERNET/PC-NET uses IRQ0, we must delay
;          hooking IRQ0 until after loadable device drivers/IFS are
;          installed.  The kernel will call with command 0 (loadable
;          device driver init) when it's OK to hook IRQ 0, and this
;          routine will be invoked.
;
;      ENTRY
;          ds -> ClkData
;
;      EXIT
;          carry flag clear
;
;      USES
;          All
;
;      CONTEXT
;          Interrupt
;
;      PSEUDOCODE
;          clear IRQ 0 latch;
;          issue EOI to PIC;
    ASSUME      CS:ClkSwap,DS:ClkData,ES:NOTHING,SS:NOTHING
RTINIT2 PROC FAR
        public  RTINIT2

        SaveReg <bx,cx,dx,di>

;Initialize physical timer 0 to proper mode running at the rate of 18.2Hz

        mov     al,SC_CNT0+RW_LSBMSB+CM_MODE2
        out     PORT_CW,al              ; program count mode of timer 0
        IODelay
        xor     al,al
        out     PORT_CNT0,al            ; program timer 0 count
        IODelay                         ; count == 0h actually means 10000h
        out     PORT_CNT0,al            ; => approx. 18.2Hz
        IODelay

;DevHlp(DevHlp_SetIRQ,TIMERIRQ,pfnPTInt,NO_INTSHARING)

        mov     ax,offset ClkCode:PTInt ; (ax)=interrupt handler offset
        mov     bx,TIMERIRQ             ; (bx)=IRQ number
        mov     dx,DevHlp_SetIRQ        ; (dh)=No interrupt sharing
        call    [DevHlp]                ; hook timer 0 interrupt

        RestoreReg <di,dx,cx,bx>
        mov     AX,STDON                ; Done
        ret

RTINIT2 ENDP

ClkSwap SEGMENT

public  endswapcode
endswapcode     label   byte

ClkSwap ENDS


BREAK <Realtime Clock Initialization Routine>
;********************** START OF SPECIFICATIONS *********************
;*
;* MODULE NAME:  RTINIT
;*
;* DESCRIPTIVE NAME:  RTC Initialization Function 0
;*
;* FUNCTION:    Initializes data structures and the Realtime Clock
;*              device, gets a LID for calling ABIOS.
;*
;* ENTRY POINT: RTINIT
;*    LINKAGE:  FAR from RTENTR
;*
;* NOTES:
;*
;* USES:  AX, DX.  Preserves others. (RU uses CX)
;*
;* INPUT: (PARAMETERS)
;*      ES:BX = pointer to Request Packet
;*
;* OUTPUT: (RETURNED)
;*      No interrupt flag modified
;*      Request Packet filled in.
;*
;* EXIT-NORMAL:
;*      AX = Status flag is "DONE"
;*
;* EXIT-ERROR:
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  ABIOS Request Block
;*    ROUTINES:   FIXISEG
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:  DevHlp_GetDOSVar, DevHlp_SetIRQ, DevHlp_ABIOSCall
;*               DevHlp_GetLIDEntry
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; set the pointer, DevHlp, to the DevHelp Router
; call DevHlp to get the address of SysInfoSeg in ES:BX
; if DevHlp failed
;   goto abort
; save the address of SysInfoSeg in InfSeg
; call DevHlp to get the address of SchedClock in ES:BX
; save the address of SchedClock
;
; call DevHlp to get the ABIOS LID for RTC
; if DevHlp failed
;   goto abort
; save RTC ABIOS LID in ClockLID
;
; call DevHlp to get the ABIOS LID clock parameters
; call DevHlp ABIOSCALL function start to setup task request block
; if DevHlp failed
;   goto abort
; initialize the task request block, periodic request block,
;           update-ended request block, alarm request block,
; call DevHlp ABIOSCALL to disable old periodic interrupt
; if DevHlp failed
;   goto abort
; call DevHlp ABIOSCALL to disable the update-ended interrupt
; if DevHlp failed
;   goto abort
; call DevHlp ABIOSCALL to disable the alarm interrupt
; if DevHlp failed
;   goto abort
; call DevHlp ABIOSCALL to set periodic interrupt to 128hz
; if DevHlp failed
;   goto abort
; call DevHlp ABIOSCALL to enable the update-ended interrupt
; if DevHlp failed
;   goto abort
;
; set up end of code & data segment for SysInit
; call DevHlp to set non-sharing IRQ 8 to routine RTCINT
; if DevHlp failed
;   goto abort
;
; call FIXISEG to initialize SysInfoSeg from the data on the clock
; if carry flag set
;   goto abort
;
; call DevHlp to set int 15 to point to routine int15rtn
; if DevHlp failed
;   goto abort
; save old int 15 vector in Int15Chain
;
; call DevHlp to set int 1A to point to routine int1Artn
; if DevHlp failed
;   goto abort
; save old int 1A vector in Int1AChain
;
; set return code = DONE
; return
;
; abort:
;      set return code = DONE+ERROR+GENERAL FAIL
;      set end of code & data segment to zero
;      return
;
;*********************** END OF PSEUDO-CODE    **********************


    ASSUME      CS:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
RTINIT  PROC FAR
        public  RTINIT

        mov     AX,WORD PTR ES:[BX].InitDevHlp   ; Save away the pointer to
        mov     WORD PTR DevHlp,AX               ; the DevHelp Router.
        mov     AX,WORD PTR ES:[BX].InitDevHlp+2
        mov     WORD PTR DevHlp+2,AX

        push    ES
        push    BX

        mov     AL,1                    ; Variable number of SysInfoSeg
        mov     DL,DevHlp_GetDOSVar
        call    [DevHlp]
        LJC     rtabort2                ; if carry flag set, abort clock
                                        ; No need to check return code here,
                                        ; because ABIOS ALWAYS returns 0 for
                                        ; the return LIDPARMS function.
        mov     ES,AX                            ; ES:BX -> SysInfoSeg
        mov     AX,ES:[BX]                       ; AX = InfoSeg segment selector
        mov     WORD PTR InfSeg+2,AX             ; Stuff for later

        mov     DL,DevHlp_SchedClock             ; ***** FOR DCR 487 *****
        call    [DevHlp]                         ; ES:BX = address of SchedClock pointer
        mov     WORD PTR SchedClock,BX           ; Save away the SchedClock ptr address
        mov     WORD PTR SchedClock+2,ES         ; **

                                        ; DS points to Data Segment...
        mov     AX,8                    ; desired device id is 8
        mov     BX,1                    ; get first LID for the RTC
        xor     DX,DX                   ;
        mov     DL,DevHlp_GetLIDEntry   ; get the ABIOS LID for the RTC
        call    [DevHlp]                ;
        LJC     rtabort2                ; if carry flag set, abort clock
        mov     ClockLID,AX             ; save LID for later use

; GET ABIOS Clock Parameters

        lea     SI, DS:ABIOS_Clock_Parms
        mov     DL, DevHlp_ABIOSGetParms
        call    [DevHlp]

; SET UP TASK REQUEST BLOCK

        mov     SI,Offset DS:AbiosRB    ; SI --> ABios request block area
        mov     [SI].RBLen,MAXBRB
        mov     [SI].LogID,AX
        mov     [SI].Function,ABretLIDP ; function is return Lid Parameters
        mov     [SI].returnCode,retCODE_IN

        xor     DH,DH                   ; calling ABIOS start
        mov     DL,DevHlp_ABIOSCall
        call    [DevHlp]                ; call ABIOS to get LID parameters
        LJC     rtabort2                ; if carry flag set, abort clock
                                        ; No need to check return code here,
                                        ; because ABIOS ALWAYS returns 0 for
                                        ; the return LIDPARMS function.

; length of RTC request block is in RTCRBL.  Set up RB Pointers
; INITIALIZE the Request Blocks and DISABLE their associated Interrupts
; Complete the Initialization of the first Request Block; no interrupts to disable

        mov     BX,[SI].Parms.RTCRBL    ; Set up the true RB size in BX
        mov     [SI].RBLen,BX           ; Set up the true RB size in 1st RB
        mov     CX,SI                   ; CX --> request block area
        mov     TaskPkt,CX              ; Set TaskPkt to point to the 1st RB

        add     CX,BX                   ; CX --> 2nd request block area
        mov     PeriodPkt,CX            ; Set PeriodPkt to point to the 2nd RB

        add     CX,BX                   ; CX --> 3rd request block area
        mov     UpdatePkt,CX            ; Set UpdatePkt to point to the 3rd RB

        add     CX,BX                   ; CX --> 4th request block area
        mov     ExtraPkt,CX             ; Set ExtraPkt to point to the 4th RB


; Initialize PERIODIC Request Block
        mov     SI,PeriodPkt            ; DS:SI --> Periodic request block
        mov     [SI].RBLen,BX
        mov     [SI].LogID,AX
        mov     [SI].ELLOffset,0
        mov     [SI].returnCode,retCODE_IN
; call DevHlp ABIOSCALL to disable old periodic interrupt
        mov     [SI].Function,ABCANPERI ; function is Cancel Periodic Interrupt
        call    [DevHlp]                ; Disable the Periodic Interrupt
        LJC      rtabort2               ; if Carry flag set, abort clock
                                        ; return Code ALWAYS ZERO!

; Initialize UPDATE Request Block
        mov     SI,UpdatePkt            ; DS:SI --> Update request block
        mov     [SI].RBLen,BX
        mov     [SI].LogID,AX
        mov     [SI].ELLOffset,0
        mov     [SI].returnCode,retCODE_IN
; Disable UPDATE Interrupts
        mov     [SI].Function,ABCANUPDI ; function is Cancel Update-Ended Int'pt
        call    [DevHlp]                ; Disable the Update-Ended Interrupt
        LJC     rtabort2                ; if carry flag set, abort clock


; Initialize Extra Request Block for Cancel Alarm Function
        mov     SI,ExtraPkt             ; DS:SI --> Alarm request block
        mov     [SI].RBLen,BX
        mov     [SI].LogID,AX
        mov     [SI].ELLOffset,0
        mov     [SI].returnCode,retCODE_IN
; Disable ALARM Interrupts
        mov     [SI].Function,ABCANALI  ; function is Cancel Alarm Interrupt
        call    [DevHlp]                ; Cancel Alarm Interrupt
        LJC     rtabort2                ; if Carry flag set, abort clock
                                        ; return Code ALWAYS ZERO!

; SET PERIODIC INTERRUPTS

        mov     ReqRTCFreq,32           ; Initally, call kernel at 32hz.
        mov     SI,PeriodPkt            ; DS:SI --> Periodic request block
        mov     [SI].returnCode,retCODE_IN
        mov     [SI].Function,ABSETPERI ; function is Set Periodic Interrupt
        mov     [SI].Parms.RTC_Prat2,IN128    ; rate is 128hz
        call    [DevHlp]                ; Enable the Periodic Interrupt
        jc      short rtabort2          ; if carry flag set, abort clock
        cmp     [si].returnCode,1       ; check ABIOS return status
        ja      short rtabort2          ; if error, then abort

; SET FOR UPDATE INTERRUPTS

        mov     SI,UpdatePkt            ; DS:SI --> UpdatePkt request block
        mov     [SI].returnCode,retCODE_IN
        mov     [si].Function,ABSETUPDI ; function is Set Update-Ended Int'pt
        call    [DevHlp]                ; Enable the Update-Ended Interrupt
        jc      short rtabort2          ; if carry flag set, abort clock
        cmp     [si].returnCode,1       ; check ABIOS return status
        ja      short rtabort2          ; if error, then abort


; Set up Code and Data Segment End Pointers for SysInit

        pop     BX
        pop     ES
        mov     CX,ExtraPkt
        mov     es:[bx].InitEdata,CX    ; InitEdata now points to the end
                                        ; of the third RB.  This is used
                                        ; to set up the END of the DATA SEG.
        mov     AX,offset ClkCode:RTINIT   ; set up END of the CODE SEGMENT
        mov     es:[bx].InitEcode,AX    ; (INIT routine goes away...)


; Finally Set up Interrupt Vector...Enable too
        mov     BX,RTCIRQ
        xor     DH,DH                   ; IRQ NOT SHARED
        mov     AX,offset ClkCode:RTCINT ; AX --> INTERRUPT routine
        mov     DL,DevHlp_SetIRQ
        call    [DevHlp]                ; set the interrupt level
        jc      short rtabort3          ; if carry flag set ,abort clock

        push    cx
        mov     cx,200                  ; read 200 times clock
        call    FIXISEG                 ; Initialize InfoSeg from Clock
        pop     cx
        jnc     short end_rtinit
        jmp     short rtabort3          ; if carry flag set ,abort clock

rtabort2:
        pop     BX
        pop     ES
rtabort3:
        mov     AX,(STDON+STERR+ERRGFAIL) ; set error code for general failure
        mov     es:[bx].InitEcode,0     ; Set up end of Code Segment
        mov     es:[bx].InitEdata,0     ; Set up end of Data Segment
        ret

end_rtinit:

;% Clk/Tmr registration and Initialization

ClkInit label near
        public ClkInit

; start d182895

;DevHlp(DevHlp_GetDOSVar, INDEX 18 decimal (SMP_Active));
        SaveReg <es,bx>
        mov     al, 18                  ; 18 is decimal index for SMP_Active
        mov     dl,DevHlp_GetDOSVar     ; (dl)=DevHlp_GetDOSVar
        call    [DevHlp]                ; (ax)=error code
        jc      short EndSMPTest        ; Assume legacy, no fx18, must be UNI
        mov     es,ax
        cmp     WORD PTR es:[bx],0      ; Is SMP Active?
        jz      short EndSMPTest        ; .. no, leave Use_SMP_PortIO == 0.
        mov     [pfnEOIRTC], offset CLKCODE:EOISMPRTC ; use SMP EOI function  d196223
        mov     [pfnEOITMR], offset CLKCODE:EOISMPTMR ; use SMP EOI function  d196223

EndSMPTest:
        RestoreReg <bx,es>

; end d182895

;DevHlp(DevHlp_RegisterPDD,pszClkName,fpfnClkVDDProc)
        SaveReg <es,bx>
        mov     dl,DevHlp_RegisterPDD   ;(dl)=DevHlp_RegisterPDD
        mov     si,offset ClkData:szClkName;(ds:si)->szClkName
        mov     di,cs
        mov     es,di
        mov     di,offset ClkCode:ClkVDDProc;(es:di)->ClkVDDProc
        call    [DevHlp]                ;(ax)=error code
        RestoreReg <bx,es>
        or      ax,ax                   ;error?
        jnz     short rtabort3          ;YES, goto error exit

;DevHlp(DevHlp_RegisterPDD,pszTmrName,fpfnPTmrProc)
        SaveReg <es,bx>
        mov     dl,DevHlp_RegisterPDD   ;(dl)=DevHlp_RegisterPDD
        mov     si,offset ClkData:szTmrName;(ds:si)->szTmrName
        mov     di,cs
        mov     es,di
        mov     di,offset ClkCode:PTVDMProc;(es:di)->PTVDMProc
        call    [DevHlp]                ;(ax)=error code
        RestoreReg <bx,es>
        or      ax,ax                   ;error?
        jnz     short rtabort3          ;YES, goto error exit

;DevHlp(DevHlp_RegisterBeep,fpfnPTBeep)
        mov     dl,DevHlp_RegisterBeep  ;(dl)=DevHlp_RegisterBeep
        mov     cx,cs
        mov     di,offset ClkCode:PTBeep;(cx:di)->PTBeep
        call    [DevHlp]
        or      ax,ax                   ;error?
        jnz     short rtabort3          ;YES, goto error exit

        call    PTBeepOff               ;disable speaker

;DevHlp(DevHlp_RegisterFreq,fpfnCLKFreq)
        mov     dl,DevHlp_RegisterFreq  ;(dl)=DevHlp_RegisterFreq
        mov     cx,cs
        mov     di,offset ClkCode:CLKFreq;(cx:di)->CLKFreq
        call    [DevHlp]
        or      ax,ax                   ;error?
        jnz     rtabort3                ;YES, goto error exit


;Initialize TMR device (PIT Ctr 0).

        SaveReg <bx,cx,dx,di>

        mov     cx,cs
        mov     di,offset ClkCode:PTTimer0;(cx:di)->PTTimer0
        mov     dl,DevHlp_RegisterTmrDD
        call    [DevHlp]

;       di:bx -> qwTmrRollover (16:16 ptr).
;       di:cx -> qwTmr (16:16 ptr).

        or      di,di                   ;Valid Selector?
        jnz     short cinit2            ; Yes.

cinit1: mov     di,ds                   ;Invalid, so store valid ptr.
        mov     bx,offset qwTmrRollover
        mov     cx,offset qwTmr
cinit2:                                 ;Store (16:16) pointers.
        mov     WORD PTR pqwTmrRollover,bx      ;Store offset.
        mov     WORD PTR pqwTmrRollover+2,di    ;Store segment.
        mov     WORD PTR pqwTmr,cx      ;Store offset.
        mov     WORD PTR pqwTmr+2,di    ;Store segment.

;Initialize current rollover count = next rollover count = 10000h.

        SaveReg <ds>
        mov     ds,di
.386p
        mov     DWORD PTR [bx].qw_ulLo,10000h
        mov     DWORD PTR [bx].qw_ulHi,10000h
CPUMode Reset
        RestoreReg <ds>

        RestoreReg <di,dx,cx,bx>

        mov     AX,STDON                ; Done
        ret

RTINIT  ENDP

ClkCode ENDS

        END
