;SCCSID = @(#)clock01.asm       6.22 91/10/22
title   clock device driver -- AT  [clock01.asm]

; ****************************************************************************
; *                                                                          *
; *                       IBM/Microsoft Confidential                         *
; *                                                                          *
; *                 Copyright (c) IBM Corporation  1987, 1991, 1997          *
; *                 Copyright (c) Microsoft Corp.  1987, 1991                *
; *                           All Rights Reserved                            *
; *                                                                          *
; ****************************************************************************
;
; Change History
;
; 64012 03/16/93 BJH    Change to retrieve century byte location from the
;                       DevConfigTbl
; 71736 08/11/93 TPL    Don't change the periodic rate (register A) when
;                       updating the date and time
; 122474 10/5/95 ChgTeam Removed STI, Keeps ints disabled while accessing CMOS
; 80100 03/18/94 LRN    Lengthen time that clock init will wait for UIP=0
;                       for IBM 5538 product. Fixed in R207 under 154190 even
;                       though line flags still say 80100.  LRN 05/06/96.
; 138704 05/21/97 PAT   Converge SMP/UNI clock01 driver.  Use runtime check
;                       for SMP rather than ifdef.
; 178847 05/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 information
        INCLUDE mvdm.inc                ; equates and data for MVDM support
        INCLUDE devhlp.inc              ;  the devhlp function eqs
        INCLUDE psd.inc
        INCLUDE infoseg.inc             ; definitions of Global Info Segment
        INCLUDE devsym.inc              ; definition of DD Request Packet
        INCLUDE clkdata.inc
        INCLUDE config.inc              ; device config table
        INCLUDE sas.inc                 ; System Anchor Segment (SAS)
        INCLUDE timer.inc               ; timer definitions
        INCLUDE clkseg.inc              ; segment definitions
        INCLUDE pvwxport.inc
        INCLUDE vtdptd.inc              ; vtimer/ptimer interface
        INCLUDE iodelay.inc             ; DosIODelay macros            80100
        INCLUDE devhlpP.inc             ; 76711

.list
; d182895 start
;
; inc\devsym.inc uses an ifdef SMP around the below def.
; Instead of changing devsym.inc at this time, I have defined
; DevHlp_Port_IO here.  The clock drivers are no longer built with
; the SMP flag
;
DevHlp_Port_IO          EQU     118     ; 76H Port I/O
;
; d182895 end
        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   Use_SMP_PortIO:BYTE
        extrn   pfnEOIRTC:WORD             ; d196223
        extrn   pfnEOITMR:WORD             ; d196223
        extrn   MS_DeltaPostEOI:WORD
        extrn   Int_Nested:BYTE
        extrn   Int_In_Progress:BYTE
        extrn   CMOS_location:BYTE
        extrn   ATDataEnd:Near

        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

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.
;*      Set the stack frame to reserve the clock information
;*      as follows:
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->| CURRENT CENTURY YEAR|
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+14->| YEAR/BCD | YEAR/BIN |
;*                +---------------------+
;*         BP+12->| MONTH/BCD| MON/BIN  |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+8 ->| DAY OF WEEK         |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD | HOUR/BIN |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  | MIN/BIN  |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  | SEC/BIN  |
;*                +---------------------+
;*         BP   ->| CENTURY  |HUNDREDTH |
;*                |  BINARY  |  BINARY  |
;*                +---------------------+
;*
;*      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.
;*
;* 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 and Write Fault
;*
;* INTERNAL REFERENCES:
;*    STRUCTURES:  none
;*    ROUTINES:         cmos_write_clock
;*                      calc_year_sec
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:         DevHlp_PhysToVirt
;*                      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
; reserve 16 bytes for the clock information
; 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
; call DevHlp_UnPhysToVirt to get original address mode
; if DevHlp_UnPhysToVirt successful
;    set return code  "DONE" & "BUSY"
; endif
; else goto write_fail
;
; disable interrupts
; read register B value to get alarm interrupt status
; set register B to abort any update cycle in progress,
;     enable periodic interrupt, previous alarm interrupt status,
;     update-endedinterrupt enable, and 24 hour mode
; read register C & D values to initialize them
;
; 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
; write seconds, minutes, hours, day_of_week, day_count,
;    month, & year to chip
;
; read register B value and reset its update cycle normally
; 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+16]
year_bcd        equ     [bp+14]
year_bin        equ     [bp+15]
month_bcd       equ     [bp+12]
month_bin       equ     [bp+13]
day_month_bcd   equ     [bp+10]
day_month_bin   equ     [bp+11]
day_week_bcd    equ     [bp+8]
day_week_bin    equ     [bp+9]
hour_bcd        equ     [bp+6]
hour_bin        equ     [bp+7]
minute_bcd      equ     [bp+4]
minute_bin      equ     [bp+5]
second_bcd      equ     [bp+2]
second_bin      equ     [bp+3]
century_bcd     equ     [bp]
hundredth_bin   equ     [bp+1]

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

        STI                             ; enable interrupts
        CMP     ES:[BX].IOcount,RW_BYTES        ; requesting 6 bytes ?
        JE      WRITE                           ; yes, continue....
