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	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();
178
179	return B_OK;
180}
181
182
183status_t
184DPCQueue::Add(void (*function)(void*), void* argument)
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);
205	if (error != B_OK)
206		Recycle(functionCallback);
207
208	return error;
209}
210
211
212bool
213DPCQueue::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
249void
250DPCQueue::Recycle(FunctionDPCCallback* callback)
251{
252	InterruptsSpinLocker locker(fLock);
253	fUnusedFunctionCallbacks.Insert(callback, false);
254}
255
256
257/*static*/ status_t
258DPCQueue::_ThreadEntry(void* data)
259{
260	return ((DPCQueue*)data)->_Thread();
261}
262
263
264status_t
265DPCQueue::_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
311void
312dpc_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