winmm: Rearrange device mapping when a new default device is chosen.
[wine] / dlls / krnl386.exe16 / 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 /* Number of milliseconds between IRQs. */
62 static DWORD TIMER_millis = 0;
63
64 /*********************************************************************** 
65  *              TIMER_Relay
66  *
67  * Decrement the number of pending IRQs after IRQ handler has been 
68  * called. This function will be called even if application has its 
69  * own IRQ handler that does not jump to builtin IRQ handler.
70  */
71 static void TIMER_Relay( CONTEXT *context, void *data )
72 {
73     InterlockedDecrement( &TIMER_pending );
74 }
75
76
77 /***********************************************************************
78  *              TIMER_TimerProc
79  */
80 static void CALLBACK TIMER_TimerProc( HWND     hwnd,
81                                       UINT     uMsg,
82                                       UINT_PTR idEvent,
83                                       DWORD    dwTime )
84 {
85     LONG pending = InterlockedIncrement( &TIMER_pending );
86     DWORD delta = (dwTime >= TIMER_stamp) ?
87         (dwTime - TIMER_stamp) : (0xffffffff - (TIMER_stamp - dwTime));
88
89     if (pending >= TIMER_MAX_PENDING)
90     {
91
92         if (delta >= 60000)
93         {
94             ERR( "DOS timer has been stuck for 60 seconds...\n" );
95             TIMER_stamp = dwTime;
96         }
97
98         InterlockedDecrement( &TIMER_pending );
99     }
100     else
101     {
102         DWORD i;
103
104         /* Calculate the number of valid timer interrupts we can generate */
105         DWORD count = delta / TIMER_millis;
106
107         /* Forward the timestamp with the time used */
108         TIMER_stamp += (count * TIMER_millis);
109
110         /* Generate interrupts */
111         for(i=0;i<count;i++)
112         {
113           DOSVM_QueueEvent( 0, DOS_PRIORITY_REALTIME, TIMER_Relay, NULL );
114         }
115     }
116 }
117
118
119 /***********************************************************************
120  *              TIMER_DoSetTimer
121  */
122 static void WINAPI TIMER_DoSetTimer( ULONG_PTR arg )
123 {
124     INT millis = MulDiv( arg, 1000, TIMER_FREQ );
125
126     /* sanity check - too fast timer */
127     if (millis < 1)
128         millis = 1;
129
130     TRACE_(int)( "setting timer tick delay to %d ms\n", millis );
131
132     if (TIMER_id)
133         KillTimer( NULL, TIMER_id );
134
135     TIMER_id = SetTimer( NULL, 0, millis, TIMER_TimerProc );
136     TIMER_stamp = GetTickCount();
137     TIMER_ticks = arg;
138
139     /* Remember number of milliseconds to wait */
140     TIMER_millis = millis;
141 }
142
143
144 /***********************************************************************
145  *              DOSVM_SetTimer
146  */
147 void DOSVM_SetTimer( UINT ticks )
148 {
149     /* PIT interprets zero as a maximum length delay. */
150     if (ticks == 0)
151         ticks = 0x10000;
152
153     if (!DOSVM_IsWin16())
154         MZ_RunInThread( TIMER_DoSetTimer, ticks );
155 }
156
157
158 /***********************************************************************
159  *              DOSVM_Int08Handler
160  *
161  * DOS interrupt 08h handler (IRQ0 - TIMER).
162  */
163 void WINAPI DOSVM_Int08Handler( CONTEXT *context )
164 {
165     BIOSDATA *bios_data      = DOSVM_BiosData();
166     CONTEXT nested_context = *context;
167     FARPROC16 int1c_proc     = DOSVM_GetRMHandler( 0x1c );
168     
169     nested_context.SegCs = SELECTOROF(int1c_proc);
170     nested_context.Eip   = OFFSETOF(int1c_proc);
171
172     /*
173      * Update BIOS ticks since midnight.
174      *
175      * FIXME: What to do when number of ticks exceeds ticks per day?
176      */
177     bios_data->Ticks++;
178
179     /*
180      * If IRQ is called from protected mode, convert
181      * context into VM86 context. Stack is invalidated so
182      * that DPMI_CallRMProc allocates a new stack.
183      */
184     if (!ISV86(&nested_context))
185     {
186         nested_context.EFlags |= V86_FLAG;
187         nested_context.SegSs = 0;
188     }
189
190     /*
191      * Call interrupt 0x1c.
192      */
193     DPMI_CallRMProc( &nested_context, NULL, 0, TRUE );
194
195     DOSVM_AcknowledgeIRQ( context );
196 }