WT_ERR:
        MOV     AX,(STDON+STERR+0ah)            ; Else, quit w/ error.
        RET

WRITE:  PUSH    BP
;       int     3
        sub     sp,18                           ; reserved 16 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     WRITE_OK

WRITE_FAIL:
        add     sp,18                   ; 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      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     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
        MOV     ah,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      IS19XX                  ; Yes, AL = actual year (M003)
        SUB     AL,100                  ; No, normalize for 20xx
        MOV     ah,20H                  ; Set century = 20x

IS19XX: mov     century_bcd,ah          ; save century byte
        mov     year_bin,al             ; save year in binary
        BN2BCD                          ; Convert year to BCD
        MOV     year_bcd,al             ; Year in BCD

;    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     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
        PUSH    SI                      ; Save SI
        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      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:  POP     SI                      ; Restore SI
        MOV     AL,CL                   ; Month number to AL
        mov     month_bin,al            ; save month/binary
        BN2BCD                          ; Convert to BCD
        mov     month_bcd,al            ; save month/BCD

        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     day_month_bcd,al        ; save day of month/BCD

        MOV     AX,ES:[DI]              ; Reget total day count
        XOR     DX,DX                   ; Clear MSW
        MOV     CX,7                    ; Divide by days in week
        INC     AX
        INC     AX                      ; First day was Tuesday
        DIV     CX                      ; Compute day of week
        INC     DL                      ; Normalize
        MOV     AL,DL                   ; Remainder is day of week (1 - 7)
        mov     day_week_bin,al         ; save day of week/binary
        BN2BCD                          ; Convert DOW to BCD
        mov     day_week_bcd,al         ; save day of week/bcd

        MOV     AL,ES:[DI+5]            ; Get seconds value
        mov     second_bin,al           ; save second in binary
        BN2BCD                          ; Convert it
        mov     second_bcd,al           ; save second in bcd

        MOV     AL,ES:[DI+2]            ; Get minutes value
        mov     minute_bin,al           ; save minute in binary
        BN2BCD
        mov     minute_bcd,al           ; save minute in bcd

        MOV     AL,ES:[DI+3]            ; Get hours value
        mov     hour_bin,al             ; save hour in binary
        BN2BCD
        mov     hour_bcd,al             ; save hour in bcd


        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     WRITE_clock
        JMP     WRITE_FAIL

WRITE_clock:
; prepare cmos clock to update clock
        cli
;71736  MOV     AX,(RABYTE SHL 8) OR 8AH        ; Register A
;71736  cmos_write_clock                ; Stuff init value
        MOV     AX,8B8BH                ; Register B (Init both for later set)
        cmos_read_clock                 ; Get current value
        AND     AL,00100000B            ; Preserve user alarm setting only
        OR      AL,RBWRIT               ; Init all others to correct values
        XCHG    AH,AL                   ; AL=register, AH=value
        cmos_write_clock                ; Stop clock updating
        MOV     AL,8CH                  ; Register C
        cmos_read_clock                 ; Read to initialize only
        MOV     AL,8DH                  ; Register D
        cmos_read_clock                 ; Read to initialize only

        cmos_reset                      ; cmos RAM=>read port w/NMI enable
