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
19static DPCQueue sNormalPriorityQueue;
20static DPCQueue sHighPriorityQueue;
21static DPCQueue sRealTimePriorityQueue;
22
23
24// #pragma mark - FunctionDPCCallback
25
26
27FunctionDPCCallback::FunctionDPCCallback(DPCQueue* owner)
28	:
29	fOwner(owner)
30{
31}
32
33
34void
35FunctionDPCCallback::SetTo(void (*function)(void*), void* argument)
36{
37	fFunction = function;
38	fArgument = argument;
39}
40
41
42void
43FunctionDPCCallback::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
55DPCCallback::DPCCallback()
56	:
57	fInQueue(NULL)
58{
59}
60
61
62DPCCallback::~DPCCallback()
63{
64}
65
66
67// #pragma mark - DPCQueue
68
69
70DPCQueue::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
82DPCQueue::~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*
100DPCQueue::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
112status_t
113DPCQueue::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
136void
137DPCQueue::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
160status_t
161DPCQueue::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
186status_t
187DPCQueue::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
215bool
216DPCQueue::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
252void
253DPCQueue::Recycle(FunctionDPCCallback* callback)
254{
255	InterruptsSpinLocker locker(fLock);
256	fUnusedFunctionCallbacks.Insert(callback, false);
257}
258
259
260/*static*/ status_t
261DPCQueue::_ThreadEntry(void* data)
262{
263	return ((DPCQueue*)data)->_Thread();
264}
265
266
267status_t
268DPCQueue::_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
314void
315dpc_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