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