; 122474 sti                            ; enable interrupts // 122474 keep ints off till later

        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

; write cmos clock
        mov     al,CMOS_location        ; AL = 37h -PS/2 OR 32h -AT
        add     al,80h
        MOV     Ah,century_bcd          ; write century
        cmos_write_clock

        MOV     AL,0+80H                ; set seconds
        mov     ah,second_bcd
        cmos_write_clock

        MOV     AL,2+80H                ; set minutes
        mov     ah,minute_bcd
        cmos_write_clock

        MOV     AL,4+80H                ; set hours
        mov     ah,hour_bcd
        cmos_write_clock

        MOV     AL,6+80H                ; set day_of_week
        mov     ah,day_week_bcd
        cmos_write_clock

        MOV     AL,7+80H                ; set day of month
        mov     ah,day_month_bcd
        cmos_write_clock

        MOV     AL,8+80H                ; set month
        mov     ah,month_bcd
        cmos_write_clock

        MOV     AL,9+80H                ; set year
        mov     ah,year_bcd
        cmos_write_clock

; restore clock state
        MOV     AX,8B8BH                ; Register B (Init both for later set)
        cmos_read_clock                 ; Get current value
        AND     AL,7FH                  ; Reset update disable bit
        XCHG    AH,AL                   ; AH=value, AL=register
        cmos_write_clock
        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        sti                             ; enable interrupts

        MOV     AX,(STDON OR STBUI)     ; No errors
        add     sp,18                   ; restore stack pointer
        POP     BP
        RET

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:  none
;*    ROUTINES:         FIXISEG, GTDAYS
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:         DevHlp_SchedClock,
;*                      DevHlp_EOI
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; enable interrupts
; call GetRegC to get interrupt flags
; enable interrupts
; if update-ended interrupt flag set
;    update_flag = 1
; if periodic interrupt flag not set
;    goto EOI_RET
; 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
        STI                             ; Run with interrupts enabled
                                        ; The interrupt managers does a pusha
        CALL    GetRegC                 ; Get register C,(AX) = interrupt flags

; 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 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
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 remove 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 remove 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_read_clock, 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 300
;     read register A
;     if register A shows update is done
;        break
; endfor
;
; if try_count > 300
;    set return code DONE+ERROR+DEVICE NOT READY
;    set carry flag on
;    return
; endif
;
; save interrupt flag
; disable interrupts
; call cmos_read_clockto read seconds, minutes, hours, years,
;      day of month, & months from chip
; restore interrupt flag
; 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,ES>
                                        ; (begin)
; for try_count = 1 to 300
;     read register A
;     if register A shows update is done
;        break
; endfor
;
; if try_count > 300
;    set return code DONE+ERROR+DEVICE NOT READY
;    set carry flag on
;    return
; endif
;
CHKAGN: MOV     AL,0AH                  ; Address of 'A' register

        pushf                           ; save interrupt flag
        cli                             ; disable interrupts
        cmos_read_clock                 ; Read register 'A'
        popf

        TEST    AL,80H                  ; If bit 7 set, cannot read clock
        JZ      FIS2                    ; Okay, go read it
        LOOP    CHKAGN                  ; Try 300 times

        MOV     AX,(STDON+STERR+ERRDNR)
        STC
        JMP     GOBACK                  ; Return Done and Device Not Ready

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

; call cmos_read_clock to read seconds, minutes, hours, years,
;      day of month, & months from chip
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->| ???                 |
;*                +---------------------+
;*         BP+14->| YEAR/BCD |          |
;*                +---------------------+
;*         BP+12->| MONTH/BCD|          |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |          |
;*                +---------------------+
;*         BP+8 ->|                     |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD |          |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  |          |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  |          |
;*                +---------------------+

        pushf                           ; save interrupt flag
        CLI                             ; DISABLE INTERRUPTS
; start 196665  -- Disable clock updates while reading.
        mov     ax,8B8Bh                ;init AL = AH = 8Bh
        cmos_read_clock                 ;read Register B
        or      al,80h                  ;set update disable bit
        xchg    al,ah                   ;now AL = 8B (addr)    AH = reg B data
        cmos_write_clock                ;rewrite Register B
