;       SCCSID = @(#)d_sleep.asm        6.1 91/02/15
;       SCCSID = @(#)d_sleep.asm        6.2 90/01/22
; ****************************************************************************
; *                                                                          *
; *                       IBM/Microsoft Confidential                         *
; *                                                                          *
; *                 Copyright (c) IBM Corporation  1987, 1990                *
; *                 Copyright (c) Microsoft Corp.  1987, 1990                *
; *                           All Rights Reserved                            *
; *                                                                          *
; ****************************************************************************
;***  D_SLEEP.ASM - API stub routine DosSleep
;
;       Member of API library
;       David Silverstein
;       Copyright MicroSoft Corporation, 1986
;       29-Apr-1986
;
;       library routine for API simulator
;
;       Called from dos 2.x,3.x compatible dos 5.x applications

;       Special Information
;
;               The ROM BIOS clock data areas are used to determine the
;               passage of time.
;
;       Modification History
;
;       06-Jan-87 (billme): Replace initial pushes and final pops
;       with calls to __api_begin and __api_end resp.  This will
;       reduce the size of FAPI and allow support for signals.
;
;       15-Jan-87 (billme): Change as many statics as possible to locals
;       to save space (both from not consuming static space and from
;       not have to use segment override - cs - for access)
;
; @F07  27-Jan-93 (franw):  DosSleep can hang the system. (defect # 59357)
;       When the target time is too near "end of day" it can be missed,
;       which wraps us into the next day (and possibly beyond).
;       Need to check for "24-hour wrap" in a "sam-day" sleep.
;

include dos2equ.inc

        EXTRN   __api_begin_lsf:near
        EXTRN   __api_end:near

;***    DOSSLEEP
;
;       simulates DOS 5.0 call from DOS 2.x,3.x environment
;
;       errorcode = unsigned far pascal
;       DosSleep(TimeInterval)
;
;       ENTRY:
;               TimeInterval - Time to wait in milliseconds
;
;       RETURN:
;               AX = 0 if no error, else error code (see DOS 5 spec)
;
;       Pseudo Code:
;               save registers
;               use BP as pointer to stack parameter
;               calculate number of days and clock ticks from TimeInterval
;               loop checking the BIOS clock data waiting for this time
;                 to expire.
;               restore registers
;               return
;
;       WARNING:
;               may return error code from Dos 2.x, 3.x (see dos manuals)
;

        title   DOSSLEEP
;
;       EXTRN   DOSSLEEP:FAR
;
;       PUSH    DWORD TimeInterval
;       CALL    DOSSLEEP
;

; This segment is defined to access memory variables in the bios data area

BIOS    segment byte at 40h
        assume  ds:BIOS

        org     6ch
        TimerLow        dw      ?       ; low word of bios timer data area
        TimerHigh       dw      ?       ; high word of bios timer data area

BIOS    ends


_TEXT   segment byte public 'CODE'

        assume  CS:_TEXT

        public  DOSSLEEP

TimeInterval    equ     [BP + 6]

HRS24_HIGH_IN_TICKS     equ     18h     ; high word of timer after 24 hours
HRS24_LOW_IN_TICKS      equ     0b0h    ; low word of timer after 24 hours
HRS24_HIGH_IN_MILSECS   equ     0526h   ; 24 hours in milliseconds (high word)
HRS24_LOW_IN_MILSECS    equ     05c00h  ; 24 hours in milliseconds (low word)
TICKTIME                equ     55      ; a tick in ms (must be less than ffh)

ExpTimeLow      equ     [bp-2]          ; low word of expiration time (dw)
ExpTimeHigh     equ     [bp-4]          ; high word of expiration time (dw)
LastTimerHigh   equ     [bp-6]          ; high word of timer when last read (dw)


DOSSLEEP        proc    far

        mov     ax,6
        call    __api_begin_lsf ; init bp and save bp,ds,bx,cx,dx,si,di,es
                                ; also create local stack frame (size of ax)
                                ; required to save: bp,bx,cx,dx,ds + si

        mov     cx, SEG BIOS                    ; set up seg addressability for
        mov     ds, cx                          ; bios data area

        ; calc expiration time

        xor     bx, bx
        mov     ax, word ptr [TimeInterval]     ; get length of sleep
        mov     dx, word ptr [TimeInterval + 2]

dossleep1:
        sub     ax, HRS24_LOW_IN_MILSECS        ; how many days?
        sbb     dx, HRS24_HIGH_IN_MILSECS
        jb      dossleep2
        inc     bx                              ; increment day wrap counter
        jmp     short dossleep1

dossleep2:
        add     ax, HRS24_LOW_IN_MILSECS        ; get fraction of a day
        adc     dx, HRS24_HIGH_IN_MILSECS

        ; Because there is no way to divide a 32-bit number and get a 32-bit
        ; quotient, we multiply the divisor by an arbitrary constant and use
        ; the remainer to get more resolution.  This works if the operand is
        ; never larger than  ffffh * (divisor * constant).

        mov     si, TICKTIME    ; a tick in milliseconds
        mov     cl, 4
        shl     si, cl          ; TICKTIME * 2**N
        shl     si, cl
        div     si              ; divide by TICKTIME * 2**N
        mov     si, dx          ; save remainer
        mov     cx, 1 SHL 8
        mul     cx              ; multiply quotient by 2**N

        push    dx              ; save 32-bit partial quotient
        push    ax
        mov     ax, si          ; calc rest of quotient from remainer
        xor     dx, dx
        mov     si, TICKTIME
        div     si
        or      dx, dx          ; is true remainer zero?
        jz      dossleep3       ; yes, rounding unnecessary
        inc     ax              ; round up 1 tick

dossleep3:
        mov     si, ax          ; save rest of quotient
        pop     ax              ; restore 32-bit partial quotient
        pop     dx
        add     ax, si          ; add in the rest for complete 32-bit quotient
        adc     dx, 0

        cli                             ; disable ints during clock var read
        add     ax, BIOS:[TimerLow]             ; add current time
        mov     si, BIOS:[TimerHigh]
        sti                                     ; reenable interrupts
        adc     dx, si
        mov     [LastTimerHigh], si             ; save for later wrap detect

        cmp     dx, HRS24_HIGH_IN_TICKS         ; will the timer wrap?
        jb      dossleep5
        ja      dossleep4
        cmp     ax, HRS24_LOW_IN_TICKS
        jb      dossleep5

dossleep4:
        sub     ax, HRS24_LOW_IN_TICKS          ; yes, subtract timer period
        sbb     dx, HRS24_HIGH_IN_TICKS
        inc     bx                              ; increment day wrap counter

dossleep5:
        mov     [ExpTimeLow], ax                ; save expiration time for later
        mov     [ExpTimeHigh], dx

; loop until expiration time is reached

dossleep6:
        ; Here we call an arbitrary dos call to check ctrlC.  Although this is
        ; slow, the keyboard cannot be checked for ctrlC directly. This is
        ; because DOSHOLDSIGNAL would get broken.

        mov     ah, D2_GetKbdStat
        int     21h                             ; check for CtrlC

        cli                                     ; disable while reading clock
        mov     ax, BIOS:[TimerLow]             ; read bios clock data
        mov     dx, BIOS:[TimerHigh]
        sti                                     ; reenable

;@f07   test    bx, bx                          ; is a timer wrap pending?
;@f07   jz      dossleep7                       ; no
;@f07   cmp     dx, [LastTimerHigh]             ; has the timer wrapped?
;@f07   mov     [LastTimerHigh], dx
;@f07   jae     dossleep6                       ; no, continue
;@f07   dec     bx
;@f07   jnz     dossleep6

        cmp     dx, [LastTimerHigh]             ; has the timer wrapped?   @F07
        jae     dossleep7                         ; no                     @F07
        dec     bx                                ; yes, adj wrap count    @F07

dossleep7:
        mov     [LastTimerHigh], dx             ; update wrap check value  @F07
        test    bx, bx                          ; is a timer wrap pending? @F07
        js      dossleep8                         ; no,  but we wrapped    @F07
        jnz     dossleep6                         ; yes, wait for next one @F07

        sub     ax, [ExpTimeLow]                ; have we reached the exp time?
        sbb     dx, [ExpTimeHigh]
        jb      dossleep6                       ; no, continue

dossleep8:                                      ;@F07
        xor     ax, ax                          ; clear error return

        call    __api_end       ; check sigs & restore es,di,si,dx,cx,bx,ds,bp

        ret     4       ; clear stack of entry parameters (bytes)
DOSSLEEP        endp

_TEXT   ends

        end
