timer.c 5.53 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
/*
 * 8253/8254 Programmable Interval Timer (PIT) emulation
 *
 * Copyright 2003 Jukka Heinonen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include "dosexe.h"
#include "wine/debug.h"
#include "wingdi.h"
#include "winuser.h"

WINE_DEFAULT_DEBUG_CHANNEL(int);

/*
 * FIXME: Use QueryPerformanceCounter for
 *        more precise GetTimer implementation.
 * FIXME: Use QueryPerformanceCounter (or GetTimer implementation)
 *        in timer tick routine to compensate for lost ticks. 
 *        This should also make it possible to
 *        emulate really fast timers.
 * FIXME: Support special timer modes in addition to periodic mode.
 * FIXME: Use timeSetEvent, NtSetEvent or timer thread for more precise
 *        timing.
 * FIXME: Move Win16 timer emulation code here.
 */

/* The PC clocks ticks at 1193180 Hz. */
#define TIMER_FREQ 1193180

46 47 48
/* How many timer IRQs can be pending at any time. */
#define TIMER_MAX_PENDING 20

49 50 51 52 53 54 55 56 57
/* Unique system timer identifier. */
static UINT_PTR TIMER_id = 0;

/* Time when timer IRQ was last queued. */
static DWORD TIMER_stamp = 0;

/* Timer ticks between timer IRQs. */
static UINT TIMER_ticks = 0;

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
/* Number of pending timer IRQs. */
static LONG TIMER_pending = 0;


/*********************************************************************** 
 *              TIMER_Relay
 *
 * Decrement the number of pending IRQs after IRQ handler has been 
 * called. This function will be called even if application has its 
 * own IRQ handler that does not jump to builtin IRQ handler.
 */
static void TIMER_Relay( CONTEXT86 *context, void *data )
{
    InterlockedDecrement( &TIMER_pending );
}

74 75 76 77 78 79 80 81 82

/***********************************************************************
 *              TIMER_TimerProc
 */
static void CALLBACK TIMER_TimerProc( HWND     hwnd,
                                      UINT     uMsg,
                                      UINT_PTR idEvent,
                                      DWORD    dwTime )
{
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
    LONG pending = InterlockedIncrement( &TIMER_pending );

    if (pending >= TIMER_MAX_PENDING)
    {
        DWORD delta = (dwTime >= TIMER_stamp) ? 
            (dwTime - TIMER_stamp) : (0xffffffff - (TIMER_stamp - dwTime));

        if (delta >= 60000)
        {
            ERR( "DOS timer has been stuck for 60 seconds...\n" );
            TIMER_stamp = dwTime;
        }

        InterlockedDecrement( &TIMER_pending );
    }
    else
    {
        TIMER_stamp = dwTime;
        DOSVM_QueueEvent( 0, DOS_PRIORITY_REALTIME, TIMER_Relay, NULL );
    }
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
}


/***********************************************************************
 *              TIMER_DoSetTimer
 */
static void WINAPI TIMER_DoSetTimer( ULONG_PTR arg )
{
    INT millis = MulDiv( arg, 1000, TIMER_FREQ );

    /* sanity check - too fast timer */
    if (millis < 1)
        millis = 1;

    TRACE_(int)( "setting timer tick delay to %d ms\n", millis );

    if (TIMER_id)
        KillTimer( NULL, TIMER_id );

    TIMER_id = SetTimer( NULL, 0, millis, TIMER_TimerProc );
    TIMER_stamp = GetTickCount();
    TIMER_ticks = arg;
}


/***********************************************************************
129
 *              DOSVM_GetTimer
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
 */
UINT WINAPI DOSVM_GetTimer( void )
{
    if (!DOSVM_IsWin16())
    {
        DWORD millis = GetTickCount() - TIMER_stamp;
        INT   ticks = MulDiv( millis, TIMER_FREQ, 1000 );

        /* sanity check - tick wrap or suspended process or update race */
        if (ticks < 0 || ticks >= TIMER_ticks)
            ticks = 0;

        return ticks;
    }

    return 0;
}


/***********************************************************************
150
 *              DOSVM_SetTimer
151 152 153
 */
void WINAPI DOSVM_SetTimer( UINT ticks )
{
154 155 156 157
    /* PIT interprets zero as a maximum length delay. */
    if (ticks == 0)
        ticks = 0x10000;

158 159 160
    if (!DOSVM_IsWin16())
        MZ_RunInThread( TIMER_DoSetTimer, ticks );
}
161 162 163 164 165 166 167 168 169


/***********************************************************************
 *              DOSVM_Int08Handler
 *
 * DOS interrupt 08h handler (IRQ0 - TIMER).
 */
void WINAPI DOSVM_Int08Handler( CONTEXT86 *context )
{
170
    BIOSDATA *bios_data      = DOSVM_BiosData();
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    CONTEXT86 nested_context = *context;
    FARPROC16 int1c_proc     = DOSVM_GetRMHandler( 0x1c );
    
    nested_context.SegCs = SELECTOROF(int1c_proc);
    nested_context.Eip   = OFFSETOF(int1c_proc);

    /*
     * Update BIOS ticks since midnight.
     *
     * FIXME: What to do when number of ticks exceeds ticks per day?
     */
    bios_data->Ticks++;

    /*
     * If IRQ is called from protected mode, convert
     * context into VM86 context. Stack is invalidated so
     * that DPMI_CallRMProc allocates a new stack.
     */
    if (!ISV86(&nested_context))
    {
        nested_context.EFlags |= V86_FLAG;
        nested_context.SegSs = 0;
    }

    /*
     * Call interrupt 0x1c.
     */
    DPMI_CallRMProc( &nested_context, NULL, 0, TRUE );

    DOSVM_AcknowledgeIRQ( context );
}