; end 196665
        MOV     AL,0+80H                ; get seconds
        cmos_read_clock
        mov     second_bcd,al

        MOV     AL,2+80H                ; get minutes
        cmos_read_clock
        mov     minute_bcd,al

        MOV     AL,4+80H                ; get hours
        cmos_read_clock
        mov     hour_bcd,al

        MOV     AL,7+80H                ; get day of month
        cmos_read_clock
        mov     day_month_bcd,al

        MOV     AL,8+80H                ; get month
        cmos_read_clock
        mov     month_bcd,al

        MOV     AL,9+80H                ; get year
        cmos_read_clock
        mov     year_bcd,al

; 189422 --------  BEGIN -----------------
; if year is over 20 century ( year < 80 )
;    insure century byte is 20
        BCD2BN                          ; Convert year value to binary
        CMP     AL,80                   ; Is it 20xx?  (year rolled over to 0,1,2...)
        JGE     NoY2KFix                ; No, no need to check century byte
        mov     bl,al                   ; Yes, save bl = year binary

        mov     al,CMOS_location        ; AL = 37h -PS/2 OR 32h -AT
        add     al,80h
        cmos_read_clock                 ; al = century 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...
; prepare clock for writing..
        MOV     AX,8B8BH                ; Register B (Init both for later set)
        cmos_read_clock                 ; Get current value
        AND     AL,00100000B            ; Preserve user alarm setting only
        OR      AL,RBWRIT               ; Init all others to correct values
        XCHG    AH,AL                   ; AL=register, AH=value
        cmos_write_clock                ; Stop clock updating
        MOV     AL,8CH                  ; Register C
        cmos_read_clock                 ; Read to initialize only
        MOV     AL,8DH                  ; Register D
        cmos_read_clock                 ; Read to initialize only

; write century byte..
        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        mov     al,CMOS_location        ; AL = 37h -PS/2 OR 32h -AT
        add     al,80h
        MOV     ah,20H                  ; Force century byte to BNC 20H.
        cmos_write_clock                ; write century

; restore clock state

NoY2KFix:
        MOV     AX,8B8BH                ; Register B (Init both for later set)
        cmos_read_clock                 ; Get current value
        AND     AL,7FH                  ; Reset update disable bit
        XCHG    AH,AL                   ; AH=value, AL=register
        cmos_write_clock
; 189422 --------  END -----------------

        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        popf

; convert seconds, minutes, hours, years, day of month, & months to BCD

        mov     al,second_bcd           ; Get seconds
        BCD2BN                          ; Convert it
        MOV     second_bin,al           ; Stuff it in table

        mov     al,minute_bcd           ; get minutes
        BCD2BN
        MOV     minute_bin,al           ; Stuff it in table

        MOV     AL,hour_bcd             ; Same for hours
        BCD2BN
        MOV     hour_bin,al             ; Stuff it in table

        MOV     AL,day_month_bcd        ; Same for day of month
        BCD2BN
        MOV     day_month_bin,al

        MOV     al,month_bcd            ; get month
        BCD2BN
        MOV     month_bin,al

        MOV     al,year_bcd             ; get month
        BCD2BN
        MOV     year_bin,al

        pushf                           ; save interrupt flag
        call    far ptr calc_year_sec   ; AX:DX=seconds from 1-1-1970
        mov     ES:[BX.SIS_HunTime],00H
        popf
        add     sp,18                   ; restore local stack
        pop     bp
        CLC

GOBACK:
        RestoreReg <ES,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
