source: cmedia/trunk/Drv16/timer.cpp@ 354

Last change on this file since 354 was 354, checked in by stevenhl, 17 years ago

Import untested baseline cmedia sources, work products and binaries
Binaries and work products should be deleted from repository.
once new builds are verified to work.

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