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