;*
;* INPUT:
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->|     ???             |
;*                |                     |
;*                +---------------------+
;*         BP+14->| YEAR/BCD | YEAR/BIN |
;*                +---------------------+
;*         BP+12->| MONTH/BCD| MON/BIN  |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+8 ->| DAY OF WEEK         |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD | HOUR/BIN |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  | MIN/BIN  |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  | SEC/BIN  |
;*                +---------------------+
;*         BP   ->| CENTURY  |HUNDREDTH |
;*                |  BINARY  |  BINARY  |
;*                +---------------------+
;*
;* OUTPUT: (RETURNED)
;*      ES:BX = address of SysInfoSeg
;*      Global InfoSeg time/date data is filled in.
;*      Interrupt Disabled.
;*                +---------------------+
;*                |  old  BP            |
;*                +---------------------+
;*         BP+16->| CURRENT CENTURY YEAR|
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+14->| YEAR/BCD | YEAR/BIN |
;*                +---------------------+
;*         BP+12->| MONTH/BCD| MON/BIN  |
;*                +---------------------+
;*         BP+10->| DAY OF MONTH        |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+8 ->| DAY OF WEEK         |
;*                |  BCD     |  BINARY  |
;*                +---------------------+
;*         BP+6 ->| HOUR/BCD | HOUR/BIN |
;*                +---------------------+
;*         BP+4 ->| MIN/BCD  | MIN/BIN  |
;*                +---------------------+
;*         BP+2 ->| SEC/BCD  | SEC/BIN  |
;*                +---------------------+
;*         BP   ->| CENTURY  |HUNDREDTH |
;*                |  BINARY  |  BINARY  |
;*                +---------------------+
;*
;* 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     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      DATERR
        OR      DH,DH                   ; Month = 0?
        JZ      DATERR
        CMP     DH,12                   ; Month > 12?
        JA      DATERR
        OR      DL,DL                   ; Day = 0?
        JZ      DATERR
        CMP     DL,31                   ; Day > 31?
        JLE     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     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     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     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

;********************** START OF SPECIFICATIONS *********************
;**     GetRegC - Get interrupt status from register C
;*
;*      This reads the C register (interrupt ID) TWICE to make
;*      sure that the IRQF will be clear.  The IRQF directly corresponds
;*      to the hardware IRQ line to the 8259.  Another interrpt can become
;*      pending while we are reading the first Interrupt.  If this happens
;*      then the IRQF line will REMAIN set and then it needs to be read
;*      again.  This routine is to assure that the IRQ line will drop.
;*
;*      NOTE: on the XT286 the clock is different than the AT clock.
;*      The XT clock requires that an OUT is done before each IN to a port.
;*      This is because of the Stand-by mode of the XT286.
;*
;*      ENTRY   none
;*
;*      EXIT    [AL] = Byte recieved from register C
;*
;*      USES    Preserves all registers except AX
;*
;*********************** END OF SPECIFICATIONS **********************
page
;********************** START OF PSEUDO-CODE   **********************
;
; save interrupt flag
; disable interrupts
; select register C to get register C 1st value
; select register C to get register C 2nd value
; restore interrupt flag
; combine register C 1st & 2nd values
; if register C combine value = 0
;    set zero flag
; else
;    clear zero flag
; return
;
;*********************** END OF PSEUDO-CODE    **********************

GetRegC PROC    NEAR

        pushf                           ; save interrupt flag
        MOV     ax,0C0ch                ; register C
        cli                             ; disable interrupts
        cmos_read_clock                 ; Get register value
        xchg    AH,AL                   ; Save this value
        cmos_read_clock                 ; Get register c value One more time
        popf

        OR      AL,AH                   ; combine 2 register C values
        RET

GetRegC ENDP


