1/*
2    File:       CTimer.cpp
3
4    Contains:   Timer object
5
6
7*/
8
9#include "CTimer.h"
10#include "IrDALog.h"
11
12#if defined(hasTracing) && defined(hasCTimerTracing)
13
14enum CTimerTraceCodes
15{
16    kLogNew = 1,
17    kLogInit,
18    kLogFree1,
19    kLogFree2,
20    kStartTimerDelay,
21    kStartTimerSignal,
22
23    kLogStopTimer1,
24    kLogStopTimer2,
25    kLogStopTimer3,
26    kLogStopTimer4,
27    kLogStopTimer5,
28
29    kLogTimeout1,
30    kLogTimeout2,
31    kLogTimeout3,
32    kLogTimeout4,
33    kLogTimeout5,
34    kLogGrimReaper
35};
36
37static
38EventTraceCauseDesc TraceEvents[] = {
39    {kLogNew,                   "CTimer: new, obj="},
40    {kLogInit,                  "CTimer: init, fTimerSrc="},
41    {kLogFree1,                 "CTimer: free, obj="},
42    {kLogFree2,                 "CTimer: free, fTimerSrc="},
43    {kStartTimerDelay,          "CTimer: Start timer, delay="},
44    {kStartTimerSignal,         "CTimer: Start timer, signal=, new retain="},
45
46    {kLogStopTimer1,            "CTimer: Stop timer, obj="},
47    {kLogStopTimer2,            "CTimer: Stop timer, idle so nop.  retain stays="},
48    {kLogStopTimer3,            "CTimer: Stop timer, cancel worked, retain before release="},
49    {kLogStopTimer4,            "CTimer: Stop timer, cancel failed, retain still="},
50    {kLogStopTimer5,            "CTimer: Stop timer, exit, retain count="},
51
52    {kLogTimeout1,              "CTimer: timeout called, obj="},
53    {kLogTimeout2,              "CTimer: timeout called, retain="},
54    {kLogTimeout3,              "CTimer: timeout called, owner="},
55    {kLogTimeout4,              "CTimer: timeout called, sender="},
56    {kLogTimeout5,              "CTimer: timeout back from calling owner"},
57    {kLogGrimReaper,            "CTimer: grim reaper"}
58
59};
60    #define XTRACE(x, y, z) IrDALogAdd ( x, y, (uintptr_t)z & 0xffff, TraceEvents, true )
61#else
62    #define XTRACE(x, y, z) ((void)0)
63#endif
64
65
66#define super OSObject
67    OSDefineMetaClassAndStructors(CTimer, OSObject);
68
69CTimer *
70CTimer::cTimer(IOWorkLoop *work, OSObject *owner, Action callback)
71{
72    CTimer * obj = new CTimer;
73
74    XTRACE(kLogNew, 0, obj);
75
76    if (obj && !obj->init(work, owner, callback)) {
77	obj->release();
78	obj = nil;
79    }
80    return obj;
81}
82
83Boolean CTimer::init(IOWorkLoop *work, OSObject *owner, Action callback)
84{
85    IOReturn rc;
86
87    fTimerSrc = nil;
88
89    require(work, Failed);
90    require(owner, Failed);
91    require(callback, Failed);
92
93    if (!super::init()) return false;
94
95    fOwner    = owner;                  // save backpointer
96    fCallback = callback;               // save the real callback, we're going to get it first
97    fWorkLoop = work;                   // save workloop for free later
98//  fTimerSrc = IOTimerEventSource::timerEventSource(owner, callback);      // make a timer
99    fTimerSrc = IrDATimerEventSource::timerEventSource(this, &CTimer::timeout);
100    require(fTimerSrc, Failed);
101
102    fBusy = false;
103
104    fGrimReaper = thread_call_allocate(grim_reaper, this);
105    require(fGrimReaper, Failed);
106
107    rc = work->addEventSource(fTimerSrc);
108    require(rc == kIOReturnSuccess, Failed);
109
110    XTRACE(kLogInit, 0, fTimerSrc);
111    return true;
112
113Failed:
114    return false;
115}
116
117//
118// free - this should now be impossible to have invoked while a timer is pending.
119//
120
121void CTimer::free()
122{
123    XTRACE(kLogFree1, 0, this);
124    Boolean ok;
125
126    check(fBusy == false);
127
128    if (fGrimReaper) {
129	ok = thread_call_cancel(fGrimReaper);
130	check(ok == false);         // should *never* have work!
131	thread_call_free(fGrimReaper);
132	fGrimReaper = nil;
133    }
134
135    if (fTimerSrc) {
136	XTRACE(kLogFree2, 0, fTimerSrc);
137	ok = fTimerSrc->SafeCancelTimeout();
138	check(ok == false);         // should *not* have worked (no timer pending)
139
140	fWorkLoop->removeEventSource(fTimerSrc);
141
142	fTimerSrc->release();
143	fTimerSrc = nil;
144    }
145
146    super::free();
147}
148
149//
150// Start a timer to fire up after a while.
151// Note the retain on self to prevent zombie
152// timers getting invoked after they've been freed.
153//
154
155void CTimer::StartTimer(TTimeout delay, UInt32 sig)
156{
157    IOReturn rc;
158
159    require(fTimerSrc, Fail);
160
161    XTRACE (kStartTimerDelay,  delay>>16, delay);
162
163    if (fBusy) StopTimer();			// cleanup
164    require(fBusy == false, Fail);                  // sanity
165
166    fBusy = true;                                   // mark timer as in-use
167    this->retain();                                 // retain to prevent dangling
168
169    fSig = sig;
170
171    if (delay < 0)                                  // microsecond timer
172	rc = fTimerSrc->setTimeoutUS(-delay);
173    else
174	rc = fTimerSrc->setTimeoutMS(delay);        // millisecond timer
175
176    check(rc == kIOReturnSuccess);
177    XTRACE (kStartTimerSignal, sig, this->getRetainCount());
178
179Fail:
180    return;
181}
182
183void CTimer::StopTimer()
184{
185    Boolean ok;
186    XTRACE(kLogStopTimer1, 0, this);
187
188    if (fBusy == false) {                       // stopped, but we're already stopped (nop)
189	XTRACE(kLogStopTimer2, 0, this->getRetainCount());
190	return;
191    }
192
193    if (fTimerSrc) {
194	ok = fTimerSrc->SafeCancelTimeout();    // returns ok if it worked
195	if (ok) {                               // if cancel timer worked, then
196	    XTRACE(kLogStopTimer3, 0, this->getRetainCount());
197	    fBusy = false;                      // mark timer as available and
198	    this->release();                    // we can release self safely
199	} else {
200	    XTRACE(kLogStopTimer4, 0, this->getRetainCount());
201	}
202    }
203    XTRACE(kLogStopTimer5, 0, this->getRetainCount());
204    return;
205}
206
207void
208CTimer::timeout(OSObject *owner, IrDATimerEventSource *sender)
209{
210    CTimer  *obj;
211
212    XTRACE(kLogTimeout1, 0, owner);
213    obj = OSDynamicCast(CTimer, owner);
214    require(obj, Fail);
215    require(obj->fOwner, Fail);
216    require(obj->fCallback, Fail);
217    require(obj->fBusy, Fail);
218
219    XTRACE(kLogTimeout2, 0, obj->getRetainCount());
220
221    if (obj->getRetainCount() == 1) {           // if it's down to one, then we're been deleted but for our start timer
222	thread_call_enter1(obj->fGrimReaper, 0);
223	return;
224    }
225    obj->fBusy = false;
226    obj->release();
227    // temp debugging
228    XTRACE(kLogTimeout3, 0, obj->fOwner);
229    XTRACE(kLogTimeout4, 0, sender);
230    (*obj->fCallback)(obj->fOwner, sender);
231    XTRACE(kLogTimeout5, 0xffff, 0xffff);
232
233Fail:
234    return;
235}
236
237/*static*/
238void CTimer::grim_reaper(thread_call_param_t param0, thread_call_param_t param1)
239{
240    CTimer *obj;
241
242    XTRACE(kLogGrimReaper, 0x1111, 0x1111);
243
244    obj = OSDynamicCast(CTimer, (OSObject *)param0);
245    require(obj, Fail);
246    require(obj->fBusy, Fail);
247
248    obj->fBusy = false;
249    obj->release();
250
251    XTRACE(kLogGrimReaper, 0xffff, 0xffff);
252    return;
253
254Fail:
255    XTRACE(kLogGrimReaper, 0xdead, 0xbeef);
256    return;
257}
258