/** * @file sys/ioq.c * * @copyright 2015 Bill Zissimopoulos */ #include /* * Overview * * An FSP_IOQ encapsulates the main FSP mechanism for handling IRP's. * It has two queues: a "Pending" queue for managing newly arrived IRP's * and a "Processing" queue for managing IRP's currently being processed * (i.e. sent to the user-mode file system for further processing). * * IRP's arrive at a MajorFunction (MJ) and are then posted to the device's * FSP_IOQ and marked pending. When the user-mode file system performs * FSP_FSCTL_TRANSACT, the IRP's are removed from the Pending queue and * are then marshalled to the user process; prior to that they are added * to the Processing queue. At a later time the user-mode will perform * another FSP_FSCTL_TRANSACT at which time any processed IRP's will be * marshalled back to us and will be then removed from the Processing queue * and completed. * * * IRP State Diagram * +--------------------+ * | | | StartProcessingIrp * v | v * +------------+ | +------------+ * | MJ | | | Processing | * +------------+ | +------------+ * | | | * | PostIrp | | EndProcessingIrp * v | v * +------------+ | +------------+ * | Pending | | | TRANSACT | * +------------+ | | IN | * | | +------------+ * | NextPendingIrp | | * v | | CompleteRequest * +------------+ | v * | TRANSACT | | +------------+ * | OUT | | | Completed | * +------------+ | +------------+ * | | * +---------------------+ * * * Event Object * * The FSP_IOQ includes a manual event object. The event object becomes * signaled when the FSP_IOQ object is stopped or for as long as the Pending * queue is not empty. */ /* * FSP_IOQ_PROCESS_NO_CANCEL * * Define this macro to disallow cancelation (other than the FSP_IOQ being stopped) * after an IRP has enter the Processing phase. * * Once a file-system operation has been started its effects cannot be ignored. Even * if the process that originated the operation decides to cancel it, we must correctly * inform it of whether the operation was successful or not. We can only do this reliably * if we do not allow cancelation after an operation has been started. */ #define FSP_IOQ_PROCESS_NO_CANCEL #if defined(FSP_IOQ_PROCESS_NO_CANCEL) static NTSTATUS FspCsqInsertIrpEx(PIO_CSQ Csq, PIRP Irp, PIO_CSQ_IRP_CONTEXT Context, PVOID InsertContext) { /* * Modelled after IoCsqInsertIrpEx. Does NOT set a cancelation routine. */ NTSTATUS Result; KIRQL Irql; Irp->Tail.Overlay.DriverContext[3] = Csq; Csq->CsqAcquireLock(Csq, &Irql); Result = ((PIO_CSQ_INSERT_IRP_EX)Csq->CsqInsertIrp)(Csq, Irp, InsertContext); Csq->CsqReleaseLock(Csq, Irql); return Result; } static PIRP FspCsqRemoveNextIrp(PIO_CSQ Csq, PVOID PeekContext) { /* * Modelled after IoCsqRemoveNextIrp. Used with FspCsqInsertIrpEx. */ KIRQL Irql; PIRP Irp; Csq->CsqAcquireLock(Csq, &Irql); Irp = Csq->CsqPeekNextIrp(Csq, 0, PeekContext); if (0 != Irp) { Csq->CsqRemoveIrp(Csq, Irp); Irp->Tail.Overlay.DriverContext[3] = 0; } Csq->CsqReleaseLock(Csq, Irql); return Irp; } #else #define FspCsqInsertIrpEx(Q, I, U, C) IoCsqInsertIrpEx(Q, I, U, C) #define FspCsqRemoveNextIrp(Q, C) IoCsqRemoveNextIrp(Q, C) #endif #define InterruptTimeToSecFactor 10000000 #define ConvertInterruptTimeToSec(Time) ((ULONG)((Time) / InterruptTimeToSecFactor)) #define QueryInterruptTimeInSec() ConvertInterruptTimeToSec(KeQueryInterruptTime()) typedef struct { PVOID IrpHint; ULONG ExpirationTime; } FSP_IOQ_PEEK_CONTEXT; static NTSTATUS FspIoqPendingInsertIrpEx(PIO_CSQ IoCsq, PIRP Irp, PVOID InsertContext) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, PendingIoCsq); if (Ioq->Stopped) return STATUS_CANCELLED; if (!InsertContext && Ioq->PendingIrpCapacity <= Ioq->PendingIrpCount) return STATUS_INSUFFICIENT_RESOURCES; Ioq->PendingIrpCount++; InsertTailList(&Ioq->PendingIrpList, &Irp->Tail.Overlay.ListEntry); /* list is not empty; wake up any waiters */ KeSetEvent(&Ioq->PendingIrpEvent, 1, FALSE); return STATUS_SUCCESS; } static VOID FspIoqPendingRemoveIrp(PIO_CSQ IoCsq, PIRP Irp) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, PendingIoCsq); Ioq->PendingIrpCount--; if (RemoveEntryList(&Irp->Tail.Overlay.ListEntry) && !Ioq->Stopped) /* list is empty; future threads should go to sleep */ KeClearEvent(&Ioq->PendingIrpEvent); } static PIRP FspIoqPendingPeekNextIrp(PIO_CSQ IoCsq, PIRP Irp, PVOID PeekContext) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, PendingIoCsq); if (PeekContext && Ioq->Stopped) return 0; PLIST_ENTRY Head = &Ioq->PendingIrpList; PLIST_ENTRY Entry = 0 == Irp ? Head->Flink : Irp->Tail.Overlay.ListEntry.Flink; if (Head == Entry) return 0; Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.ListEntry); if (!PeekContext) return Irp; PVOID IrpHint = ((FSP_IOQ_PEEK_CONTEXT *)PeekContext)->IrpHint; if (0 == IrpHint) { ULONG ExpirationTime = ((FSP_IOQ_PEEK_CONTEXT *)PeekContext)->ExpirationTime; for (;;) { if (FspIrpTimestampInfinity != FspIrpTimestamp(Irp)) return FspIrpTimestamp(Irp) <= ExpirationTime ? Irp : 0; Entry = Entry->Flink; if (Head == Entry) return 0; Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.ListEntry); } } else { if (Irp == IrpHint) return 0; return Irp; } } _IRQL_raises_(DISPATCH_LEVEL) static VOID FspIoqPendingAcquireLock(PIO_CSQ IoCsq, _At_(*PIrql, _IRQL_saves_) PKIRQL PIrql) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, PendingIoCsq); KeAcquireSpinLock(&Ioq->SpinLock, PIrql); } _IRQL_requires_(DISPATCH_LEVEL) static VOID FspIoqPendingReleaseLock(PIO_CSQ IoCsq, _IRQL_restores_ KIRQL Irql) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, PendingIoCsq); KeReleaseSpinLock(&Ioq->SpinLock, Irql); } static VOID FspIoqPendingCompleteCanceledIrp(PIO_CSQ IoCsq, PIRP Irp) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, PendingIoCsq); Ioq->CompleteCanceledIrp(Irp); } static NTSTATUS FspIoqProcessInsertIrpEx(PIO_CSQ IoCsq, PIRP Irp, PVOID InsertContext) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, ProcessIoCsq); if (Ioq->Stopped) return STATUS_CANCELLED; InsertTailList(&Ioq->ProcessIrpList, &Irp->Tail.Overlay.ListEntry); ULONG Index = FspHashMixPointer(Irp) % Ioq->ProcessIrpBucketCount; #if DBG for (PIRP IrpX = Ioq->ProcessIrpBuckets[Index]; IrpX; IrpX = FspIrpDictNext(IrpX)) ASSERT(IrpX != Irp); #endif ASSERT(0 == FspIrpDictNext(Irp)); FspIrpDictNext(Irp) = Ioq->ProcessIrpBuckets[Index]; Ioq->ProcessIrpBuckets[Index] = Irp; return STATUS_SUCCESS; } static VOID FspIoqProcessRemoveIrp(PIO_CSQ IoCsq, PIRP Irp) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, ProcessIoCsq); ULONG Index = FspHashMixPointer(Irp) % Ioq->ProcessIrpBucketCount; for (PIRP *PIrp = (PIRP *)&Ioq->ProcessIrpBuckets[Index];; PIrp = &FspIrpDictNext(*PIrp)) { ASSERT(0 != *PIrp); if (*PIrp == Irp) { *PIrp = FspIrpDictNext(Irp); FspIrpDictNext(Irp) = 0; break; } } RemoveEntryList(&Irp->Tail.Overlay.ListEntry); } static PIRP FspIoqProcessPeekNextIrp(PIO_CSQ IoCsq, PIRP Irp, PVOID PeekContext) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, ProcessIoCsq); if (PeekContext && Ioq->Stopped) return 0; PLIST_ENTRY Head = &Ioq->ProcessIrpList; PLIST_ENTRY Entry = 0 == Irp ? Head->Flink : Irp->Tail.Overlay.ListEntry.Flink; if (Head == Entry) return 0; Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.ListEntry); if (!PeekContext) return Irp; PVOID IrpHint = ((FSP_IOQ_PEEK_CONTEXT *)PeekContext)->IrpHint; if (0 == IrpHint) { ULONG ExpirationTime = ((FSP_IOQ_PEEK_CONTEXT *)PeekContext)->ExpirationTime; for (;;) { if (FspIrpTimestampInfinity != FspIrpTimestamp(Irp)) return FspIrpTimestamp(Irp) <= ExpirationTime ? Irp : 0; Entry = Entry->Flink; if (Head == Entry) return 0; Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.ListEntry); } } else { ULONG Index = FspHashMixPointer(IrpHint) % Ioq->ProcessIrpBucketCount; for (Irp = Ioq->ProcessIrpBuckets[Index]; Irp; Irp = FspIrpDictNext(Irp)) if (Irp == IrpHint) return Irp; return 0; } } _IRQL_raises_(DISPATCH_LEVEL) static VOID FspIoqProcessAcquireLock(PIO_CSQ IoCsq, _At_(*PIrql, _IRQL_saves_) PKIRQL PIrql) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, ProcessIoCsq); KeAcquireSpinLock(&Ioq->SpinLock, PIrql); } _IRQL_requires_(DISPATCH_LEVEL) static VOID FspIoqProcessReleaseLock(PIO_CSQ IoCsq, _IRQL_restores_ KIRQL Irql) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, ProcessIoCsq); KeReleaseSpinLock(&Ioq->SpinLock, Irql); } static VOID FspIoqProcessCompleteCanceledIrp(PIO_CSQ IoCsq, PIRP Irp) { FSP_IOQ *Ioq = CONTAINING_RECORD(IoCsq, FSP_IOQ, ProcessIoCsq); Ioq->CompleteCanceledIrp(Irp); } NTSTATUS FspIoqCreate( ULONG IrpCapacity, PLARGE_INTEGER IrpTimeout, VOID (*CompleteCanceledIrp)(PIRP Irp), FSP_IOQ **PIoq) { ASSERT(0 != CompleteCanceledIrp); *PIoq = 0; FSP_IOQ *Ioq; ULONG BucketCount = (PAGE_SIZE - sizeof *Ioq) / sizeof Ioq->ProcessIrpBuckets[0]; Ioq = FspAllocNonPaged(PAGE_SIZE); if (0 == Ioq) return STATUS_INSUFFICIENT_RESOURCES; RtlZeroMemory(Ioq, PAGE_SIZE); KeInitializeSpinLock(&Ioq->SpinLock); KeInitializeEvent(&Ioq->PendingIrpEvent, NotificationEvent, FALSE); InitializeListHead(&Ioq->PendingIrpList); InitializeListHead(&Ioq->ProcessIrpList); IoCsqInitializeEx(&Ioq->PendingIoCsq, FspIoqPendingInsertIrpEx, FspIoqPendingRemoveIrp, FspIoqPendingPeekNextIrp, FspIoqPendingAcquireLock, FspIoqPendingReleaseLock, FspIoqPendingCompleteCanceledIrp); IoCsqInitializeEx(&Ioq->ProcessIoCsq, FspIoqProcessInsertIrpEx, FspIoqProcessRemoveIrp, FspIoqProcessPeekNextIrp, FspIoqProcessAcquireLock, FspIoqProcessReleaseLock, FspIoqProcessCompleteCanceledIrp); Ioq->IrpTimeout = ConvertInterruptTimeToSec(IrpTimeout->QuadPart + InterruptTimeToSecFactor - 1); /* convert to seconds (and round up) */ Ioq->PendingIrpCapacity = IrpCapacity; Ioq->CompleteCanceledIrp = CompleteCanceledIrp; Ioq->ProcessIrpBucketCount = BucketCount; *PIoq = Ioq; return STATUS_SUCCESS; } VOID FspIoqDelete(FSP_IOQ *Ioq) { FspIoqStop(Ioq); FspFree(Ioq); } VOID FspIoqStop(FSP_IOQ *Ioq) { KIRQL Irql; KeAcquireSpinLock(&Ioq->SpinLock, &Irql); Ioq->Stopped = TRUE; /* we are being stopped, permanently wake up waiters */ KeSetEvent(&Ioq->PendingIrpEvent, 1, FALSE); KeReleaseSpinLock(&Ioq->SpinLock, Irql); PIRP Irp; while (0 != (Irp = IoCsqRemoveNextIrp(&Ioq->PendingIoCsq, 0))) Ioq->CompleteCanceledIrp(Irp); while (0 != (Irp = FspCsqRemoveNextIrp(&Ioq->ProcessIoCsq, 0))) Ioq->CompleteCanceledIrp(Irp); } BOOLEAN FspIoqStopped(FSP_IOQ *Ioq) { BOOLEAN Result; KIRQL Irql; KeAcquireSpinLock(&Ioq->SpinLock, &Irql); Result = Ioq->Stopped; KeReleaseSpinLock(&Ioq->SpinLock, Irql); return Result; } VOID FspIoqRemoveExpired(FSP_IOQ *Ioq) { FSP_IOQ_PEEK_CONTEXT PeekContext; PeekContext.IrpHint = 0; PeekContext.ExpirationTime = QueryInterruptTimeInSec(); PIRP Irp; while (0 != (Irp = IoCsqRemoveNextIrp(&Ioq->PendingIoCsq, &PeekContext))) Ioq->CompleteCanceledIrp(Irp); #if !defined(FSP_IOQ_PROCESS_NO_CANCEL) while (0 != (Irp = FspCsqRemoveNextIrp(&Ioq->ProcessIoCsq, &PeekContext))) Ioq->CompleteCanceledIrp(Irp); #endif } BOOLEAN FspIoqPostIrpEx(FSP_IOQ *Ioq, PIRP Irp, BOOLEAN BestEffort, NTSTATUS *PResult) { NTSTATUS Result; FspIrpTimestamp(Irp) = BestEffort ? FspIrpTimestampInfinity : QueryInterruptTimeInSec() + Ioq->IrpTimeout; Result = IoCsqInsertIrpEx(&Ioq->PendingIoCsq, Irp, 0, (PVOID)BestEffort); if (NT_SUCCESS(Result)) return TRUE; if (0 != PResult) *PResult = Result; return FALSE; } PIRP FspIoqNextPendingIrp(FSP_IOQ *Ioq, PIRP BoundaryIrp, PLARGE_INTEGER Timeout) { /* timeout of 0 normally means infinite wait; for us it means do not do any wait at all! */ if (0 != Timeout) { NTSTATUS Result; Result = KeWaitForSingleObject(&Ioq->PendingIrpEvent, Executive, KernelMode, FALSE, Timeout); ASSERT(STATUS_SUCCESS == Result || STATUS_TIMEOUT == Result); if (STATUS_TIMEOUT == Result) return FspIoqTimeout; } FSP_IOQ_PEEK_CONTEXT PeekContext; PeekContext.IrpHint = 0 != BoundaryIrp ? BoundaryIrp : (PVOID)1; PeekContext.ExpirationTime = 0; return IoCsqRemoveNextIrp(&Ioq->PendingIoCsq, &PeekContext); } BOOLEAN FspIoqStartProcessingIrp(FSP_IOQ *Ioq, PIRP Irp) { NTSTATUS Result; #if defined(FSP_IOQ_PROCESS_NO_CANCEL) FspIrpTimestamp(Irp) = FspIrpTimestampInfinity; #else if (FspIrpTimestampMax != FspIrpTimestamp(Irp)) FspIrpTimestamp(Irp) = QueryInterruptTimeInSec() + Ioq->IrpTimeout; #endif Result = FspCsqInsertIrpEx(&Ioq->ProcessIoCsq, Irp, 0, 0); return NT_SUCCESS(Result); } PIRP FspIoqEndProcessingIrp(FSP_IOQ *Ioq, UINT_PTR IrpHint) { if (0 == IrpHint) return 0; FSP_IOQ_PEEK_CONTEXT PeekContext; PeekContext.IrpHint = (PVOID)IrpHint; PeekContext.ExpirationTime = 0; return FspCsqRemoveNextIrp(&Ioq->ProcessIoCsq, &PeekContext); }