Home | Info | Community | Development | myReactOS | Contact Us
ReactOS Development > Doxygencsq.c
Go to the documentation of this file.
00001 /* 00002 * ReactOS Cancel-Safe Queue library 00003 * Copyright (c) 2004, Vizzini (vizzini@plasmic.com) 00004 * Licensed under the GNU GPL for the ReactOS project 00005 * 00006 * This file implements the ReactOS CSQ library. For background and overview 00007 * information on these routines, read csq.h. For the authoritative reference 00008 * to using these routines, see the current DDK (IoCsqXXX and CsqXxx callbacks). 00009 * 00010 * There are a couple of subtle races that this library is designed to avoid. 00011 * Please read the code (particularly IoCsqInsertIrpEx and IoCsqRemoveIrp) for 00012 * some details. 00013 * 00014 * In general, we try here to avoid the race between these queue/dequeue 00015 * interfaces and our own cancel routine. This library supplies a cancel 00016 * routine that is used in all IRPs that are queued to it. The major race 00017 * conditions surround the proper handling of in-between cases, such as in-progress 00018 * queue and de-queue operations. 00019 * 00020 * When you're thinking about these operations, keep in mind that three or four 00021 * processors can have queue and dequeue operations in progress simultaneously, 00022 * and a user thread may cancel any IRP at any time. Also, these operations don't 00023 * all happen at DISPATCH_LEVEL all of the time, so thread switching on a single 00024 * processor can create races too. 00025 */ 00026 /* $Id: csq.c 42461 2009-08-07 10:45:59Z sginsberg $ */ 00027 00028 #include <ntdef.h> 00029 #undef DECLSPEC_IMPORT 00030 #define DECLSPEC_IMPORT 00031 #include <ntifs.h> 00032 00033 00034 static VOID NTAPI IopCsqCancelRoutine(PDEVICE_OBJECT DeviceObject, 00035 PIRP Irp) 00036 /* 00037 * FUNCTION: Cancel routine that is installed on any IRP that this library manages 00038 * ARGUMENTS: 00039 * [Called back by the system] 00040 * NOTES: 00041 * - We assume that Irp->Tail.Overlay.DriverContext[3] has either a IO_CSQ 00042 * or an IO_CSQ_IRP_CONTEXT in it, but we have to figure out which it is 00043 * - By the time this routine executes, the I/O Manager has already cleared 00044 * the cancel routine pointer in the IRP, so it will only be canceled once 00045 * - Because of this, we're guaranteed that Irp is valid the whole time 00046 * - Don't forget to release the cancel spinlock ASAP --> #1 hot lock in the 00047 * system 00048 * - May be called at high IRQL 00049 */ 00050 { 00051 PIO_CSQ Csq; 00052 KIRQL Irql; 00053 00054 /* First things first: */ 00055 IoReleaseCancelSpinLock(Irp->CancelIrql); 00056 00057 /* We could either get a context or just a csq */ 00058 Csq = (PIO_CSQ)Irp->Tail.Overlay.DriverContext[3]; 00059 00060 if(Csq->Type == IO_TYPE_CSQ_IRP_CONTEXT) 00061 { 00062 PIO_CSQ_IRP_CONTEXT Context = (PIO_CSQ_IRP_CONTEXT)Csq; 00063 Csq = Context->Csq; 00064 00065 /* clean up context while we're here */ 00066 Context->Irp = NULL; 00067 } 00068 00069 /* Now that we have our CSQ, complete the IRP */ 00070 Csq->CsqAcquireLock(Csq, &Irql); 00071 { 00072 Csq->CsqRemoveIrp(Csq, Irp); 00073 Csq->CsqCompleteCanceledIrp(Csq, Irp); 00074 } 00075 Csq->CsqReleaseLock(Csq, Irql); 00076 } 00077 00078 00079 NTSTATUS NTAPI IoCsqInitialize(PIO_CSQ Csq, 00080 PIO_CSQ_INSERT_IRP CsqInsertIrp, 00081 PIO_CSQ_REMOVE_IRP CsqRemoveIrp, 00082 PIO_CSQ_PEEK_NEXT_IRP CsqPeekNextIrp, 00083 PIO_CSQ_ACQUIRE_LOCK CsqAcquireLock, 00084 PIO_CSQ_RELEASE_LOCK CsqReleaseLock, 00085 PIO_CSQ_COMPLETE_CANCELED_IRP CsqCompleteCanceledIrp) 00086 /* 00087 * FUNCTION: Set up a CSQ struct to initialize the queue 00088 * ARGUMENTS: 00089 * Csq: Caller-allocated non-paged space for our IO_CSQ to be initialized 00090 * CsqInsertIrp: Insert routine 00091 * CsqRemoveIrp: Remove routine 00092 * CsqPeekNextIrp: Routine to paeek at the next IRP in queue 00093 * CsqAcquireLock: Acquire the queue's lock 00094 * CsqReleaseLock: Release the queue's lock 00095 * CsqCompleteCanceledIrp: Routine to complete IRPs when they are canceled 00096 * RETURNS: 00097 * - STATUS_SUCCESS in all cases 00098 * NOTES: 00099 * - Csq must be non-paged, as the queue is manipulated with a held spinlock 00100 */ 00101 { 00102 Csq->Type = IO_TYPE_CSQ; 00103 Csq->CsqInsertIrp = CsqInsertIrp; 00104 Csq->CsqRemoveIrp = CsqRemoveIrp; 00105 Csq->CsqPeekNextIrp = CsqPeekNextIrp; 00106 Csq->CsqAcquireLock = CsqAcquireLock; 00107 Csq->CsqReleaseLock = CsqReleaseLock; 00108 Csq->CsqCompleteCanceledIrp = CsqCompleteCanceledIrp; 00109 Csq->ReservePointer = NULL; 00110 00111 return STATUS_SUCCESS; 00112 } 00113 00114 00115 NTSTATUS NTAPI IoCsqInitializeEx(PIO_CSQ Csq, 00116 PIO_CSQ_INSERT_IRP_EX CsqInsertIrpEx, 00117 PIO_CSQ_REMOVE_IRP CsqRemoveIrp, 00118 PIO_CSQ_PEEK_NEXT_IRP CsqPeekNextIrp, 00119 PIO_CSQ_ACQUIRE_LOCK CsqAcquireLock, 00120 PIO_CSQ_RELEASE_LOCK CsqReleaseLock, 00121 PIO_CSQ_COMPLETE_CANCELED_IRP CsqCompleteCanceledIrp) 00122 /* 00123 * FUNCTION: Set up a CSQ struct to initialize the queue (extended version) 00124 * ARGUMENTS: 00125 * Csq: Caller-allocated non-paged space for our IO_CSQ to be initialized 00126 * CsqInsertIrpEx: Extended insert routine 00127 * CsqRemoveIrp: Remove routine 00128 * CsqPeekNextIrp: Routine to paeek at the next IRP in queue 00129 * CsqAcquireLock: Acquire the queue's lock 00130 * CsqReleaseLock: Release the queue's lock 00131 * CsqCompleteCanceledIrp: Routine to complete IRPs when they are canceled 00132 * RETURNS: 00133 * - STATUS_SUCCESS in all cases 00134 * NOTES: 00135 * - Csq must be non-paged, as the queue is manipulated with a held spinlock 00136 */ 00137 { 00138 Csq->Type = IO_TYPE_CSQ_EX; 00139 Csq->CsqInsertIrp = (PIO_CSQ_INSERT_IRP)CsqInsertIrpEx; 00140 Csq->CsqRemoveIrp = CsqRemoveIrp; 00141 Csq->CsqPeekNextIrp = CsqPeekNextIrp; 00142 Csq->CsqAcquireLock = CsqAcquireLock; 00143 Csq->CsqReleaseLock = CsqReleaseLock; 00144 Csq->CsqCompleteCanceledIrp = CsqCompleteCanceledIrp; 00145 Csq->ReservePointer = NULL; 00146 00147 return STATUS_SUCCESS; 00148 } 00149 00150 00151 VOID NTAPI IoCsqInsertIrp(PIO_CSQ Csq, 00152 PIRP Irp, 00153 PIO_CSQ_IRP_CONTEXT Context) 00154 /* 00155 * FUNCTION: Insert an IRP into the CSQ 00156 * ARGUMENTS: 00157 * Csq: Pointer to the initialized CSQ 00158 * Irp: Pointer to the IRP to queue 00159 * Context: Context record to track the IRP while queued 00160 * NOTES: 00161 * - Just passes through to IoCsqInsertIrpEx, with no InsertContext 00162 */ 00163 { 00164 IoCsqInsertIrpEx(Csq, Irp, Context, 0); 00165 } 00166 00167 00168 NTSTATUS NTAPI IoCsqInsertIrpEx(PIO_CSQ Csq, 00169 PIRP Irp, 00170 PIO_CSQ_IRP_CONTEXT Context, 00171 PVOID InsertContext) 00172 /* 00173 * FUNCTION: Insert an IRP into the CSQ, with additional tracking context 00174 * ARGUMENTS: 00175 * Csq: Pointer to the initialized CSQ 00176 * Irp: Pointer to the IRP to queue 00177 * Context: Context record to track the IRP while queued 00178 * InsertContext: additional data that is passed through to CsqInsertIrpEx 00179 * NOTES: 00180 * - Passes the additional context through to the driver-supplied callback, 00181 * which can be used with more sophistocated queues 00182 * - Marks the IRP pending in all cases 00183 * - Guaranteed to not queue a canceled IRP 00184 * - This is complicated logic, and is patterend after the Microsoft library. 00185 * I'm sure I have gotten the details wrong on a fine point or two, but 00186 * basically this works with the MS-supplied samples. 00187 */ 00188 { 00189 NTSTATUS Retval = STATUS_SUCCESS; 00190 KIRQL Irql; 00191 00192 Csq->CsqAcquireLock(Csq, &Irql); 00193 00194 do 00195 { 00196 /* mark all irps pending -- says so in the cancel sample */ 00197 IoMarkIrpPending(Irp); 00198 00199 /* set up the context if we have one */ 00200 if(Context) 00201 { 00202 Context->Type = IO_TYPE_CSQ_IRP_CONTEXT; 00203 Context->Irp = Irp; 00204 Context->Csq = Csq; 00205 Irp->Tail.Overlay.DriverContext[3] = Context; 00206 } 00207 else 00208 Irp->Tail.Overlay.DriverContext[3] = Csq; 00209 00210 /* 00211 * NOTE! This is very sensitive to order. If you set the cancel routine 00212 * *before* you queue the IRP, our cancel routine will get called back for 00213 * an IRP that isn't in its queue. 00214 * 00215 * There are three possibilities: 00216 * 1) We get an IRP, we queue it, and it is valid the whole way 00217 * 2) We get an IRP, and the IO manager cancels it before we're done here 00218 * 3) We get an IRP, queue it, and the IO manager cancels it. 00219 * 00220 * #2 is is a booger. 00221 * 00222 * When the IO manger receives a request to cancel an IRP, it sets the cancel 00223 * bit in the IRP's control byte to TRUE. Then, it looks to see if a cancel 00224 * routine is set. If it isn't, the IO manager just returns to the caller. 00225 * If there *is* a routine, it gets called. 00226 * 00227 * If we test for cancel first and then set the cancel routine, there is a spot 00228 * between test and set that the IO manager can cancel us without our knowledge, 00229 * so we miss a cancel request. That is bad. 00230 * 00231 * If we set a routine first and then test for cancel, we race with our completion 00232 * routine: We set the routine, the IO Manager sets cancel, we test cancel and find 00233 * it is TRUE. Meanwhile the IO manager has called our cancel routine already, so 00234 * we can't complete the IRP because it'll rip it out from under the cancel routine. 00235 * 00236 * The IO manager does us a favor though: it nulls out the cancel routine in the IRP 00237 * before calling it. Therefore, if we test to see if the cancel routine is NULL 00238 * (after we have just set it), that means our own cancel routine is already working 00239 * on the IRP, and we can just return quietly. Otherwise, we have to de-queue the 00240 * IRP and cancel it ourselves. 00241 * 00242 * We have to go through all of this mess because this API guarantees that we will 00243 * never return having left a canceled IRP in the queue. 00244 */ 00245 00246 /* Step 1: Queue the IRP */ 00247 if(Csq->Type == IO_TYPE_CSQ) 00248 Csq->CsqInsertIrp(Csq, Irp); 00249 else 00250 { 00251 PIO_CSQ_INSERT_IRP_EX pCsqInsertIrpEx = (PIO_CSQ_INSERT_IRP_EX)Csq->CsqInsertIrp; 00252 Retval = pCsqInsertIrpEx(Csq, Irp, InsertContext); 00253 if(Retval != STATUS_SUCCESS) 00254 break; 00255 } 00256 00257 /* Step 2: Set our cancel routine */ 00258 (void)IoSetCancelRoutine(Irp, IopCsqCancelRoutine); 00259 00260 /* Step 3: Deal with an IRP that is already canceled */ 00261 if(!Irp->Cancel) 00262 break; 00263 00264 /* 00265 * Since we're canceled, see if our cancel routine is already running 00266 * If this is NULL, the IO Manager has already called our cancel routine 00267 */ 00268 if(!IoSetCancelRoutine(Irp, NULL)) 00269 break; 00270 00271 /* OK, looks like we have to de-queue and complete this ourselves */ 00272 Csq->CsqRemoveIrp(Csq, Irp); 00273 Csq->CsqCompleteCanceledIrp(Csq, Irp); 00274 00275 if(Context) 00276 Context->Irp = NULL; 00277 } 00278 while(0); 00279 00280 Csq->CsqReleaseLock(Csq, Irql); 00281 00282 return Retval; 00283 } 00284 00285 00286 PIRP NTAPI IoCsqRemoveIrp(PIO_CSQ Csq, 00287 PIO_CSQ_IRP_CONTEXT Context) 00288 /* 00289 * FUNCTION: Remove anb IRP from the queue 00290 * ARGUMENTS: 00291 * Csq: Queue to remove the IRP from 00292 * Context: Context record containing the IRP to be dequeued 00293 * RETURNS: 00294 * - Pointer to an IRP if we found it 00295 * NOTES: 00296 * - Don't forget that we can be canceled any time up to the point 00297 * where we unset our cancel routine 00298 */ 00299 { 00300 KIRQL Irql; 00301 PIRP Irp = NULL; 00302 00303 Csq->CsqAcquireLock(Csq, &Irql); 00304 00305 do 00306 { 00307 /* It's possible that this IRP could have been canceled */ 00308 Irp = Context->Irp; 00309 00310 if(!Irp) 00311 break; 00312 00313 /* Unset the cancel routine and see if it has already been canceled */ 00314 if(!IoSetCancelRoutine(Irp, NULL)) 00315 { 00316 /* 00317 * already gone, return NULL --> NOTE that we cannot touch this IRP *or* the context, 00318 * since the context is being simultaneously twiddled by the cancel routine 00319 */ 00320 Irp = NULL; 00321 break; 00322 } 00323 00324 /* This IRP is valid and is ours. Dequeue it, fix it up, and return */ 00325 Csq->CsqRemoveIrp(Csq, Irp); 00326 00327 Context = (PIO_CSQ_IRP_CONTEXT)InterlockedExchangePointer(&Irp->Tail.Overlay.DriverContext[3], NULL); 00328 00329 if(Context && Context->Type == IO_TYPE_CSQ_IRP_CONTEXT) 00330 Context->Irp = NULL; 00331 } 00332 while(0); 00333 00334 Csq->CsqReleaseLock(Csq, Irql); 00335 00336 return Irp; 00337 } 00338 00339 PIRP NTAPI IoCsqRemoveNextIrp(PIO_CSQ Csq, 00340 PVOID PeekContext) 00341 /* 00342 * FUNCTION: IoCsqRemoveNextIrp - Removes the next IRP from the queue 00343 * ARGUMENTS: 00344 * Csq: Queue to remove the IRP from 00345 * PeekContext: Identifier of the IRP to be removed 00346 * RETURNS: 00347 * Pointer to the IRP that was removed, or NULL if one 00348 * could not be found 00349 * NOTES: 00350 * - This function is sensitive to yet another race condition. 00351 * The basic idea is that we have to return the first IRP that 00352 * we get that matches the PeekContext >that is not already canceled<. 00353 * Therefore, we have to do a trick similar to the one done in Insert 00354 * above. 00355 */ 00356 { 00357 KIRQL Irql; 00358 PIRP Irp = NULL; 00359 PIO_CSQ_IRP_CONTEXT Context; 00360 00361 Csq->CsqAcquireLock(Csq, &Irql); 00362 00363 while((Irp = Csq->CsqPeekNextIrp(Csq, Irp, PeekContext))) 00364 { 00365 /* 00366 * If the cancel routine is gone, we're already canceled, 00367 * and are spinning on the queue lock in our own cancel 00368 * routine. Move on to the next candidate. It'll get 00369 * removed by the cance routine. 00370 */ 00371 if(!IoSetCancelRoutine(Irp, NULL)) 00372 continue; 00373 00374 Csq->CsqRemoveIrp(Csq, Irp); 00375 00376 /* Unset the context stuff and return */ 00377 Context = (PIO_CSQ_IRP_CONTEXT)InterlockedExchangePointer(&Irp->Tail.Overlay.DriverContext[3], NULL); 00378 00379 if(Context && Context->Type == IO_TYPE_CSQ_IRP_CONTEXT) 00380 Context->Irp = NULL; 00381 00382 break; 00383 } 00384 00385 Csq->CsqReleaseLock(Csq, Irql); 00386 00387 return Irp; 00388 } 00389 Generated on Sat May 26 2012 04:34:53 for ReactOS by
1.7.6.1
|