* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include <DPC.h>
#include <util/AutoLock.h>
#define NORMAL_PRIORITY B_NORMAL_PRIORITY
#define HIGH_PRIORITY B_URGENT_DISPLAY_PRIORITY
#define REAL_TIME_PRIORITY B_FIRST_REAL_TIME_PRIORITY
#define DEFAULT_QUEUE_SLOT_COUNT 64
static DPCQueue sNormalPriorityQueue;
static DPCQueue sHighPriorityQueue;
static DPCQueue sRealTimePriorityQueue;
FunctionDPCCallback::FunctionDPCCallback(DPCQueue* owner)
:
fOwner(owner)
{
}
void
FunctionDPCCallback::SetTo(void (*function)(void*), void* argument)
{
fFunction = function;
fArgument = argument;
}
void
FunctionDPCCallback::DoDPC(DPCQueue* queue)
{
fFunction(fArgument);
if (fOwner != NULL)
fOwner->Recycle(this);
}
DPCCallback::DPCCallback()
:
fInQueue(NULL)
{
}
DPCCallback::~DPCCallback()
{
}
DPCQueue::DPCQueue()
:
fThreadID(-1),
fCallbackInProgress(NULL),
fCallbackDoneCondition(NULL)
{
B_INITIALIZE_SPINLOCK(&fLock);
fPendingCallbacksCondition.Init(this, "dpc queue");
}
DPCQueue::~DPCQueue()
{
{
InterruptsSpinLocker locker(fLock);
if (!_IsClosed()) {
locker.Unlock();
Close(false);
}
}
while (DPCCallback* callback = fUnusedFunctionCallbacks.RemoveHead())
delete callback;
}
DPCQueue*
DPCQueue::DefaultQueue(int priority)
{
if (priority <= NORMAL_PRIORITY)
return &sNormalPriorityQueue;
if (priority <= HIGH_PRIORITY)
return &sHighPriorityQueue;
return &sRealTimePriorityQueue;
}
status_t
DPCQueue::Init(const char* name, int32 priority, uint32 reservedSlots)
{
for (uint32 i = 0; i < reservedSlots; i++) {
FunctionDPCCallback* callback
= new(std::nothrow) FunctionDPCCallback(this);
if (callback == NULL)
return B_NO_MEMORY;
fUnusedFunctionCallbacks.Add(callback);
}
fThreadID = spawn_kernel_thread(&_ThreadEntry, name, priority, this);
if (fThreadID < 0)
return fThreadID;
resume_thread(fThreadID);
return B_OK;
}
void
DPCQueue::Close(bool cancelPending)
{
InterruptsSpinLocker locker(fLock);
if (_IsClosed())
return;
if (cancelPending)
fCallbacks.MakeEmpty();
thread_id thread = fThreadID;
fThreadID = -1;
locker.Unlock();
fPendingCallbacksCondition.NotifyAll();
wait_for_thread(thread, NULL);
}
status_t
DPCQueue::Add(DPCCallback* callback)
{
InterruptsSpinLocker locker(fLock);
if (_IsClosed())
return B_NOT_INITIALIZED;
if (callback->fInQueue != NULL)
return EALREADY;
bool wasEmpty = fCallbacks.IsEmpty();
fCallbacks.Add(callback);
callback->fInQueue = this;
locker.Unlock();
if (wasEmpty)
fPendingCallbacksCondition.NotifyAll();
return B_OK;
}
status_t
DPCQueue::Add(void (*function)(void*), void* argument)
{
if (function == NULL)
return B_BAD_VALUE;
InterruptsSpinLocker locker(fLock);
DPCCallback* callback = fUnusedFunctionCallbacks.RemoveHead();
if (callback == NULL)
return B_NO_MEMORY;
locker.Unlock();
FunctionDPCCallback* functionCallback
= static_cast<FunctionDPCCallback*>(callback);
functionCallback->SetTo(function, argument);
status_t error = Add(functionCallback);
if (error != B_OK)
Recycle(functionCallback);
return error;
}
bool
DPCQueue::Cancel(DPCCallback* callback)
{
InterruptsSpinLocker locker(fLock);
if (callback->fInQueue == this) {
fCallbacks.Remove(callback);
return true;
}
if (callback != fCallbackInProgress)
return false;
ConditionVariable condition;
if (fCallbackDoneCondition == NULL)
fCallbackDoneCondition = &condition;
ConditionVariableEntry waitEntry;
fCallbackDoneCondition->Add(&waitEntry);
locker.Unlock();
waitEntry.Wait();
return false;
}
void
DPCQueue::Recycle(FunctionDPCCallback* callback)
{
InterruptsSpinLocker locker(fLock);
fUnusedFunctionCallbacks.Insert(callback, false);
}
status_t
DPCQueue::_ThreadEntry(void* data)
{
return ((DPCQueue*)data)->_Thread();
}
status_t
DPCQueue::_Thread()
{
while (true) {
InterruptsSpinLocker locker(fLock);
DPCCallback* callback = fCallbacks.RemoveHead();
if (callback == NULL) {
if (_IsClosed())
break;
ConditionVariableEntry waitEntry;
fPendingCallbacksCondition.Add(&waitEntry);
locker.Unlock();
waitEntry.Wait();
continue;
}
callback->fInQueue = NULL;
fCallbackInProgress = callback;
locker.Unlock();
callback->DoDPC(this);
locker.Lock();
fCallbackInProgress = NULL;
ConditionVariable* doneCondition = fCallbackDoneCondition;
fCallbackDoneCondition = NULL;
locker.Unlock();
if (doneCondition != NULL)
doneCondition->NotifyAll();
}
return B_OK;
}
void
dpc_init()
{
new(&sNormalPriorityQueue) DPCQueue;
new(&sHighPriorityQueue) DPCQueue;
new(&sRealTimePriorityQueue) DPCQueue;
if (sNormalPriorityQueue.Init("dpc: normal priority", NORMAL_PRIORITY,
DEFAULT_QUEUE_SLOT_COUNT) != B_OK
|| sHighPriorityQueue.Init("dpc: high priority", HIGH_PRIORITY,
DEFAULT_QUEUE_SLOT_COUNT) != B_OK
|| sRealTimePriorityQueue.Init("dpc: real-time priority",
REAL_TIME_PRIORITY, DEFAULT_QUEUE_SLOT_COUNT) != B_OK) {
panic("Failed to create default DPC queues!");
}
}