;*DDK*************************************************************************/
;
; COPYRIGHT (C) Microsoft Corporation, 1989
; COPYRIGHT    Copyright (C) 1995 IBM Corporation
;
;    The following IBM OS/2 WARP source code is provided to you solely for
;    the purpose of assisting you in your development of OS/2 WARP device
;    drivers. You may use this code in accordance with the IBM License
;    Agreement provided in the IBM Device Driver Source Kit for OS/2. This
;    Copyright statement may not be removed.;
;*****************************************************************************/
;SCCSID = @(#)clock02.asm	6.13 91/10/22
title   clock device driver -- PS/2  [clock02.asm]

; ****************************************************************************
; *                                                                          *
; *                                                                          *
; *                                                                          *
; ****************************************************************************

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 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 rtc.inc                 ; Real Time Clock info
        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.
        extrn   selClkData:word         ; BUGBUG loader should resolve

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

        extrn   ReqRTCFreq:BYTE
        extrn   CurRTCFreq:BYTE
        extrn   Hundred_Whole:BYTE
        extrn   Hundred_Frac:BYTE
        extrn   Hundred_Inc:WORD
        extrn   MS_Fraction:WORD
        extrn   MS_Inc_Whole:DWORD
        extrn   MS_Inc_Frac:WORD
        extrn   MS_Delta:WORD

        extrn   Hz128To32:BYTE
        extrn   Accum8msTicks:WORD
        extrn   ExpectedMsCount:DWORD

ifdef   MONITOR
        extrn   LagCount:DWORD
        extrn   LagTotal:DWORD

        extrn   Accm:DWORD
        extrn   Accm8:WORD
        extrn   Accm8p:WORD
        extrn   Accm8mod4:WORD
        extrn   Ticks8:DWORD
        extrn   Ticks32:DWORD
endif

        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

;
;   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 service
;
;       CLKFreq is called by the kernel to request a new frequency at
;       which real time clock periodic "ticks" are generated. (See DCR 1345)
;       The actual reprogramming of the device is delayed until such time
;       as it can be done safely: interrupts disabled, pre-EOI, and on the
;       next 32ms tick boundary
;
;       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 frequency desired; the actual change
;                will occur on the next 32ms boundary
;              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     ds,cs:[selClkData]
        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,al           ; 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.
;*
;*      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
; Accum8msTicks++;
; increment hundredths by time (in hundredths) since last tick
; increment millseconds by time (in milliseconds) since last tick
; if Hz128To32 (first 32hz tick since RTC programmed from 128->32hz)
;    set increments to 1/32 sec and clear Hz128To32 flag
; if pending request to reprogram RTC frequency
;    if requested RTC frequency is 128hz
;        change rtc frequency to 128hz
;        change interval to 1/128
;        clear accumulated 8ms tick counter (Accum8msTicks)
;        clear pending request to reprogram RTC frequency
;    else (requested RTC frequency is 32hz)
;        if clock is on a 32ms boundary
;            change rtc frequency to 32hz
;            change interval to zero
;            set Hz128To32 to indicate transition
;            clear pending request to reprogram RTC frequency
; enable interrupts
;
; update_flag = 0
; if previous update_flag != 0
;   call FIXISEG to read clock to update SysInfoSeg
; endif
;
; increment system time by Interval
; subtract off IntervalAdjustment
; if IntervalAdjustment wasn't 0
;   IntervalAdjustment = 0
;
; post_time += interval
;
; call SchedClock to do pre-EOI with AX = interval
; if we are nested (i.e. Int_In_Progress != 0 )
;   increment nest_count
;   goto EOI
; 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 = post_time
; Int_In_Progress = 0
;
; clear carry flag
; return
;
; EOI:
;   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                     ; 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 & return
; endif

        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                     ; No, EOI and return...

; disable interrupts to keep hundredths and milliseconds in sync
; Accum8msTicks++;
; increment hundredths by time (in hundredths) since last tick
; increment millseconds by time (in milliseconds) since last tick
; if Hz128To32 (first 32hz tick since RTC programmed from 128->32hz)
;    set increments to 1/32 sec and clear Hz128To32 flag
; if pending request to reprogram RTC frequency
;    if requested RTC frequency is 128hz
;        change rtc frequency to 128hz
;        change interval to 1/128
;        clear accumulated 8ms tick counter (Accum8msTicks)
;        clear pending request to reprogram RTC frequency
;    else (requested RTC frequency is 32hz)
;        if clock is on a 32ms boundary
;            change rtc frequency to 32hz
;            change interval to zero
;            set Hz128To32 to indicate transition
;            clear pending request to reprogram RTC frequency
; enable interrupts

GotPeriodic:

        cli     ; disable ints during update; keep everything synchronous
        inc     Accum8msTicks;  ; only needed if at 128hz, but do always

; 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,Hundred_Inc                  ; add in hundredths tick inc.
        mov     Hundred_Frac,al                 ; save new fractional part

;** the following is a      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 condidtion.  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
NoOverflow :
        mov     BYTE PTR ES:[BX.SIS_HunTime],ah ; update Hundredths

; increment millseconds by time (in milliseconds) since last tick
        mov     ax,MS_Inc_Frac          ; get fraction of millisecond increment
        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,MS_Inc_Whole        ; 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_Delta,ax             ; save delta in word

        cmp     Hz128To32,0             ; First 32hz tick after reprogramming?
        je      short NoAdjust          ; .. no, increment is ok as is.
        mov     Hundred_Inc,((100 SHL 8) / 32); .. yes, set increments to 1/32
        mov     MS_Inc_Whole,(1000 / 32)
        mov     MS_Inc_Frac,(((1000 SHL 16) / 32) AND 0FFFFh)
        mov     Hz128To32,0             ; reset flag

NoAdjust:
        cmp     ReqRTCFreq,0    ; pending request to change the rtc frequency?
        je      rtcdone         ; .. no, done with the rtc
        mov     al,ReqRTCFreq   ; Get requested rtc frequency
        cmp     al,CurRTCFreq   ; Are we requesting the current frequency?
        je      hzchanged       ; .. yes, don't reprogram, but mark change
        cmp     al,128          ; request to change to 128hz?
        je      short hz128     ; .. yes, safe to do it now
                                ; .. no, change to 32hz only on 32ms border
        mov     ax,Accum8msTicks  ; get #accumulated 8ms ticks
        and     ax,3            ; are we on 1/32nd tick border?
        jnz     rtcdone         ; .. no, not safe to reprogram clock yet

hz32:

; cancel old periodic interrupt
; program new periodic interrupt rate to 32hz
; set frequency to indicate 1/32nd interval
; set hundreths, milliseconds, frequency to zero
;   Increment zero time the next tick, rather than a full 1/32 interval,
;   or the rest of the 1/32 current interval remaining.
;   Experiment has shown that incrementing time
;   in this manner keeps MsCount adding up correctly
;   while changing the RTC's frequency.
;   Time will increment at 1/32 starting the tick after next
; clear frequency change request (ReqRTCFreq)

ifdef   MONITOR
; This shows how 8ms ticks accumulate.
ACCM8SIZE EQU 64
        mov     bx,Accum8msTicks        ; get #accumulated 8ms ticks
        cmp     bx,ACCM8SIZE
        jl      short inceach
        inc     Accm8p
        jmp     short incedone
inceach:
        shl     bx,1
        inc     Accm8[bx]
        shr     bx,1
incedone:
        and     bx,3                    ; ..last 1/32nd tick  (0,1,2 or 3)
        shl     bx,1
        inc     Accm8mod4[bx]
        shr     bx,1
endif

        mov     SI,PeriodPkt            ; DS:SI --> Periodic request block
        mov     [SI].Function,ABCANPERI ; ready to cancel Periodic Interrupt
        mov     AX,ClockLID             ; Get LID in AX
        mov     dl,DevHlp_ABIOSCall
        xor     DH,DH                   ; Calling ABIOS Start Entry point
        call    [DevHlp]                       ; Cancel Periodic Interrupt
ifdef MISCSTRICT
        jnc    short cancelled32
        int 3  ; cancelling the real time clock interrupt should not fail
cancelled32:
endif
        mov     [SI].Function,ABSETPERI        ; Set Periodic Interrupt
        mov     [SI].Parms.RTC_Prat2,IN032     ; rate is 32Hz
        call    [DevHlp]                       ; do it
ifdef MISCSTRICT
        jnc    short hz32ok
        int 3  ; reprogramming the real time clock should not fail
hz32ok:
endif
        mov     CurRTCFreq,32   ; indicate new 32hz frequency
        mov     Hundred_Inc,0   ; set all increments to zero..
        mov     MS_Inc_Whole,0  ; .. they will be zero for the
        mov     MS_Inc_Frac,0   ; .. next tick ONLY.
        mov     Hz128To32,1     ; set flag to indicate 128->32hz transition
        jmp     short hzchanged ; 

hz128:
; cancel old periodic interrupt
; program new periodic interrupt rate to 128hz
; set hundreths, milliseconds, frequency to indicate 1/128nd interval

        mov     SI,PeriodPkt            ; DS:SI --> Periodic request block
        mov     [SI].Function,ABCANPERI ; ready to cancel Periodic Interrupt
        mov     AX,ClockLID             ; Get LID in AX
        mov     dl,DevHlp_ABIOSCall
        xor     DH,DH                   ; Calling ABIOS Start Entry point
        call    [DevHlp]                       ; Cancel Periodic Interrupt
ifdef MISCSTRICT
        jnc     short cancelled128
        int 3   ; cancelling the real time clock interrupt should not fail
cancelled128:
endif
        mov     [SI].Function,ABSETPERI        ; Set Periodic Interrupt
        mov     [SI].Parms.RTC_Prat2,IN128     ; rate is 128Hz
        call    [DevHlp]
ifdef MISCSTRICT
        jnc    short hz128ok
        int 3  ; reprogramming the real time clock should not fail
hz128ok:
endif
        mov     CurRTCFreq,128                 ; indicate new 128hz frequency
        mov     Hundred_Inc,((100 SHL 8) / 128); set increments to 1/128
        mov     MS_Inc_Whole,(1000 / 128)
        mov     MS_Inc_Frac,(((1000 SHL 16) / 128) AND 0FFFFh)
        mov     Accum8msTicks,0                ; start counting 7.8125ms ticks

hzchanged:
        mov     ReqRTCFreq,0    ; clear frequency change request indicator

rtcdone:
        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.

; This driver makes every effort to program the clock to 128hz ONLY within
; the first 7.8125ms after a full 32hz tick.  This is required in order for
; increments of the running millisecond counter to stay accurate.  In
; (hopefully rare) cases, higher priority interrupt activity may prevent
; the clock from being reprogrammed to 128hz within the 7.8125ms window.
;
; When the clock is programmed from 32hz to 128hz, system time will
; increment at 7.8125ms starting the next tick.  In the worst case, up to
; 31.25ms of real time may have passed just prior to the reprogramming.
; This can result in up to 24ms of time being "lost".  We attempt to limit
; the accumulation of this "lost time" within a one second period to no
; more than 32ms.  We predict what the MSCount should be one second from
; now.  If it is more than 32ms less, we make a correction.  This also
; helps keep the MsCount accurate in the event of missed periodic
; interrupts.
;
; It is possible that the independent one-second interrupt arrived just
; before the last 32hz periodic interrupt.  In that case, the time will be
; 32ms short of one second.  That is allowed.  But if the time accumulation
; is more than 32ms short, we assume either periodic interrupt(s) were missed
; or several reprogrammings of the clock frequency resulted in more than
; 32ms of "lost time".  In this case, we adjust the running millisecond
; count to our previously predicted value.  This limits the loss to 32ms.

        mov     eax,ExpectedMsCount     ; recall expected millisecond count
        mov     ecx,ES:[BX.SIS_MsCount] ; get the real MsCount
        sub     eax,ecx                 ; Did the real MsCount fall behind?
        jle     short NotLagging        ; .. No, continue
        add     ecx,eax                 ; .. Yes, MsCount should be this
        mov     ES:[BX.SIS_MsCount],ecx ; bring it up to speed
        add     MS_Delta,ax             ; This contributes to the delta
ifdef   MONITOR
        add     LagTotal,eax            ; it is nice to know these values..
        inc     LagCount                ; .. to observe timing dynamics.
endif

NotLagging:
        add     ecx,968                 ; We expect at least 1000-32ms to pass
        mov     ExpectedMsCount,ecx     ; save for next time

        push    cx
        mov     cx,1                    ; only read clock once
        call    FIXISEG                 ; Read clock and update Infoseg
        pop     cx

Continue:
        mov     ax,MS_Delta             ; get number of milliseconds transpired
        add     Post_Time,ax            ; Post = n * Pre
;*************** end of sequence problem ************************

        push    DS
        les     BX,SchedClock
        xor     DH,DH                   ; DH=0 to indicate pre-EOI
        call    DWORD PTR ES:[BX]       ; call timer tick routine
        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      short Not_Nest          ; if ZR, we are not
        inc     Int_Nested              ; bump nested counter
        jmp     SHORT EOI               ; end of interrupt

;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

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

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

        push    DS
        les     BX,SchedClock
        xor     ax,ax
        xchg    ax,Post_Time            ; 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

; reset all variables used to check for nesting
        cli
        mov     Int_In_Progress,0       ; clear nested flag
        jmp     SHORT LOC_retN

EOI:
        cli
  ;     mov     AL,RTCIRQ               ; do EOI. (Interrupt level = 8)  remove 76711
  ;     mov     DL,DevHlp_EOI                           remove 76711
  ;     call    [DevHlp]                                remove 76711
        DevEOI  <RTCIRQ>,DevHlp         ;               add     76711
LOC_retN:
        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     short 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

        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     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

        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 31.25 ms
; 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

;%    Temporary      to fix loader problem for which the data selector
;%    does not get fixed up.

ES_DI   EQU     1                       ;equate for PhysToVirt DevHlp service


        SaveReg <bx,es>
        SaveReg <ds>
        push    ds
        pop     es
        ASSUME  es:ClkData
        push    cs
        pop     ds
        ASSUME  ds:NOTHING

        mov     si,offset selClkData    ;(ds:si)->selClkData
        mov     dl,DevHlp_VirtToPhys
        call    es:[DevHlp]             ;(ax:bx)=physical address of selClkData

        mov     cx,2                    ;length of word we need to write into
        mov     dx,DevHlp_PhysToVirt + ES_DI*100h
        call    es:[DevHlp]             ;(es:di)->selClkData (writeable!)

        ASSUME  es:NOTHING
        RestoreReg <ds>
        ASSUME  ds:ClkData
        mov     es:[di],ds              ;save ds into code segment

        RestoreReg <es,bx>
        ASSUME  es:NOTHING

        mov     dl,DevHlp_UnPhysToVirt  ;free selector
        call    [DevHlp]
;% END hack

        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 hundredths and milliseconds increments to initial values, based on
; initial periodic interrupt frequency.

        mov     CurRTCFreq,RTCHZINIT
        mov     Hundred_Inc,((100 SHL 8) / RTCHZINIT)
        mov     MS_Inc_Whole,(1000 / RTCHZINIT)
        mov     MS_Inc_Frac,(((1000 SHL 16) / RTCHZINIT) AND 0FFFFh)

; SET PERIODIC INTERRUPTS

        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,IN032    ; rate is 31.25 ms
        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

;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     short 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
