xref: /haiku/src/system/kernel/DPC.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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, bool schedulerLocked)
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 	bool wasEmpty = fCallbacks.IsEmpty();
170 	fCallbacks.Add(callback);
171 	callback->fInQueue = this;
172 
173 	locker.Unlock();
174 
175 	// notify the condition variable, if necessary
176 	if (wasEmpty)
177 		fPendingCallbacksCondition.NotifyAll(schedulerLocked);
178 
179 	return B_OK;
180 }
181 
182 
183 status_t
184 DPCQueue::Add(void (*function)(void*), void* argument, bool schedulerLocked)
185 {
186 	if (function == NULL)
187 		return B_BAD_VALUE;
188 
189 	// get a free callback
190 	InterruptsSpinLocker locker(fLock);
191 
192 	DPCCallback* callback = fUnusedFunctionCallbacks.RemoveHead();
193 	if (callback == NULL)
194 		return B_NO_MEMORY;
195 
196 	locker.Unlock();
197 
198 	// init the callback
199 	FunctionDPCCallback* functionCallback
200 		= static_cast<FunctionDPCCallback*>(callback);
201 	functionCallback->SetTo(function, argument);
202 
203 	// add it
204 	status_t error = Add(functionCallback, schedulerLocked);
205 	if (error != B_OK)
206 		Recycle(functionCallback);
207 
208 	return error;
209 }
210 
211 
212 bool
213 DPCQueue::Cancel(DPCCallback* callback)
214 {
215 	InterruptsSpinLocker locker(fLock);
216 
217 	// If the callback is queued, remove it.
218 	if (callback->fInQueue == this) {
219 		fCallbacks.Remove(callback);
220 		return true;
221 	}
222 
223 	// The callback is not queued. If it isn't in progress, we're done, too.
224 	if (callback != fCallbackInProgress)
225 		return false;
226 
227 	// The callback is currently being executed. We need to wait for it to be
228 	// done.
229 
230 	// Set the respective condition, if not set yet. For the unlikely case that
231 	// there are multiple threads trying to cancel the callback at the same
232 	// time, the condition variable of the first thread will be used.
233 	ConditionVariable condition;
234 	if (fCallbackDoneCondition == NULL)
235 		fCallbackDoneCondition = &condition;
236 
237 	// add our wait entry
238 	ConditionVariableEntry waitEntry;
239 	fCallbackDoneCondition->Add(&waitEntry);
240 
241 	// wait
242 	locker.Unlock();
243 	waitEntry.Wait();
244 
245 	return false;
246 }
247 
248 
249 void
250 DPCQueue::Recycle(FunctionDPCCallback* callback)
251 {
252 	InterruptsSpinLocker locker(fLock);
253 	fUnusedFunctionCallbacks.Insert(callback, false);
254 }
255 
256 
257 /*static*/ status_t
258 DPCQueue::_ThreadEntry(void* data)
259 {
260 	return ((DPCQueue*)data)->_Thread();
261 }
262 
263 
264 status_t
265 DPCQueue::_Thread()
266 {
267 	while (true) {
268 		InterruptsSpinLocker locker(fLock);
269 
270 		// get the next pending callback
271 		DPCCallback* callback = fCallbacks.RemoveHead();
272 		if (callback == NULL) {
273 			// nothing is pending -- wait unless the queue is already closed
274 			if (_IsClosed())
275 				break;
276 
277 			ConditionVariableEntry waitEntry;
278 			fPendingCallbacksCondition.Add(&waitEntry);
279 
280 			locker.Unlock();
281 			waitEntry.Wait();
282 
283 			continue;
284 		}
285 
286 		callback->fInQueue = NULL;
287 		fCallbackInProgress = callback;
288 
289 		// call the callback
290 		locker.Unlock();
291 		callback->DoDPC(this);
292 		locker.Lock();
293 
294 		fCallbackInProgress = NULL;
295 
296 		// wake up threads waiting for the callback to be done
297 		ConditionVariable* doneCondition = fCallbackDoneCondition;
298 		fCallbackDoneCondition = NULL;
299 		locker.Unlock();
300 		if (doneCondition != NULL)
301 			doneCondition->NotifyAll();
302 	}
303 
304 	return B_OK;
305 }
306 
307 
308 // #pragma mark - kernel private
309 
310 
311 void
312 dpc_init()
313 {
314 	// create the default queues
315 	new(&sNormalPriorityQueue) DPCQueue;
316 	new(&sHighPriorityQueue) DPCQueue;
317 	new(&sRealTimePriorityQueue) DPCQueue;
318 
319 	if (sNormalPriorityQueue.Init("dpc: normal priority", NORMAL_PRIORITY,
320 			DEFAULT_QUEUE_SLOT_COUNT) != B_OK
321 		|| sHighPriorityQueue.Init("dpc: high priority", HIGH_PRIORITY,
322 			DEFAULT_QUEUE_SLOT_COUNT) != B_OK
323 		|| sRealTimePriorityQueue.Init("dpc: real-time priority",
324 			REAL_TIME_PRIORITY, DEFAULT_QUEUE_SLOT_COUNT) != B_OK) {
325 		panic("Failed to create default DPC queues!");
326 	}
327 }
328