/*++ Copyright (c) 1989 Microsoft Corporation Module Name: queue.c Abstract: This module implements IRP queue processing routines for ws2ifsl.sys driver. Author: Vadim Eydelman (VadimE) Dec-1996 Revision History: Vadim Eydelman (VadimE) Oct-1997, rewrite to properly handle IRP cancellation --*/ #include "precomp.h" // // Private prototypes // VOID QueueKernelRoutine ( IN struct _KAPC *Apc, IN OUT PKNORMAL_ROUTINE *NormalRoutine, IN OUT PVOID *NormalContext, IN OUT PVOID *SystemArgument1, IN OUT PVOID *SystemArgument2 ); VOID SignalRequest ( IN PIFSL_PROCESS_CTX ProcessCtx ); VOID RequestRundownRoutine ( IN struct _KAPC *Apc ); VOID FlushRequestQueue ( PIFSL_QUEUE Queue ); VOID QueuedCancelRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); VOID SignalCancel ( IN PIFSL_PROCESS_CTX ProcessCtx ); VOID CancelRundownRoutine ( IN struct _KAPC *Apc ); VOID FlushCancelQueue ( PIFSL_QUEUE Queue ); #pragma alloc_text(PAGE, InitializeRequestQueue) #pragma alloc_text(PAGE, InitializeCancelQueue) VOID InitializeRequestQueue ( IN PIFSL_PROCESS_CTX ProcessCtx, IN PKTHREAD ApcThread, IN KPROCESSOR_MODE ApcMode, IN PKNORMAL_ROUTINE ApcRoutine, IN PVOID ApcContext ) /*++ Routine Description: Initializes request queue object Arguments: ProcessCtx - process context to which queue belongs ApcThread - thread to which to queue APC requests for processing ApcMode - mode of the caller (should be user) ApcRoutine - routine that processes requests ApcContext - context to pass to the routine in addition to request parameters Return Value: None --*/ { PAGED_CODE (); InitializeListHead (&ProcessCtx->RequestQueue.ListHead); ProcessCtx->RequestQueue.Busy = FALSE; KeInitializeSpinLock (&ProcessCtx->RequestQueue.Lock); KeInitializeApc (&ProcessCtx->RequestQueue.Apc, ApcThread, OriginalApcEnvironment, QueueKernelRoutine, RequestRundownRoutine, ApcRoutine, ApcMode, ApcContext); } BOOLEAN QueueRequest ( IN PIFSL_PROCESS_CTX ProcessCtx, IN PIRP Irp ) /*++ Routine Description: Queues IRP to IFSL request queue and signals to user mode DLL to start processing if it is not busy already. Arguments: ProcessCtx - process context in which to queue Irp - request to be queued Return Value: TRUE - IRP was queued FALSE - IRP was already cancelled --*/ { BOOLEAN res; KIRQL oldIRQL; PIFSL_QUEUE queue = &ProcessCtx->RequestQueue; IoSetCancelRoutine (Irp, QueuedCancelRoutine); KeAcquireSpinLock (&queue->Lock, &oldIRQL); if (!Irp->Cancel) { // // Request is not cancelled, insert it into the queue // InsertTailList (&queue->ListHead, &Irp->Tail.Overlay.ListEntry); Irp->Tail.Overlay.IfslRequestQueue = queue; // // If queue wasn't busy, signal to user mode DLL to pick up new request // if (!queue->Busy) { ASSERT (queue->ListHead.Flink==&Irp->Tail.Overlay.ListEntry); SignalRequest (ProcessCtx); ASSERT (queue->Busy); } res = TRUE; } else { res = FALSE; } KeReleaseSpinLock (&queue->Lock, oldIRQL); return res; } // QueueRequest PIRP DequeueRequest ( PIFSL_PROCESS_CTX ProcessCtx, ULONG UniqueId, BOOLEAN *more ) /*++ Routine Description: Removes IRP from IFSL request queue. Arguments: ProcessCtx - process context from which to remove UniqueId - unique request id more - set to TRUE if there are more requests in the queue Return Value: IRP - pointer to the IRP NULL - the request was not in the queue --*/ { KIRQL oldIRQL; PIRP irp; PIFSL_QUEUE queue = &ProcessCtx->RequestQueue; KeAcquireSpinLock (&queue->Lock, &oldIRQL); irp = CONTAINING_RECORD (queue->ListHead.Flink, IRP, Tail.Overlay.ListEntry); if (!IsListEmpty (&queue->ListHead) && (irp->Tail.Overlay.IfslRequestId==UlongToPtr(UniqueId))) { // // Queue is not empty and first request matches passed in parameters, // dequeue and return it // ASSERT (queue->Busy); RemoveEntryList (&irp->Tail.Overlay.ListEntry); irp->Tail.Overlay.IfslRequestQueue = NULL; } else { irp = NULL; } if (IsListEmpty (&queue->ListHead)) { // // Queue is now empty, change its state, so that new request knows to // signal to user mode DLL // queue->Busy = FALSE; } else { // // There is another request pending, signal it now // SignalRequest (ProcessCtx); ASSERT (queue->Busy); } // // Hint the caller that we just signalled, so it does not have to wait on event // *more = queue->Busy; KeReleaseSpinLock (&queue->Lock, oldIRQL); return irp; } VOID SignalRequest ( IN PIFSL_PROCESS_CTX ProcessCtx ) /*++ Routine Description: Fills request parameters & signals user mode DLL to process the request Arguments: ProcessCtx - our context for the process which IRP belongs to Return Value: None Note: SHOULD ONLY BE CALLED WITH QUEUE SPINLOCK HELD --*/ { PIRP irp; PIO_STACK_LOCATION irpSp; ULONG bufferLen; ASSERT (!IsListEmpty (&ProcessCtx->RequestQueue.ListHead)); irp = CONTAINING_RECORD ( ProcessCtx->RequestQueue.ListHead.Flink, IRP, Tail.Overlay.ListEntry ); irpSp = IoGetCurrentIrpStackLocation (irp); switch (irpSp->MajorFunction) { case IRP_MJ_READ: bufferLen = irpSp->Parameters.Read.Length;; break; case IRP_MJ_WRITE: bufferLen = irpSp->Parameters.Write.Length; break; case IRP_MJ_DEVICE_CONTROL: switch (irpSp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_AFD_RECEIVE_DATAGRAM: bufferLen = ADDR_ALIGN(irpSp->Parameters.DeviceIoControl.OutputBufferLength) + irpSp->Parameters.DeviceIoControl.InputBufferLength; break; case IOCTL_AFD_RECEIVE: bufferLen = irpSp->Parameters.DeviceIoControl.OutputBufferLength; break; case IOCTL_AFD_SEND_DATAGRAM: bufferLen = ADDR_ALIGN(irpSp->Parameters.DeviceIoControl.OutputBufferLength) + irpSp->Parameters.DeviceIoControl.InputBufferLength; break; } break; case IRP_MJ_PNP: bufferLen = sizeof (HANDLE); break; default: ASSERT (FALSE); break; } if (KeInsertQueueApc (&ProcessCtx->RequestQueue.Apc, irp->Tail.Overlay.IfslRequestId, UlongToPtr(bufferLen), IO_NETWORK_INCREMENT)) { WsProcessPrint (ProcessCtx, DBG_QUEUE, ("WS2IFSL-%04lx SignalRequest: Irp %p (id %ld) on socket %p.\n", PsGetCurrentProcessId(), irp, irp->Tail.Overlay.IfslRequestId, irpSp->FileObject)); ProcessCtx->RequestQueue.Busy = TRUE; } else { WsProcessPrint (ProcessCtx, DBG_QUEUE|DBG_FAILURES, ("WS2IFSL-%04lx KeInsertQueueApc failed: Irp %p (id %ld) on socket %p.\n", PsGetCurrentProcessId(), irp, irp->Tail.Overlay.IfslRequestId, irpSp->FileObject)); // // APC queing failed, cancel all outstanding requests. // FlushRequestQueue (&ProcessCtx->RequestQueue); } } // SignalRequest VOID QueueKernelRoutine ( IN struct _KAPC *Apc, IN OUT PKNORMAL_ROUTINE *NormalRoutine, IN OUT PVOID *NormalContext, IN OUT PVOID *SystemArgument1, IN OUT PVOID *SystemArgument2 ) { NOTHING; } VOID RequestRundownRoutine ( IN struct _KAPC *Apc ) /*++ Routine Description: APC rundown routine for request queue APC Flushes the queue and marks it as not busy so new request fail immediately as well. Arguments: APC - cancel queue APC structure Return Value: None --*/ { PIFSL_QUEUE Queue; KIRQL oldIrql; Queue = CONTAINING_RECORD (Apc, IFSL_QUEUE, Apc); KeAcquireSpinLock (&Queue->Lock, &oldIrql); Queue->Busy = FALSE; FlushRequestQueue (Queue); KeReleaseSpinLock (&Queue->Lock, oldIrql); } VOID FlushRequestQueue ( PIFSL_QUEUE Queue ) /*++ Routine Description: Flushes and completes IRPs in the request queue Arguments: Queue - request queue to flush Return Value: None Note: SHOULD ONLY BE CALLED WITH QUEUE SPINLOCK HELD --*/ { while (!IsListEmpty (&Queue->ListHead)) { PIRP irp = CONTAINING_RECORD (Queue->ListHead.Flink, IRP, Tail.Overlay.ListEntry); RemoveEntryList (&irp->Tail.Overlay.ListEntry); irp->Tail.Overlay.IfslRequestQueue = NULL; KeReleaseSpinLockFromDpcLevel (&Queue->Lock); irp->IoStatus.Information = 0; irp->IoStatus.Status = STATUS_CANCELLED; CompleteSocketIrp (irp); KeAcquireSpinLockAtDpcLevel (&Queue->Lock); } } VOID CleanupQueuedRequests ( IN PIFSL_PROCESS_CTX ProcessCtx, IN PFILE_OBJECT SocketFile, OUT PLIST_ENTRY IrpList ) /*++ Routine Description: Cleans up all IRPs associated with a socket file object from the request queue Arguments: ProcessCtx - process context to which queue belongs SocketFile - socket file object for which to remove requests IrpList - list head to hold the IRPs removed from the queue Return Value: None --*/ { KIRQL oldIRQL; PLIST_ENTRY entry; PIFSL_QUEUE queue = &ProcessCtx->RequestQueue; KeAcquireSpinLock (&queue->Lock, &oldIRQL); entry = queue->ListHead.Flink; while (entry!=&queue->ListHead) { PIRP irp = CONTAINING_RECORD (entry, IRP, Tail.Overlay.ListEntry); PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation (irp); entry = entry->Flink; if (irpSp->FileObject==SocketFile) { RemoveEntryList (&irp->Tail.Overlay.ListEntry); irp->Tail.Overlay.IfslRequestQueue = NULL; InsertTailList (IrpList, &irp->Tail.Overlay.ListEntry); } } KeReleaseSpinLock (&queue->Lock, oldIRQL); } VOID QueuedCancelRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: Driver cancel routine for socket request waiting in the queue to be reported to user mode DLL. Arguments: DeviceObject - WS2IFSL device object Irp - Irp to be cancelled Return Value: None --*/ { PIO_STACK_LOCATION irpSp; PIFSL_SOCKET_CTX SocketCtx; PIFSL_PROCESS_CTX ProcessCtx; irpSp = IoGetCurrentIrpStackLocation (Irp); SocketCtx = irpSp->FileObject->FsContext; ProcessCtx = SocketCtx->ProcessRef->FsContext; WsProcessPrint (ProcessCtx, DBG_QUEUE, ("WS2IFSL-%04lx CancelQueuedRequest: Socket %p , Irp %p\n", PsGetCurrentProcessId(), irpSp->FileObject, Irp)); KeAcquireSpinLockAtDpcLevel (&ProcessCtx->RequestQueue.Lock); if (Irp->Tail.Overlay.IfslRequestQueue!=NULL) { ASSERT (Irp->Tail.Overlay.IfslRequestQueue==&ProcessCtx->RequestQueue); // // Request was in the queue, remove and cancel it here // RemoveEntryList (&Irp->Tail.Overlay.ListEntry); Irp->Tail.Overlay.IfslRequestQueue = NULL; KeReleaseSpinLockFromDpcLevel (&ProcessCtx->RequestQueue.Lock); IoReleaseCancelSpinLock (Irp->CancelIrql); Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_CANCELLED; CompleteSocketIrp (Irp); } else { // // Request was not in the queue, whoever removed should note the // cancel flag and properly deal with it // KeReleaseSpinLockFromDpcLevel (&ProcessCtx->RequestQueue.Lock); IoReleaseCancelSpinLock (Irp->CancelIrql); // // Don't touch IRP after this as we do not own it anymore // } } VOID InitializeCancelQueue ( IN PIFSL_PROCESS_CTX ProcessCtx, IN PKTHREAD ApcThread, IN KPROCESSOR_MODE ApcMode, IN PKNORMAL_ROUTINE ApcRoutine, IN PVOID ApcContext ) /*++ Routine Description: Initializes cancel queue object Arguments: ProcessCtx - process context to which queue belongs ApcThread - thread to which to queue APC requests for processing ApcMode - mode of the caller (should be user) ApcRoutine - routine that processes requests ApcContext - context to pass to the routine in addition to request parameters Return Value: None --*/ { PAGED_CODE (); InitializeListHead (&ProcessCtx->CancelQueue.ListHead); ProcessCtx->CancelQueue.Busy = FALSE; KeInitializeSpinLock (&ProcessCtx->CancelQueue.Lock); KeInitializeApc (&ProcessCtx->CancelQueue.Apc, ApcThread, OriginalApcEnvironment, QueueKernelRoutine, CancelRundownRoutine, ApcRoutine, ApcMode, ApcContext); } VOID QueueCancel ( IN PIFSL_PROCESS_CTX ProcessCtx, IN PIFSL_CANCEL_CTX CancelCtx ) /*++ Routine Description: Queues cancel request to IFSL cancel queue and signals to user mode DLL to start processing if it is not busy already. Arguments: ProcessCtx - process context in which to queue CancelCtx - request to be queued Return Value: None --*/ { KIRQL oldIRQL; PIFSL_QUEUE queue = &ProcessCtx->CancelQueue; KeAcquireSpinLock (&queue->Lock, &oldIRQL); InsertTailList (&queue->ListHead, &CancelCtx->ListEntry); ASSERT (CancelCtx->ListEntry.Flink != NULL); if (!queue->Busy) { ASSERT (queue->ListHead.Flink==&CancelCtx->ListEntry); SignalCancel (ProcessCtx); ASSERT (ProcessCtx->CancelQueue.Busy); } KeReleaseSpinLock (&queue->Lock, oldIRQL); } // QueueCancel PIFSL_CANCEL_CTX DequeueCancel ( PIFSL_PROCESS_CTX ProcessCtx, ULONG UniqueId, BOOLEAN *more ) /*++ Routine Description: Removes cancel request from IFSL cancel queue. Arguments: ProcessCtx - process context from which to remove UniqueId - unique cancel request id more - set to TRUE if there are more requests in the queue Return Value: CTX - pointer to cancel request context NULL - the request was not in the queue --*/ { KIRQL oldIRQL; PIFSL_CANCEL_CTX cancelCtx; PIFSL_QUEUE queue = &ProcessCtx->CancelQueue; KeAcquireSpinLock (&queue->Lock, &oldIRQL); cancelCtx = CONTAINING_RECORD ( queue->ListHead.Flink, IFSL_CANCEL_CTX, ListEntry ); if (!IsListEmpty (&queue->ListHead) && (cancelCtx->UniqueId==UniqueId)) { // // Queue is not empty and first request matches passed in parameters, // dequeue and return it // ASSERT (queue->Busy); RemoveEntryList (&cancelCtx->ListEntry); cancelCtx->ListEntry.Flink = NULL; } else cancelCtx = NULL; if (IsListEmpty (&queue->ListHead)) { // // Queue is now empty, change its state, so that new request knows to // signal to user mode DLL // queue->Busy = FALSE; } else { // // There is another request pending, signal it now // SignalCancel (ProcessCtx); ASSERT (queue->Busy); } // // Hint the caller that we just signalled, so it does not have to wait on event // *more = queue->Busy; KeReleaseSpinLock (&queue->Lock, oldIRQL); return cancelCtx; } VOID SignalCancel ( IN PIFSL_PROCESS_CTX ProcessCtx ) /*++ Routine Description: Fills request parameters & signals user mode DLL to process the request Arguments: ProcessCtx - our context for the process which cancel request belongs to Return Value: None Note: SHOULD ONLY BE CALLED WITH QUEUE SPINLOCK HELD --*/ { PIFSL_CANCEL_CTX cancelCtx; PIFSL_SOCKET_CTX SocketCtx; ASSERT (!IsListEmpty (&ProcessCtx->CancelQueue.ListHead)); ProcessCtx->CancelQueue.Busy = TRUE; cancelCtx = CONTAINING_RECORD ( ProcessCtx->CancelQueue.ListHead.Flink, IFSL_CANCEL_CTX, ListEntry ); SocketCtx = cancelCtx->SocketFile->FsContext; if (KeInsertQueueApc (&ProcessCtx->CancelQueue.Apc, UlongToPtr(cancelCtx->UniqueId), SocketCtx->DllContext, IO_NETWORK_INCREMENT)) { WsProcessPrint (ProcessCtx, DBG_QUEUE, ("WS2IFSL-%04lx SignalCancel: Context %p on socket %p (h %p).\n", PsGetCurrentProcessId(), cancelCtx, cancelCtx->SocketFile, SocketCtx->DllContext)); } else { // // APC queing failed, cancel all outstanding requests. // FlushCancelQueue (&ProcessCtx->CancelQueue); } } // SignalCancel VOID FlushCancelQueue ( PIFSL_QUEUE Queue ) /*++ Routine Description: Flushes and frees entries in the cancel queue Arguments: Queue - request queue to flush Return Value: None Note: SHOULD ONLY BE CALLED WITH QUEUE SPINLOCK HELD --*/ { while (!IsListEmpty (&Queue->ListHead)) { PIFSL_CANCEL_CTX cancelCtx = CONTAINING_RECORD ( Queue->ListHead.Flink, IFSL_CANCEL_CTX, ListEntry ); RemoveEntryList (&cancelCtx->ListEntry); cancelCtx->ListEntry.Flink = NULL; FreeSocketCancel (cancelCtx); } } VOID CancelRundownRoutine ( IN struct _KAPC *Apc ) /*++ Routine Description: APC rundown routine for cancel queue APC Flushes the queue and marks it as not busy so new request fail immediately as well. Arguments: APC - cancel queue APC structure Return Value: None --*/ { PIFSL_QUEUE Queue; KIRQL oldIrql; Queue = CONTAINING_RECORD (Apc, IFSL_QUEUE, Apc); KeAcquireSpinLock (&Queue->Lock, &oldIrql); Queue->Busy = FALSE; FlushCancelQueue (Queue); KeReleaseSpinLock (&Queue->Lock, oldIrql); } BOOLEAN RemoveQueuedCancel ( PIFSL_PROCESS_CTX ProcessCtx, PIFSL_CANCEL_CTX CancelCtx ) /*++ Routine Description: Remove cancel request from the cancel queue if it is there Arguments: ProcessCtx - process context to which queue belongs CancelCtx - request to remove Return Value: None --*/ { KIRQL oldIRQL; BOOLEAN res; // Acquire queue lock KeAcquireSpinLock (&ProcessCtx->CancelQueue.Lock, &oldIRQL); res = (CancelCtx->ListEntry.Flink!=NULL); if (res) { RemoveEntryList (&CancelCtx->ListEntry); CancelCtx->ListEntry.Flink = NULL; } KeReleaseSpinLock (&ProcessCtx->CancelQueue.Lock, oldIRQL); return res; }