source: sbliveos2/trunk/drv16/timer.cpp

Last change on this file was 561, checked in by rudi, 14 years ago

SBLliveOS2: Adapt to newer OpenWatcom versions

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.4 KB
Line 
1/* $Id: timer.cpp 561 2011-07-28 09:16:56Z rudi $ */
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.
37enum { 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.
43int 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.
47PQUEUEHEAD 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.
52IRQ* TIMER::_pIRQ = 0;
53int TIMER::_eTechnology = 0;
54USHORT TIMER::_usTimerCount;
55USHORT TIMER::_uInterval_mSec;
56USHORT 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)
69const 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 */
81static 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 */
114int __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#pragma off (unreferenced)
153TIMER::TIMER( IRQ* pIRQ, USHORT uTargetMSec, ULONG ulStreamType ) :
154 _ulStreamType ( ulStreamType )
155#pragma on (unreferenced)
156{
157 USHORT rc;
158 static ULONG hookHandle;
159
160 // Set initial state - "not functional".
161 _eState = TIMER_Disabled;
162
163 // Setup the new context hook.
164 rc = DevHelp_AllocateCtxHook( (NPFN) TIMER__iCtxHook, &hookHandle );
165 if (!rc)
166 _ctxHookHandle = hookHandle;
167 else {
168 return;
169 }
170
171 // If this is the first Timer we've created, do the "first time only"
172 // work, such as selecting the interrupt technology & programming the
173 // timer interrupt.
174
175 if (TIMER::_eTechnology != 0) // Will be 0 if 1st time through.
176 _eState = TIMER_Stopped; // Not the first Timer.
177#if 0
178 else { // First time through, set up static vbls.
179 // Try using timer feature on the audio adapter. The fNoHWTimer
180 // flag is set from the DEVICE= config.sys linne, and can be used to
181 // force system timer.
182 if (! fNoHWTimer) {
183
184 _pIRQ = pIRQ;
185 if ( _pIRQ )
186 bGoodReturn = _pIRQ->bAddHandler( TIMER::_vTimerHook );
187 else
188 bGoodReturn = FALSE;
189
190 // We got an interrupt slot, now figure out the values for HW timer setup.
191 if ( bGoodReturn )
192 {
193 TIMER::_usTimerCount =
194 (USHORT) (((ULONG) uTargetMSec * 1000000L) / ulCS4232_Timer_nSec);
195
196 // Figure out what this timer count equates to in mSec and uSec.
197 ULONG ulInterval_nSec = (((ULONG) _usTimerCount) * ulCS4232_Timer_nSec);
198 _uInterval_mSec = ulInterval_nSec / 1000000L;
199 _uIntervalErr_uSec =
200 (ulInterval_nSec - (_uInterval_mSec * 1000000L)) / 1000;
201 // Should always be positive, in range of 0..1000.
202
203 // All set to enable timer interrupt on the chip. Log status.
204 //### Would be very good to check that it works, so we know to go
205 //### to alternate strategy if necessary.
206 _eTechnology = TIMER_AdapterTimerInt;
207 _eState = TIMER_Stopped;
208 }
209 }
210#endif
211
212 // Timer IRQ hook didn't work for some reason, use system timer.
213 if ( _eState == TIMER_Disabled ) {
214 _eTechnology = TIMER_SysTimer;
215 _uInterval_mSec = 31;
216 _uIntervalErr_uSec = 250;
217 _eState = TIMER_Stopped;
218 }
219// } // End of setup for interrupt operation, executed for 1st Timer only.
220
221 // If good creation, add Timer to global timer list & reset all time vbls.
222 if ( _eState != TIMER_Disabled ) {
223 _ulTime = 0;
224 _ulSchedTime = 0;
225 _uCumulativeError = 0;
226 pTimerList->PushOnTail( this );
227 }
228
229 // TIMER is in TIMER_Stopped state upon normal return, TIMER_Disabled
230 // state on error.
231}
232
233
234/**@external TIMER::Start
235 * Start the timer.
236 * @param None
237 * @return int
238 * @notes Maps operation to _iStart (Start and Resume are identical).
239 */
240int TIMER::Start( void )
241{
242 return _iStart();
243}
244
245
246/**@external TIMER::Stop
247 * Stop the operation of the timer.
248 * @param None.
249 * @return int 0.
250 * @notes Maps operation to _iStop (Stop and Pause are identical).
251 */
252int TIMER::Stop( void )
253{
254 return _iStop();
255}
256
257
258/**@external TIMER::Pause
259 * Pause the operation of the timer.
260 * @param None.
261 * @return int 0.
262 * @notes Maps operation to _iStop (Stop and Pause are identical).
263 */
264int TIMER::Pause( void )
265{
266 return _iStop();
267}
268
269
270/**@external TIMER::Resume
271 * Resume the operation of the timer interrupt.
272 * @param None.
273 * @return int 0.
274 * @notes Maps operation to _iStart (Start and Resume are identical).
275 */
276int TIMER::Resume( void )
277{
278 return _iStart();
279}
280
281
282/**@external TIMER::vSchedule
283 * Schedule the next Context hook invocation, 0 for next tick.
284 * @param ULONG ulTime - Absolute time (mSec) at which to schedule next Ctx hook.
285 * @return VOID
286 * @notes
287 */
288VOID TIMER::vSchedule ( ULONG ulTime )
289{
290 cli();
291 _ulSchedTime = ulTime;
292 sti();
293}
294
295
296// Get current stream time (milliseconds). Runs in task context.
297ULONG TIMER::ulGetTime( void )
298{
299 ULONG ulResult; // Return value.
300
301 cli();
302 ulResult = _ulTime;
303 sti();
304 return ulResult;
305}
306
307// Set current stream time (milliseconds). Runs in task context.
308VOID TIMER::vSetTime( ULONG ulTime )
309{
310 cli();
311 _ulTime = ulTime;
312 sti();
313}
314
315
316/**@external TIMER::eState
317 * Query state of timer. TIMER_Disabled inidicates internal failure.
318 * @param None.
319 * @return int Timer state, as enumerated in .hpp file.
320 */
321const int TIMER::eState ( void ) { return _eState; };
322
323
324/**@internal TIMER::_isAnyRunning()
325 * Predicate that determines whether any Timer is in Running state.
326 * @param None.
327 * @return BOOL TRUE iff at least one Timer is in Running state.
328 * @notes Normally called in Task context by Start and Stop routines.
329 */
330static BOOL TIMER::_isAnyRunning( void )
331{
332 TIMER* pTimer; // Used to find Timers in AudioHW list.
333
334 // Walk through all Timers, seeking one that is running.
335 pTimer = (TIMER*) pTimerList->Head();
336 while (pTimer != NULL) {
337 if ( pTimer->eState() == TIMER_Running )
338 return TRUE;
339 pTimer = (TIMER*) pTimer->pNext;
340 }
341 return FALSE;
342}
343
344
345/**@internal TIMER::_iStart
346 * Internal worker to start the timer operation. If we have a HW timer,
347 * will start the interrupt generation. If kernel timer, start it.
348 * @param None
349 * @return int 1 (Boolean TRUE) if timer starts properly
350 * @return int 0 (Boolean FALSE) if problem starting timer
351 * @notes Will force Ctx hook to be scheduled on next tick.
352 */
353int TIMER::_iStart( void )
354{
355 USHORT rc;
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 */
413int 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.
434VOID 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.
460static VOID TIMER::_vStartHWTicks( void )
461{
462}
463
464
465// Start the generation of HW timer ticks on the chip.
466static VOID TIMER::_vStopHWTicks( void )
467{
468}
Note: See TracBrowser for help on using the repository browser.