;***LP  PTInt - Timer 0 Interrupt Handler
;
;       ENTRY
;           ds -> ClkData (interrupt manager guarantee this)
;
;       EXIT
;           carry flag clear
;
;       USES
;           All
;
;       CONTEXT
;           Interrupt
;
;       PSEUDOCODE
;           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     eax,DWORD PTR [bx].qw_ulLo
        mov     ecx,eax                         ; (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     pti30                   ; YES, skip the rest

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

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

        push    WORD PTR 0
        push    WORD PTR 0              ; (TOS+4)=f16p1=0

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


        .386p
        call    FWORD PTR fpfnVTProc
        CPUMode Reset

pti30: ; 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:ClkCode,DS:ClkData,ES:NOTHING,SS:NOTHING
RTINIT2 PROC FAR
        public  RTINIT2

        .386p

LocalVar io,<size port_io_s>

        EnterProc
        SaveReg <ebx,ecx,edx,edi,ds,es>

        cmp     Use_SMP_PortIO,0
        jne     RTINIT2OnSMP

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

RTINIT2OnSMP:
        mov     ax,ss
        mov     es,ax
        lea     di,io

        mov     dl,DevHlp_Port_IO

        mov     io.port_io_port,PORT_CW              ; port 43
        mov     io.port_io_flags,IO_WRITE_BYTE       ; write
        mov     al,SC_CNT0+RW_LSBMSB+CM_MODE2
        mov     byte ptr io.port_io_data, al         ; data

        call    [DevHlp]

        mov     io.port_io_port,PORT_CNT0            ; port
        mov     io.port_io_flags,IO_WRITE_BYTE       ; write
        xor     al,al
        mov     byte ptr io.port_io_data, al         ; data
        call    [DevHlp]

        mov     io.port_io_port,PORT_CNT0            ; port
        mov     io.port_io_flags,IO_WRITE_BYTE       ; write
        xor     al,al
        mov     byte ptr io.port_io_data, al         ; data
        call    [DevHlp]

;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

RTINIT2Ret:
        RestoreReg <es,ds,edi,edx,ecx,ebx>
        LeaveProc

        CpuMode reset
        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.
;*
;* ENTRY POINT: RTINIT
;*    LINKAGE:  FAR from RTENTR
;*
;* NOTES:  Interrupt flag is modified
;*
;* USES:  AX, DX.  Preserves others.
;*
;* 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:  none
;*    ROUTINES:   FIXISEG
;*
;* EXTERNAL REFERENCES:
;*    STRUCTURES:  Global Information Segment (SysInfoSeg)
;*    ROUTINES:  DevHlp_GetDOSVar, DevHlp_SetIRQ
;*
;*********************** 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
;
; get pointer to DevConfigTbl from SystemAnchorSegment(SAS)
; get the TYPE byte from DevConfigTbl
; based on TYPE byte, store correct CMOS location in
;
; call DevHlp to get the address of SchedClock in ES:BX
; save the address of SchedClock
; initialize register B to periodic interrupt enable,
;            update-endedinterrupt enable, and 24 hour mode
; initialize register A to 32.768kHz square wave output, and
;            7.8125millisecond periodic interrupt rate (128hz)
; read register C to initialize it
; read register D to initialize it
; call DevHlp to set non-sharing IRQ 8 to routine RTCINT
; if DevHlp failed
;    goto abort
;
; call GetRegC to read register C
; if zero flag not set
;    goto abort
; set up end of code & data segment for SysInit
;
; call FIXISEG to initialize SysInfoSeg from the data on the clock
; if carry flag set
;    goto abort
;
; call DevHlp to set int 1A to point to routine int1Artn
; if DevHlp failed
;    goto abort
; save old int 1A vector in Int1AChain
;
; call DevHlp to set int 15 to point to routine int15rtn
; if DevHlp failed
;    goto abort
; save old int 15 vector in Int15Chain
; 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]
        jnc     rtok
        jmp     rtabort2                ; if carry flag set, abort clock

rtok:   mov     ES,AX                    ; ES:BX -> SysInfoSeg
        mov     AX,ES:[BX]               ; AX = InfoSeg segment selector
        mov     WORD PTR InfSeg+2,AX     ; Stuff for later

        mov     ax,SAS_selector          ; AX = SAS selector = DevConfigTbl Sel
        mov     es,ax                    ; ES = DevConfigTbl selector
        mov     bx,es:[SAS_config_data]  ; BX = @ of DevConfigTbl off.
        mov     bx,es:[bx]               ; BX = DevConfigTbl off.

