1 | /* $Id: timer.cpp 142 2000-04-23 14:55:46Z ktk $ */
|
---|
2 |
|
---|
3 | /* SCCSID = %W% %E% */
|
---|
4 | /****************************************************************************
|
---|
5 | * *
|
---|
6 | * Copyright (c) IBM Corporation 1994 - 1997. *
|
---|
7 | * *
|
---|
8 | * The following IBM OS/2 source code is provided to you solely for the *
|
---|
9 | * the purpose of assisting you in your development of OS/2 device drivers. *
|
---|
10 | * You may use this code in accordance with the IBM License Agreement *
|
---|
11 | * provided in the IBM Device Driver Source Kit for OS/2. *
|
---|
12 | * *
|
---|
13 | ****************************************************************************/
|
---|
14 | /**@internal %W%
|
---|
15 | * TIMER object implementation.
|
---|
16 | * @version %I%
|
---|
17 | * @context
|
---|
18 | * Unless otherwise noted, all interfaces are Ring-0, 16-bit, kernel stack.
|
---|
19 | * Many of these functions run in interrupt context, this is noted in each
|
---|
20 | * function header.
|
---|
21 | * @notes Suggestions for future work
|
---|
22 | * 1.) Should have a diagnostic in the constructor to ensure the Timer works.
|
---|
23 | * 2.) Should respect the Clock Select on the CS 4232.
|
---|
24 | * @history
|
---|
25 | */
|
---|
26 |
|
---|
27 | #include <devhelp.h>
|
---|
28 | #include <include.h>
|
---|
29 | #include <string.h> // memset()
|
---|
30 | #include "parse.h" // fNoHWTimer
|
---|
31 | #include "irq.hpp" // Object definition.
|
---|
32 | #include "timer.hpp" // Object definition.
|
---|
33 | #include "midistrm.hpp" // Object definition.
|
---|
34 |
|
---|
35 |
|
---|
36 | // Enumerate the technologies from which we can generate a timer tick.
|
---|
37 | enum { TIMER_TechNotIdentified = 0, // Haven't yet identified the technology.
|
---|
38 | TIMER_AdapterTimerInt, // Adapter onboard HW timer interrupt.
|
---|
39 | TIMER_SysTimer }; // OS/2 system timer (31 msec).
|
---|
40 |
|
---|
41 |
|
---|
42 | // Force use of System timer.
|
---|
43 | int fNoHWTimer; // Set in parse.c when "/O:NoHWTimer"
|
---|
44 | // is included on DEVICE= config.sys line.
|
---|
45 |
|
---|
46 | // Provide a global reference so interrupt handler can find all Timers.
|
---|
47 | PQUEUEHEAD pTimerList;
|
---|
48 |
|
---|
49 |
|
---|
50 | // Definitions for static data members of the Timer class. Ref timer.hpp
|
---|
51 | // for comments on each vbl. All these are initialized by TIMER::TIMER.
|
---|
52 | IRQ* TIMER::_pIRQ = 0;
|
---|
53 | int TIMER::_eTechnology = 0;
|
---|
54 | USHORT TIMER::_usTimerCount;
|
---|
55 | USHORT TIMER::_uInterval_mSec;
|
---|
56 | USHORT TIMER::_uIntervalErr_uSec;
|
---|
57 |
|
---|
58 |
|
---|
59 | // Countdown timer resolution of the CS4232, in nano-seconds.
|
---|
60 | //### The countdown timer resolution is 9.969 uSec when C2SL = 0,
|
---|
61 | //### and 9.92 uSec when C2SL = 1. The C2SL can change on the fly,
|
---|
62 | //### depending on what the Wave objects are doing.
|
---|
63 | //### We pick a value that is about 1/2 way between the two possible
|
---|
64 | //### actual values; introduces about a 0.25% error in actual time rates,
|
---|
65 | //### or 1 second off after 400 seconds of play.
|
---|
66 | //### It might be better to periodically read and respect the current
|
---|
67 | //### value of the C2SL select register on the fly (more overhead, but
|
---|
68 | //### more accurate timing)
|
---|
69 | const ULONG ulCS4232_Timer_nSec = 9945;
|
---|
70 |
|
---|
71 |
|
---|
72 | /**@internal TIMER::_vTimerHook
|
---|
73 | * Handle timer tick. Runs in Interrupt context.
|
---|
74 | * @param None.
|
---|
75 | * @return BOOL - TRUE if interrupt handled, FALSE otherwise.
|
---|
76 | * @notes First checks if this interrupt was intended for us. If using
|
---|
77 | * interrupt from adapter, we share this Int with Wave. Then, if our
|
---|
78 | * interrupt, walks the AudioHW list looking for all Timers, and gives
|
---|
79 | * each instantiated timer object an opportunity to perform tasks.
|
---|
80 | */
|
---|
81 | static BOOL __far __loadds __saveregs TIMER::_vTimerHook(void)
|
---|
82 | {
|
---|
83 | TIMER* pTimer; // Used to find Timers in AudioHW list.
|
---|
84 |
|
---|
85 | // Check if this is our interrupt.
|
---|
86 |
|
---|
87 | // Walk through all running timers, perform the per-tick services.
|
---|
88 | pTimer = (TIMER*) pTimerList->Head();
|
---|
89 | while (pTimer != NULL) {
|
---|
90 | if ( pTimer->eState() == TIMER_Running )
|
---|
91 | pTimer->_vPerTickTasks();
|
---|
92 | pTimer = (TIMER*) pTimer->pNext;
|
---|
93 | }
|
---|
94 |
|
---|
95 | return TRUE;
|
---|
96 | }
|
---|
97 |
|
---|
98 |
|
---|
99 | /**@internal TIMER__iCtxHook
|
---|
100 | * Context hook for timer object. Used to call the Process() method
|
---|
101 | * on the currently active MIDI stream.
|
---|
102 | * @param (register EAX) - Data provided by the timer interrupt handler
|
---|
103 | * when the context hook is armed. The interrupt handler should have
|
---|
104 | * supplied a valid stream type (ref. stream.hpp).
|
---|
105 | * @return int 0 - per DevHelp_* definitions on Context Hook operation.
|
---|
106 | * @notes The Context hook function is called by the OS/2 kernel out of a
|
---|
107 | * ring0, task context, _not_ in interrupt context. The function is called
|
---|
108 | * as result of the context hook being armed. The timer interrupt handler
|
---|
109 | * arms the context hook in interrupt context; this routine handles it (in
|
---|
110 | * task context).
|
---|
111 | * @notes We apply the Process() method only to the 1st stream that we find;
|
---|
112 | * that is, we don't search for more than one active MPU or FMSYNTH stream.
|
---|
113 | */
|
---|
114 | int __far __pascal TIMER__iCtxHook( void )
|
---|
115 | {
|
---|
116 | ULONG ulStreamType;
|
---|
117 | MIDISTREAM* pStream;
|
---|
118 |
|
---|
119 | // The OS/2 kernel supplies us with the Ctx Hook data in the EAX
|
---|
120 | // register. We take advantage of this, by stuffing the stream
|
---|
121 | // type into EAX when we arm the contex hook. Here, we make the
|
---|
122 | // dangerous assumption here that the compiler has not yet clobbered
|
---|
123 | // EAX. The _EAX() function is a pragma that we define in include.h.
|
---|
124 |
|
---|
125 | ulStreamType = _EAX();
|
---|
126 | pStream = (MIDISTREAM*) FindActiveStream( ulStreamType );
|
---|
127 |
|
---|
128 | if (pStream) // Should always be a valid fn adr when timer is running.
|
---|
129 | pStream->Process();
|
---|
130 |
|
---|
131 | return 0;
|
---|
132 | }
|
---|
133 |
|
---|
134 | /**@external TIMER::TIMER
|
---|
135 | * Constructor for TIMER object.
|
---|
136 | * @param IRQ* pIRQ - pointer to IRQ object, NULL if none.
|
---|
137 | * @param USHORT uTargetMSec - target resolution in milliseconds.
|
---|
138 | * @param ULONG ulStreamType - type of MIDISTREAM that this Timer is
|
---|
139 | * associated with. This param controls the stream lookup when the
|
---|
140 | * timer goes off.
|
---|
141 | * @notes Constructs timer based on interrupt if possible, otherwise
|
---|
142 | * uses 31.25 mSec kernel timer. The timer interrupt handler is a
|
---|
143 | * static function: no matter how many Timer objects are defined, they
|
---|
144 | * all use the same timer interrupt handler. The interrupt handler
|
---|
145 | * makes a callout to process the unique instance information of all
|
---|
146 | * Timer objects in existance.
|
---|
147 | * @notes New Timer instance is added to the global AUDIOHW object list
|
---|
148 | * as part of its construction.
|
---|
149 | * @notes Timer is left in TIMER_Stopped state on good creation,
|
---|
150 | * TIMER_Disabled state on problem.
|
---|
151 | */
|
---|
152 | TIMER::TIMER( IRQ* pIRQ, USHORT uTargetMSec, ULONG ulStreamType ) :
|
---|
153 | _ulStreamType ( ulStreamType )
|
---|
154 | {
|
---|
155 | USHORT rc;
|
---|
156 | BOOL bGoodReturn;
|
---|
157 | static ULONG hookHandle;
|
---|
158 |
|
---|
159 | // Set initial state - "not functional".
|
---|
160 | _eState = TIMER_Disabled;
|
---|
161 |
|
---|
162 | // Setup the new context hook.
|
---|
163 | rc = DevHelp_AllocateCtxHook( (NPFN) TIMER__iCtxHook, &hookHandle );
|
---|
164 | if (!rc)
|
---|
165 | _ctxHookHandle = hookHandle;
|
---|
166 | else {
|
---|
167 | return;
|
---|
168 | }
|
---|
169 |
|
---|
170 | // If this is the first Timer we've created, do the "first time only"
|
---|
171 | // work, such as selecting the interrupt technology & programming the
|
---|
172 | // timer interrupt.
|
---|
173 |
|
---|
174 | if (TIMER::_eTechnology != 0) // Will be 0 if 1st time through.
|
---|
175 | _eState = TIMER_Stopped; // Not the first Timer.
|
---|
176 | #if 0
|
---|
177 | else { // First time through, set up static vbls.
|
---|
178 | // Try using timer feature on the audio adapter. The fNoHWTimer
|
---|
179 | // flag is set from the DEVICE= config.sys linne, and can be used to
|
---|
180 | // force system timer.
|
---|
181 | if (! fNoHWTimer) {
|
---|
182 |
|
---|
183 | _pIRQ = pIRQ;
|
---|
184 | if ( _pIRQ )
|
---|
185 | bGoodReturn = _pIRQ->bAddHandler( TIMER::_vTimerHook );
|
---|
186 | else
|
---|
187 | bGoodReturn = FALSE;
|
---|
188 |
|
---|
189 | // We got an interrupt slot, now figure out the values for HW timer setup.
|
---|
190 | if ( bGoodReturn )
|
---|
191 | {
|
---|
192 | TIMER::_usTimerCount =
|
---|
193 | (USHORT) (((ULONG) uTargetMSec * 1000000L) / ulCS4232_Timer_nSec);
|
---|
194 |
|
---|
195 | // Figure out what this timer count equates to in mSec and uSec.
|
---|
196 | ULONG ulInterval_nSec = (((ULONG) _usTimerCount) * ulCS4232_Timer_nSec);
|
---|
197 | _uInterval_mSec = ulInterval_nSec / 1000000L;
|
---|
198 | _uIntervalErr_uSec =
|
---|
199 | (ulInterval_nSec - (_uInterval_mSec * 1000000L)) / 1000;
|
---|
200 | // Should always be positive, in range of 0..1000.
|
---|
201 |
|
---|
202 | // All set to enable timer interrupt on the chip. Log status.
|
---|
203 | //### Would be very good to check that it works, so we know to go
|
---|
204 | //### to alternate strategy if necessary.
|
---|
205 | _eTechnology = TIMER_AdapterTimerInt;
|
---|
206 | _eState = TIMER_Stopped;
|
---|
207 | }
|
---|
208 | }
|
---|
209 |
|
---|
210 | // Timer IRQ hook didn't work for some reason, use system timer.
|
---|
211 | if ( _eState == TIMER_Disabled ) {
|
---|
212 | _eTechnology = TIMER_SysTimer;
|
---|
213 | _uInterval_mSec = 31;
|
---|
214 | _uIntervalErr_uSec = 250;
|
---|
215 | _eState = TIMER_Stopped;
|
---|
216 | }
|
---|
217 | } // End of setup for interrupt operation, executed for 1st Timer only.
|
---|
218 | #endif
|
---|
219 |
|
---|
220 | // If good creation, add Timer to global timer list & reset all time vbls.
|
---|
221 | if ( _eState != TIMER_Disabled ) {
|
---|
222 | _ulTime = 0;
|
---|
223 | _ulSchedTime = 0;
|
---|
224 | _uCumulativeError = 0;
|
---|
225 | pTimerList->PushOnTail( this );
|
---|
226 | }
|
---|
227 |
|
---|
228 | // TIMER is in TIMER_Stopped state upon normal return, TIMER_Disabled
|
---|
229 | // state on error.
|
---|
230 | }
|
---|
231 |
|
---|
232 |
|
---|
233 | /**@external TIMER::Start
|
---|
234 | * Start the timer.
|
---|
235 | * @param None
|
---|
236 | * @return int
|
---|
237 | * @notes Maps operation to _iStart (Start and Resume are identical).
|
---|
238 | */
|
---|
239 | int TIMER::Start( void )
|
---|
240 | {
|
---|
241 | return _iStart();
|
---|
242 | }
|
---|
243 |
|
---|
244 |
|
---|
245 | /**@external TIMER::Stop
|
---|
246 | * Stop the operation of the timer.
|
---|
247 | * @param None.
|
---|
248 | * @return int 0.
|
---|
249 | * @notes Maps operation to _iStop (Stop and Pause are identical).
|
---|
250 | */
|
---|
251 | virtual int TIMER::Stop( void )
|
---|
252 | {
|
---|
253 | return _iStop();
|
---|
254 | }
|
---|
255 |
|
---|
256 |
|
---|
257 | /**@external TIMER::Pause
|
---|
258 | * Pause the operation of the timer.
|
---|
259 | * @param None.
|
---|
260 | * @return int 0.
|
---|
261 | * @notes Maps operation to _iStop (Stop and Pause are identical).
|
---|
262 | */
|
---|
263 | virtual int TIMER::Pause( void )
|
---|
264 | {
|
---|
265 | return _iStop();
|
---|
266 | }
|
---|
267 |
|
---|
268 |
|
---|
269 | /**@external TIMER::Resume
|
---|
270 | * Resume the operation of the timer interrupt.
|
---|
271 | * @param None.
|
---|
272 | * @return int 0.
|
---|
273 | * @notes Maps operation to _iStart (Start and Resume are identical).
|
---|
274 | */
|
---|
275 | virtual int TIMER::Resume( void )
|
---|
276 | {
|
---|
277 | return _iStart();
|
---|
278 | }
|
---|
279 |
|
---|
280 |
|
---|
281 | /**@external TIMER::vSchedule
|
---|
282 | * Schedule the next Context hook invocation, 0 for next tick.
|
---|
283 | * @param ULONG ulTime - Absolute time (mSec) at which to schedule next Ctx hook.
|
---|
284 | * @return VOID
|
---|
285 | * @notes
|
---|
286 | */
|
---|
287 | VOID TIMER::vSchedule ( ULONG ulTime )
|
---|
288 | {
|
---|
289 | cli();
|
---|
290 | _ulSchedTime = ulTime;
|
---|
291 | sti();
|
---|
292 | }
|
---|
293 |
|
---|
294 |
|
---|
295 | // Get current stream time (milliseconds). Runs in task context.
|
---|
296 | ULONG TIMER::ulGetTime( void )
|
---|
297 | {
|
---|
298 | ULONG ulResult; // Return value.
|
---|
299 |
|
---|
300 | cli();
|
---|
301 | ulResult = _ulTime;
|
---|
302 | sti();
|
---|
303 | return ulResult;
|
---|
304 | }
|
---|
305 |
|
---|
306 | // Set current stream time (milliseconds). Runs in task context.
|
---|
307 | VOID TIMER::vSetTime( ULONG ulTime )
|
---|
308 | {
|
---|
309 | cli();
|
---|
310 | _ulTime = ulTime;
|
---|
311 | sti();
|
---|
312 | }
|
---|
313 |
|
---|
314 |
|
---|
315 | /**@external TIMER::eState
|
---|
316 | * Query state of timer. TIMER_Disabled inidicates internal failure.
|
---|
317 | * @param None.
|
---|
318 | * @return int Timer state, as enumerated in .hpp file.
|
---|
319 | */
|
---|
320 | const int TIMER::eState ( void ) { return _eState; };
|
---|
321 |
|
---|
322 |
|
---|
323 | /**@internal TIMER::_isAnyRunning()
|
---|
324 | * Predicate that determines whether any Timer is in Running state.
|
---|
325 | * @param None.
|
---|
326 | * @return BOOL TRUE iff at least one Timer is in Running state.
|
---|
327 | * @notes Normally called in Task context by Start and Stop routines.
|
---|
328 | */
|
---|
329 | static BOOL TIMER::_isAnyRunning( void )
|
---|
330 | {
|
---|
331 | TIMER* pTimer; // Used to find Timers in AudioHW list.
|
---|
332 |
|
---|
333 | // Walk through all Timers, seeking one that is running.
|
---|
334 | pTimer = (TIMER*) pTimerList->Head();
|
---|
335 | while (pTimer != NULL) {
|
---|
336 | if ( pTimer->eState() == TIMER_Running )
|
---|
337 | return TRUE;
|
---|
338 | pTimer = (TIMER*) pTimer->pNext;
|
---|
339 | }
|
---|
340 | return FALSE;
|
---|
341 | }
|
---|
342 |
|
---|
343 |
|
---|
344 | /**@internal TIMER::_iStart
|
---|
345 | * Internal worker to start the timer operation. If we have a HW timer,
|
---|
346 | * will start the interrupt generation. If kernel timer, start it.
|
---|
347 | * @param None
|
---|
348 | * @return int 1 (Boolean TRUE) if timer starts properly
|
---|
349 | * @return int 0 (Boolean FALSE) if problem starting timer
|
---|
350 | * @notes Will force Ctx hook to be scheduled on next tick.
|
---|
351 | */
|
---|
352 | int TIMER::_iStart( void )
|
---|
353 | {
|
---|
354 | USHORT rc;
|
---|
355 | BOOL bGoodReturn;
|
---|
356 |
|
---|
357 | // Disable any any pre-existing timer and reset state variables.
|
---|
358 | if ( _eState != TIMER_Stopped )
|
---|
359 | Stop(); // Stop() method on this Timer.
|
---|
360 |
|
---|
361 | // _eState now equals TIMER_Stopped.
|
---|
362 |
|
---|
363 | // Reset Timer vbls & start Timer interrupt if it's not already running.
|
---|
364 | // MMPM/2 will reset the stream time when the user rewinds, etc.
|
---|
365 |
|
---|
366 | _ulSchedTime = 0; // Force arming ctx hook on next tick.
|
---|
367 | _uCumulativeError = 0; // Zero out any fractional time.
|
---|
368 |
|
---|
369 | if ( TIMER::_isAnyRunning() ) // If timer hardware already running
|
---|
370 | _eState = TIMER_Running; // Then just flip our state.
|
---|
371 | else { // Otherwise start interrupts.
|
---|
372 | switch ( _eTechnology ) {
|
---|
373 | #if 0
|
---|
374 | case TIMER_AdapterTimerInt:
|
---|
375 | bGoodReturn = _pIRQ->bEnableHandler( TIMER::_vTimerHook );
|
---|
376 | /*** Our ISR can be called at any point after this, so all our state
|
---|
377 | * variables must be consistent. Even though we haven't enabled
|
---|
378 | * the Timer on the chip, we could get an Int from Wave operations
|
---|
379 | * (which should be ignored).
|
---|
380 | */
|
---|
381 | // Everything is setup for the interrupt, now enable it on the chip.
|
---|
382 | if (bGoodReturn) {
|
---|
383 | _vStartHWTicks();
|
---|
384 | _eState = TIMER_Running;
|
---|
385 | }
|
---|
386 | break;
|
---|
387 | #endif
|
---|
388 | case TIMER_SysTimer:
|
---|
389 | rc = DevHelp_SetTimer( (NPFN) TIMER::_vTimerHook );
|
---|
390 | if (! rc)
|
---|
391 | _eState = TIMER_Running;
|
---|
392 | else
|
---|
393 | break;
|
---|
394 | }
|
---|
395 | }
|
---|
396 |
|
---|
397 | if ( _eState != TIMER_Running ) { // Set error condition & log problem.
|
---|
398 | _eState = TIMER_Disabled;
|
---|
399 | }
|
---|
400 |
|
---|
401 | return ( _eState == TIMER_Running );
|
---|
402 | }
|
---|
403 |
|
---|
404 |
|
---|
405 | /**@internal TIMER::_iStop
|
---|
406 | * Internal worker to shutdown the timer interrupt and the timer clock.
|
---|
407 | * @param None.
|
---|
408 | * @return int 0.
|
---|
409 | * @notes Will not destroy "next scheduled ctx hook" information.
|
---|
410 | * @notes Leaves timer in TIMER_Stopped state. Timer interrupts are stopped
|
---|
411 | * as well if no other Timer is running.
|
---|
412 | */
|
---|
413 | int TIMER::_iStop()
|
---|
414 | {
|
---|
415 | _eState = TIMER_Stopped; // Stop this Timer.
|
---|
416 | if (! TIMER::_isAnyRunning()) { // If no other Timers are running...
|
---|
417 | switch( _eTechnology ) { // Then shutdown the interrupt.
|
---|
418 | case TIMER_AdapterTimerInt:
|
---|
419 | // Disable the interrupt on the chip and in the IRQ object.
|
---|
420 | _vStopHWTicks();
|
---|
421 | break;
|
---|
422 | case TIMER_SysTimer:
|
---|
423 | DevHelp_ResetTimer( (NPFN) TIMER::_vTimerHook );
|
---|
424 | break;
|
---|
425 | }
|
---|
426 | }
|
---|
427 | return 0;
|
---|
428 | }
|
---|
429 |
|
---|
430 |
|
---|
431 | // Perform the per tick, per timer object tasks: maintain time,
|
---|
432 | // arm a context hook if it's time to run the MIDI parser. Runs
|
---|
433 | // in interrupt context.
|
---|
434 | VOID TIMER::_vPerTickTasks( void )
|
---|
435 | {
|
---|
436 | USHORT uMSec;
|
---|
437 |
|
---|
438 | if ( _eState == TIMER_Running ) {
|
---|
439 | // Update our clock.
|
---|
440 | uMSec = _uInterval_mSec;
|
---|
441 | _uCumulativeError += _uIntervalErr_uSec;
|
---|
442 | if (_uCumulativeError >= 1000) {
|
---|
443 | ++uMSec;
|
---|
444 | _uCumulativeError -= 1000;
|
---|
445 | }
|
---|
446 | _ulTime += uMSec;
|
---|
447 |
|
---|
448 | // Set the context hook if it's time.
|
---|
449 | if ((_ulSchedTime == 0) || (_ulTime >= _ulSchedTime))
|
---|
450 | {
|
---|
451 | // Arm ctx hook, pass stream type to the ctx hook handler.
|
---|
452 | DevHelp_ArmCtxHook( _ulStreamType, _ctxHookHandle );
|
---|
453 | _ulSchedTime = 0xFFFFFFFF;
|
---|
454 | }
|
---|
455 | }
|
---|
456 | }
|
---|
457 |
|
---|
458 |
|
---|
459 | // Start the generation of HW timer ticks on the chip.
|
---|
460 | static VOID TIMER::_vStartHWTicks( void )
|
---|
461 | {
|
---|
462 | }
|
---|
463 |
|
---|
464 |
|
---|
465 | // Start the generation of HW timer ticks on the chip.
|
---|
466 | static VOID TIMER::_vStopHWTicks( void )
|
---|
467 | {
|
---|
468 | }
|
---|