timer.c 5.48 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * 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
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
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
 */

#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
/* Number of pending timer IRQs. */
static LONG TIMER_pending = 0;

61 62
/* Number of milliseconds between IRQs. */
static DWORD TIMER_millis = 0;
63 64 65 66 67 68 69 70

/*********************************************************************** 
 *              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.
 */
71
static void TIMER_Relay( CONTEXT *context, void *data )
72 73 74 75
{
    InterlockedDecrement( &TIMER_pending );
}

76 77 78 79 80 81 82 83 84

/***********************************************************************
 *              TIMER_TimerProc
 */
static void CALLBACK TIMER_TimerProc( HWND     hwnd,
                                      UINT     uMsg,
                                      UINT_PTR idEvent,
                                      DWORD    dwTime )
{
85
    LONG pending = InterlockedIncrement( &TIMER_pending );
86 87
    DWORD delta = (dwTime >= TIMER_stamp) ?
        (dwTime - TIMER_stamp) : (0xffffffff - (TIMER_stamp - dwTime));
88 89 90 91 92 93 94 95 96 97 98 99 100 101

    if (pending >= TIMER_MAX_PENDING)
    {

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

        InterlockedDecrement( &TIMER_pending );
    }
    else
    {
102
        DWORD i;
103 104 105 106 107 108 109 110 111 112 113 114

        /* Calculate the number of valid timer interrupts we can generate */
        DWORD count = delta / TIMER_millis;

        /* Forward the timestamp with the time used */
        TIMER_stamp += (count * TIMER_millis);

        /* Generate interrupts */
        for(i=0;i<count;i++)
        {
          DOSVM_QueueEvent( 0, DOS_PRIORITY_REALTIME, TIMER_Relay, NULL );
        }
115
    }
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
}


/***********************************************************************
 *              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;
138 139 140

    /* Remember number of milliseconds to wait */
    TIMER_millis = millis;
141 142 143 144
}


/***********************************************************************
145
 *              DOSVM_SetTimer
146
 */
147
void DOSVM_SetTimer( UINT ticks )
148
{
149 150 151 152
    /* PIT interprets zero as a maximum length delay. */
    if (ticks == 0)
        ticks = 0x10000;

153 154 155
    if (!DOSVM_IsWin16())
        MZ_RunInThread( TIMER_DoSetTimer, ticks );
}
156 157 158 159 160 161 162


/***********************************************************************
 *              DOSVM_Int08Handler
 *
 * DOS interrupt 08h handler (IRQ0 - TIMER).
 */
163
void WINAPI DOSVM_Int08Handler( CONTEXT *context )
164
{
165
    BIOSDATA *bios_data      = DOSVM_BiosData();
166
    CONTEXT nested_context = *context;
167 168 169 170 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
    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 );
}