xref: /haiku/src/system/kernel/DPC.cpp (revision 084e24d0bf3808808e2bf58d4d65f493bd2b8f49)
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 
27 FunctionDPCCallback::FunctionDPCCallback(DPCQueue* owner)
28 	:
29 	fOwner(owner)
30 {
31 }
32 
33 
34 void
35 FunctionDPCCallback::SetTo(void (*function)(void*), void* argument)
36 {
37 	fFunction = function;
38 	fArgument = argument;
39 }
40 
41 
42 void
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 
55 DPCCallback::DPCCallback()
56 	:
57 	fInQueue(NULL)
58 {
59 }
60 
61 
62 DPCCallback::~DPCCallback()
63 {
64 }
65 
66 
67 // #pragma mark - DPCQueue
68 
69 
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 
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*
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
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
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
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
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
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
253 DPCQueue::Recycle(FunctionDPCCallback* callback)
254 {
255 	InterruptsSpinLocker locker(fLock);
256 	fUnusedFunctionCallbacks.Insert(callback, false);
257 }
258 
259 
260 /*static*/ status_t
261 DPCQueue::_ThreadEntry(void* data)
262 {
263 	return ((DPCQueue*)data)->_Thread();
264 }
265 
266 
267 status_t
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
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