1 /*
2 * Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7 #include <DPC.h>
8
9 #include <util/AutoLock.h>
10
11
12 #define NORMAL_PRIORITY B_NORMAL_PRIORITY
13 #define HIGH_PRIORITY B_URGENT_DISPLAY_PRIORITY
14 #define REAL_TIME_PRIORITY B_FIRST_REAL_TIME_PRIORITY
15
16 #define DEFAULT_QUEUE_SLOT_COUNT 64
17
18
19 static DPCQueue sNormalPriorityQueue;
20 static DPCQueue sHighPriorityQueue;
21 static DPCQueue sRealTimePriorityQueue;
22
23
24 // #pragma mark - FunctionDPCCallback
25
26
FunctionDPCCallback(DPCQueue * owner)27 FunctionDPCCallback::FunctionDPCCallback(DPCQueue* owner)
28 :
29 fOwner(owner)
30 {
31 }
32
33
34 void
SetTo(void (* function)(void *),void * argument)35 FunctionDPCCallback::SetTo(void (*function)(void*), void* argument)
36 {
37 fFunction = function;
38 fArgument = argument;
39 }
40
41
42 void
DoDPC(DPCQueue * queue)43 FunctionDPCCallback::DoDPC(DPCQueue* queue)
44 {
45 fFunction(fArgument);
46
47 if (fOwner != NULL)
48 fOwner->Recycle(this);
49 }
50
51
52 // #pragma mark - DPCCallback
53
54
DPCCallback()55 DPCCallback::DPCCallback()
56 :
57 fInQueue(NULL)
58 {
59 }
60
61
~DPCCallback()62 DPCCallback::~DPCCallback()
63 {
64 }
65
66
67 // #pragma mark - DPCQueue
68
69
DPCQueue()70 DPCQueue::DPCQueue()
71 :
72 fThreadID(-1),
73 fCallbackInProgress(NULL),
74 fCallbackDoneCondition(NULL)
75 {
76 B_INITIALIZE_SPINLOCK(&fLock);
77
78 fPendingCallbacksCondition.Init(this, "dpc queue");
79 }
80
81
~DPCQueue()82 DPCQueue::~DPCQueue()
83 {
84 // close, if not closed yet
85 {
86 InterruptsSpinLocker locker(fLock);
87 if (!_IsClosed()) {
88 locker.Unlock();
89 Close(false);
90 }
91 }
92
93 // delete function callbacks
94 while (DPCCallback* callback = fUnusedFunctionCallbacks.RemoveHead())
95 delete callback;
96 }
97
98
99 /*static*/ DPCQueue*
DefaultQueue(int priority)100 DPCQueue::DefaultQueue(int priority)
101 {
102 if (priority <= NORMAL_PRIORITY)
103 return &sNormalPriorityQueue;
104
105 if (priority <= HIGH_PRIORITY)
106 return &sHighPriorityQueue;
107
108 return &sRealTimePriorityQueue;
109 }
110
111
112 status_t
Init(const char * name,int32 priority,uint32 reservedSlots)113 DPCQueue::Init(const char* name, int32 priority, uint32 reservedSlots)
114 {
115 // create function callbacks
116 for (uint32 i = 0; i < reservedSlots; i++) {
117 FunctionDPCCallback* callback
118 = new(std::nothrow) FunctionDPCCallback(this);
119 if (callback == NULL)
120 return B_NO_MEMORY;
121
122 fUnusedFunctionCallbacks.Add(callback);
123 }
124
125 // spawn the thread
126 fThreadID = spawn_kernel_thread(&_ThreadEntry, name, priority, this);
127 if (fThreadID < 0)
128 return fThreadID;
129
130 resume_thread(fThreadID);
131
132 return B_OK;
133 }
134
135
136 void
Close(bool cancelPending)137 DPCQueue::Close(bool cancelPending)
138 {
139 InterruptsSpinLocker locker(fLock);
140
141 if (_IsClosed())
142 return;
143
144 // If requested, dequeue all pending callbacks
145 if (cancelPending)
146 fCallbacks.MakeEmpty();
147
148 // mark the queue closed
149 thread_id thread = fThreadID;
150 fThreadID = -1;
151
152 locker.Unlock();
153
154 // wake up the thread and wait for it
155 fPendingCallbacksCondition.NotifyAll();
156 wait_for_thread(thread, NULL);
157 }
158
159
160 status_t
Add(DPCCallback * callback)161 DPCQueue::Add(DPCCallback* callback)
162 {
163 // queue the callback, if the queue isn't closed already
164 InterruptsSpinLocker locker(fLock);
165
166 if (_IsClosed())
167 return B_NOT_INITIALIZED;
168
169 if (callback->fInQueue != NULL)
170 return EALREADY;
171
172 bool wasEmpty = fCallbacks.IsEmpty();
173 fCallbacks.Add(callback);
174 callback->fInQueue = this;
175
176 locker.Unlock();
177
178 // notify the condition variable, if necessary
179 if (wasEmpty)
180 fPendingCallbacksCondition.NotifyAll();
181
182 return B_OK;
183 }
184
185
186 status_t
Add(void (* function)(void *),void * argument)187 DPCQueue::Add(void (*function)(void*), void* argument)
188 {
189 if (function == NULL)
190 return B_BAD_VALUE;
191
192 // get a free callback
193 InterruptsSpinLocker locker(fLock);
194
195 DPCCallback* callback = fUnusedFunctionCallbacks.RemoveHead();
196 if (callback == NULL)
197 return B_NO_MEMORY;
198
199 locker.Unlock();
200
201 // init the callback
202 FunctionDPCCallback* functionCallback
203 = static_cast<FunctionDPCCallback*>(callback);
204 functionCallback->SetTo(function, argument);
205
206 // add it
207 status_t error = Add(functionCallback);
208 if (error != B_OK)
209 Recycle(functionCallback);
210
211 return error;
212 }
213
214
215 bool
Cancel(DPCCallback * callback)216 DPCQueue::Cancel(DPCCallback* callback)
217 {
218 InterruptsSpinLocker locker(fLock);
219
220 // If the callback is queued, remove it.
221 if (callback->fInQueue == this) {
222 fCallbacks.Remove(callback);
223 return true;
224 }
225
226 // The callback is not queued. If it isn't in progress, we're done, too.
227 if (callback != fCallbackInProgress)
228 return false;
229
230 // The callback is currently being executed. We need to wait for it to be
231 // done.
232
233 // Set the respective condition, if not set yet. For the unlikely case that
234 // there are multiple threads trying to cancel the callback at the same
235 // time, the condition variable of the first thread will be used.
236 ConditionVariable condition;
237 if (fCallbackDoneCondition == NULL)
238 fCallbackDoneCondition = &condition;
239
240 // add our wait entry
241 ConditionVariableEntry waitEntry;
242 fCallbackDoneCondition->Add(&waitEntry);
243
244 // wait
245 locker.Unlock();
246 waitEntry.Wait();
247
248 return false;
249 }
250
251
252 void
Recycle(FunctionDPCCallback * callback)253 DPCQueue::Recycle(FunctionDPCCallback* callback)
254 {
255 InterruptsSpinLocker locker(fLock);
256 fUnusedFunctionCallbacks.Insert(callback, false);
257 }
258
259
260 /*static*/ status_t
_ThreadEntry(void * data)261 DPCQueue::_ThreadEntry(void* data)
262 {
263 return ((DPCQueue*)data)->_Thread();
264 }
265
266
267 status_t
_Thread()268 DPCQueue::_Thread()
269 {
270 while (true) {
271 InterruptsSpinLocker locker(fLock);
272
273 // get the next pending callback
274 DPCCallback* callback = fCallbacks.RemoveHead();
275 if (callback == NULL) {
276 // nothing is pending -- wait unless the queue is already closed
277 if (_IsClosed())
278 break;
279
280 ConditionVariableEntry waitEntry;
281 fPendingCallbacksCondition.Add(&waitEntry);
282
283 locker.Unlock();
284 waitEntry.Wait();
285
286 continue;
287 }
288
289 callback->fInQueue = NULL;
290 fCallbackInProgress = callback;
291
292 // call the callback
293 locker.Unlock();
294 callback->DoDPC(this);
295 locker.Lock();
296
297 fCallbackInProgress = NULL;
298
299 // wake up threads waiting for the callback to be done
300 ConditionVariable* doneCondition = fCallbackDoneCondition;
301 fCallbackDoneCondition = NULL;
302 locker.Unlock();
303 if (doneCondition != NULL)
304 doneCondition->NotifyAll();
305 }
306
307 return B_OK;
308 }
309
310
311 // #pragma mark - kernel private
312
313
314 void
dpc_init()315 dpc_init()
316 {
317 // create the default queues
318 new(&sNormalPriorityQueue) DPCQueue;
319 new(&sHighPriorityQueue) DPCQueue;
320 new(&sRealTimePriorityQueue) DPCQueue;
321
322 if (sNormalPriorityQueue.Init("dpc: normal priority", NORMAL_PRIORITY,
323 DEFAULT_QUEUE_SLOT_COUNT) != B_OK
324 || sHighPriorityQueue.Init("dpc: high priority", HIGH_PRIORITY,
325 DEFAULT_QUEUE_SLOT_COUNT) != B_OK
326 || sRealTimePriorityQueue.Init("dpc: real-time priority",
327 REAL_TIME_PRIORITY, DEFAULT_QUEUE_SLOT_COUNT) != B_OK) {
328 panic("Failed to create default DPC queues!");
329 }
330 }
331