winedos: Make UMB executable.
[wine] / dlls / winedos / timer.c
1 /*
2  * 8253/8254 Programmable Interval Timer (PIT) emulation
3  *
4  * Copyright 2003 Jukka Heinonen
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * 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 #include "config.h"
22
23 #include "dosexe.h"
24 #include "wine/debug.h"
25 #include "wingdi.h"
26 #include "winuser.h"
27
28 WINE_DEFAULT_DEBUG_CHANNEL(int);
29
30 /*
31  * FIXME: Use QueryPerformanceCounter for
32  *        more precise GetTimer implementation.
33  * FIXME: Use QueryPerformanceCounter (or GetTimer implementation)
34  *        in timer tick routine to compensate for lost ticks. 
35  *        This should also make it possible to
36  *        emulate really fast timers.
37  * FIXME: Support special timer modes in addition to periodic mode.
38  * FIXME: Use timeSetEvent, NtSetEvent or timer thread for more precise
39  *        timing.
40  * FIXME: Move Win16 timer emulation code here.
41  */
42
43 /* The PC clocks ticks at 1193180 Hz. */
44 #define TIMER_FREQ 1193180
45
46 /* How many timer IRQs can be pending at any time. */
47 #define TIMER_MAX_PENDING 20
48
49 /* Unique system timer identifier. */
50 static UINT_PTR TIMER_id = 0;
51
52 /* Time when timer IRQ was last queued. */
53 static DWORD TIMER_stamp = 0;
54
55 /* Timer ticks between timer IRQs. */
56 static UINT TIMER_ticks = 0;
57
58 /* Number of pending timer IRQs. */
59 static LONG TIMER_pending = 0;
60
61
62 /*********************************************************************** 
63  *              TIMER_Relay
64  *
65  * Decrement the number of pending IRQs after IRQ handler has been 
66  * called. This function will be called even if application has its 
67  * own IRQ handler that does not jump to builtin IRQ handler.
68  */
69 static void TIMER_Relay( CONTEXT86 *context, void *data )
70 {
71     InterlockedDecrement( &TIMER_pending );
72 }
73
74
75 /***********************************************************************
76  *              TIMER_TimerProc
77  */
78 static void CALLBACK TIMER_TimerProc( HWND     hwnd,
79                                       UINT     uMsg,
80                                       UINT_PTR idEvent,
81                                       DWORD    dwTime )
82 {
83     LONG pending = InterlockedIncrement( &TIMER_pending );
84
85     if (pending >= TIMER_MAX_PENDING)
86     {
87         DWORD delta = (dwTime >= TIMER_stamp) ? 
88             (dwTime - TIMER_stamp) : (0xffffffff - (TIMER_stamp - dwTime));
89
90         if (delta >= 60000)
91         {
92             ERR( "DOS timer has been stuck for 60 seconds...\n" );
93             TIMER_stamp = dwTime;
94         }
95
96         InterlockedDecrement( &TIMER_pending );
97     }
98     else
99     {
100         TIMER_stamp = dwTime;
101         DOSVM_QueueEvent( 0, DOS_PRIORITY_REALTIME, TIMER_Relay, NULL );
102     }
103 }
104
105
106 /***********************************************************************
107  *              TIMER_DoSetTimer
108  */
109 static void WINAPI TIMER_DoSetTimer( ULONG_PTR arg )
110 {
111     INT millis = MulDiv( arg, 1000, TIMER_FREQ );
112
113     /* sanity check - too fast timer */
114     if (millis < 1)
115         millis = 1;
116
117     TRACE_(int)( "setting timer tick delay to %d ms\n", millis );
118
119     if (TIMER_id)
120         KillTimer( NULL, TIMER_id );
121
122     TIMER_id = SetTimer( NULL, 0, millis, TIMER_TimerProc );
123     TIMER_stamp = GetTickCount();
124     TIMER_ticks = arg;
125 }
126
127
128 /***********************************************************************
129  *              DOSVM_GetTimer
130  */
131 UINT WINAPI DOSVM_GetTimer( void )
132 {
133     if (!DOSVM_IsWin16())
134     {
135         DWORD millis = GetTickCount() - TIMER_stamp;
136         INT   ticks = MulDiv( millis, TIMER_FREQ, 1000 );
137
138         /* sanity check - tick wrap or suspended process or update race */
139         if (ticks < 0 || ticks >= TIMER_ticks)
140             ticks = 0;
141
142         return ticks;
143     }
144
145     return 0;
146 }
147
148
149 /***********************************************************************
150  *              DOSVM_SetTimer
151  */
152 void WINAPI DOSVM_SetTimer( UINT ticks )
153 {
154     /* PIT interprets zero as a maximum length delay. */
155     if (ticks == 0)
156         ticks = 0x10000;
157
158     if (!DOSVM_IsWin16())
159         MZ_RunInThread( TIMER_DoSetTimer, ticks );
160 }
161
162
163 /***********************************************************************
164  *              DOSVM_Int08Handler
165  *
166  * DOS interrupt 08h handler (IRQ0 - TIMER).
167  */
168 void WINAPI DOSVM_Int08Handler( CONTEXT86 *context )
169 {
170     BIOSDATA *bios_data      = DOSVM_BiosData();
171     CONTEXT86 nested_context = *context;
172     FARPROC16 int1c_proc     = DOSVM_GetRMHandler( 0x1c );
173     
174     nested_context.SegCs = SELECTOROF(int1c_proc);
175     nested_context.Eip   = OFFSETOF(int1c_proc);
176
177     /*
178      * Update BIOS ticks since midnight.
179      *
180      * FIXME: What to do when number of ticks exceeds ticks per day?
181      */
182     bios_data->Ticks++;
183
184     /*
185      * If IRQ is called from protected mode, convert
186      * context into VM86 context. Stack is invalidated so
187      * that DPMI_CallRMProc allocates a new stack.
188      */
189     if (!ISV86(&nested_context))
190     {
191         nested_context.EFlags |= V86_FLAG;
192         nested_context.SegSs = 0;
193     }
194
195     /*
196      * Call interrupt 0x1c.
197      */
198     DPMI_CallRMProc( &nested_context, NULL, 0, TRUE );
199
200     DOSVM_AcknowledgeIRQ( context );
201 }