;;;     Decide wether to use the AT or the PS/2 CMOS address.
;;;     Refer to DCR 1085.
        mov     al,es:[bx].Config_century_location ;AL = century byte location  64012

        mov    CMOS_location,al                  ; store CMOS LOCATION in var.

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

                                        ; (begin)
        pushf                           ; save interrupt flag
        CLI                             ; DISABLE INTERRUPTS FOR CMOS ACCESS

        MOV     AX,(RBINIT SHL 8) OR 0BH        ; Register B
        cmos_write_clock                ; Initialize it
        MOV     AX,((DV32K OR IN128) SHL 8) OR 0AH ; Register A to 128hz
        cmos_write_clock                ; Initialize it
        MOV     AL,0CH                  ; Register C
        cmos_read_clock                 ; Read to initialize
        MOV     AL,0DH                  ; Register D
        cmos_read_clock                 ; Read to initialize
        cmos_reset                      ; cmos RAM=>read port w/NMI enable
        mov     ReqRTCFreq,32           ; Initally, call kernel at 32hz.
        popf

        MOV     BX,RTCIRQ               ; IRQ Number
        XOR     DH,DH                   ; Not Sharing this IRQ !
        MOV     AX,OFFSET ClkCode:RTCINT ; Get the handler address
        MOV     DL,DevHlp_SetIRQ        ; Dev_Hlp Set interrupt function
        CALL    [DevHlp]                ; Install interrupt handler
        JNC     Get_RegC                ; 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

Get_RegC:
;       Clear out any pending interrupts.  This is here for a bug fix.
;       After the clock is initialized and BEFORE the 8259 slave is told
;       about this IRQ line, the clock can interrupt.  If this happens
;       the clock will be waiting for this interrupt to be serviced and
;       no one will know about it.  Thus hanging the system since things
;       like the floppy driver depend on it.
        CALL    GetRegC                 ; Get register C (interrupt flags)

        POP     BX
        POP     ES
        MOV     AX,offset RTINIT        ;  Set up end of Code Segment
        MOV     es:[bx].InitEcode,AX
        MOV     AX,offset ATDataEnd     ;  Set up end of Data Segment
        MOV     es:[bx].InitEdata,AX
                                        ; (end)

; Increase time out period for IBM 5338 update cycle which             80100
; is at most 3 ms. We must wait at least that long for uip=0.          80100
; Note that the delay loop counters were determined to be 3 ms.        80100
; We will also have the additional delay for the time it takes to      80100
; call and loop within FIXISEG.                                        80100
; Note: bx=200 * 15 usec delay = 3000 usec.                            80100
        push    cx
        push    bx                      ; save it                      80100
        mov     bx,200                  ; delay loop count             80100
tryagain:                               ;                              80100
        mov     cx,200                  ; read 200 times clock
        CALL    FIXISEG                 ; Initialize InfoSeg from Clock
;        pop     cx
        jnc     short loopend           ; good. uip went to 0.         80100
        dec     bx                      ; outer loop                   80100
        jz      short isegbad           ; something is wrong           80100
        mov     cx,30                   ; to provide >= 15 usec delay  80100
delayit:                                ;                              80100
        DevIODelay <ax>                 ; each delay is >= 500 ns      80100
        loop    delayit                 ;                              80100
        jmp     short tryagain          ;                              80100
isegbad:                                ;                              80100
        stc                             ;                              80100
loopend:                                ;                              80100
        pop     bx                      ;                              80100
        pop     cx                      ;                              80100
        JC      rtabort3                ; fixiseg could have failed

;% Clk/Tmr registration and Initialization

ClkInit label near
        public ClkInit

;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     Use_SMP_PortIO,1        ; .. yes, flag to use SMP Port IO
        mov     [pfnEOIRTC], offset CLKCODE:EOISMPRTC ; use SMP EOI function  d196223
        mov     [pfnEOITMR], offset CLKCODE:EOISMPTMR ; use SMP EOI function  d196223

EndSMPTest:
        RestoreReg <bx,es>

;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?
        jz      clkok                   ; .. no, continue
        jmp     rtabort3                ; .. YES, goto error exit
clkok:

;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?
        jz      tmrok                   ; .. no, continue
        jmp     rtabort3                ; .. YES, goto error exit
tmrok:

;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?
        jz      beepok                  ; change test for long jump     80100
;        jnz     rtabort3                ;YES, goto error exit
        jmp     rtabort3                ; YES, goto error exit          80100
beepok:                                 ;                               80100

        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?
        jz      freqok                  ; change test for long jump     80100
;        jnz     rtabort3                ;YES, goto error exit
        jmp     rtabort3                ;YES, goto error exit           80100
freqok:                                 ;                               80100


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