/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    StackOvf.c

Abstract:

    The file lock package provides a worker thread to handle
    stack overflow conditions in the file systems.  When the
    file system detects that it is near the end of its stack
    during a paging I/O read request it will post the request
    to this extra thread.

--*/

#include "FsRtlP.h"
//
// Queue object that is used to hold work queue entries and synchronize
// worker thread activity.
//

KQUEUE FsRtlWorkerQueues[2];

//
//  Define a tag for general pool allocations from this module
//

#undef MODULE_POOL_TAG
#define MODULE_POOL_TAG                  ('srSF')


//
//  Local Support Routine
//

VOID
FsRtlStackOverflowRead (
    IN PVOID Context
    );

#if defined(_AMD64_)

VOID
FsRtlpNopStackOverflowRoutine (
    IN PVOID Context,
    IN PKEVENT Event
    );

#endif

VOID
FsRtlpPostStackOverflow (
    IN PVOID Context,
    IN PKEVENT Event,
    IN PFSRTL_STACK_OVERFLOW_ROUTINE StackOverflowRoutine,
    IN BOOLEAN PagingFile
    );

//
// Procedure prototype for the worker thread.
//

VOID
FsRtlWorkerThread(
    IN PVOID StartContext
    );

//
//  The following type is used to store an enqueue work item
//

typedef struct _STACK_OVERFLOW_ITEM {

    WORK_QUEUE_ITEM Item;

    //
    //  This is the call back routine
    //

    PFSRTL_STACK_OVERFLOW_ROUTINE StackOverflowRoutine;

    //
    //  Here are the parameters for the call back routine
    //

    PVOID Context;
    PKEVENT Event;

} STACK_OVERFLOW_ITEM;
typedef STACK_OVERFLOW_ITEM *PSTACK_OVERFLOW_ITEM;

KEVENT StackOverflowFallbackSerialEvent;
STACK_OVERFLOW_ITEM StackOverflowFallback;

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, FsRtlInitializeWorkerThread)
#endif


NTSTATUS
FsRtlInitializeWorkerThread (
    VOID
    )
{
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE Thread;
    ULONG i;
    NTSTATUS Status = STATUS_SUCCESS;

    //
    // Create worker threads to handle normal and paging overflow reads.
    //

    InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);

    for (i=0; i < 2; i++) {

        //
        // Initialize the FsRtl stack overflow work Queue objects.
        //

        KeInitializeQueue(&FsRtlWorkerQueues[i], 0);

        Status = PsCreateSystemThread( &Thread,
                                       THREAD_ALL_ACCESS,
                                       &ObjectAttributes,
                                       0L,
                                       NULL,
                                       FsRtlWorkerThread,
                                       ULongToPtr( i ));
        
        if (!NT_SUCCESS( Status )) {

            return Status;
        }

        ZwClose( Thread );
    }

    //
    //  Initialize the serial workitem so we can guarantee to post items
    //  for paging files to the worker threads.
    //

    KeInitializeEvent( &StackOverflowFallbackSerialEvent, SynchronizationEvent, TRUE );

    //
    // Pass a NOP routine through FsRtlPostStackOverfow() in order to flush out
    // any prefetchw instructions that may lie in that path.  Otherwise we'll
    // try to patch the prefetchw instruction when we can ill afford the stack
    // space.
    // 

#if defined(_AMD64_)

    FsRtlPostStackOverflow( NULL, NULL, FsRtlpNopStackOverflowRoutine );
    FsRtlPostPagingFileStackOverflow( NULL, NULL, FsRtlpNopStackOverflowRoutine );

#endif

    return Status;
}

DECLSPEC_NOINLINE
VOID
FsRtlPostStackOverflow (
    IN PVOID Context,
    IN PKEVENT Event,
    IN PFSRTL_STACK_OVERFLOW_ROUTINE StackOverflowRoutine
    )

/*++

Routine Description:

    This routines posts a stack overflow item to the stack overflow
    thread and returns.

Arguments:

    Context - Supplies the context to pass to the stack overflow
        call back routine.  If the low order bit is set, then
        this overflow was a read to a paging file.

    Event - Supplies a pointer to an event to pass to the stack
        overflow call back routine.

    StackOverflowRoutine - Supplies the call back to use when
        processing the request in the overflow thread.

Return Value:

    None.

--*/

{
    FsRtlpPostStackOverflow( Context, Event, StackOverflowRoutine, FALSE );
    return;
}


DECLSPEC_NOINLINE
VOID
FsRtlPostPagingFileStackOverflow (
    IN PVOID Context,
    IN PKEVENT Event,
    IN PFSRTL_STACK_OVERFLOW_ROUTINE StackOverflowRoutine
    )

/*++

Routine Description:

    This routines posts a stack overflow item to the stack overflow
    thread and returns.
    
Arguments:

    Context - Supplies the context to pass to the stack overflow
        call back routine.  If the low order bit is set, then
        this overflow was a read to a paging file.

    Event - Supplies a pointer to an event to pass to the stack
        overflow call back routine.

    StackOverflowRoutine - Supplies the call back to use when
        processing the request in the overflow thread.

Return Value:

    None.

--*/

{
    FsRtlpPostStackOverflow( Context, Event, StackOverflowRoutine, TRUE );
    return;
}


VOID
FsRtlpPostStackOverflow (
    IN PVOID Context,
    IN PKEVENT Event,
    IN PFSRTL_STACK_OVERFLOW_ROUTINE StackOverflowRoutine,
    IN BOOLEAN PagingFile
    )

/*++

Routine Description:

    This routines posts a stack overflow item to the stack overflow
    thread and returns.

Arguments:

    Context - Supplies the context to pass to the stack overflow
        call back routine.  If the low order bit is set, then
        this overflow was a read to a paging file.

    Event - Supplies a pointer to an event to pass to the stack
        overflow call back routine.

    StackOverflowRoutine - Supplies the call back to use when
        processing the request in the overflow thread.

    PagingFile - Indicates if the read is destined to a paging file.

Return Value:

    None.

--*/

{
    PSTACK_OVERFLOW_ITEM StackOverflowItem;

    //
    //  Allocate a stack overflow work item it will later be deallocated by
    //  the stack overflow thread.  Conserve stack by raising here.
    //

    StackOverflowItem = ExAllocatePoolWithTag( NonPagedPool,
                                               sizeof(STACK_OVERFLOW_ITEM),
                                               MODULE_POOL_TAG );

    //
    //  If this fails, go to the fallback item for the paging file overflows.
    //  We can't have a single fallback item for non-pagingfile IO since this
    //  could lead to deadlocks if it waits on a thread that itself needs
    //  the fallback item.
    //
    
    if (StackOverflowItem == NULL) {
        
        if (!PagingFile) {
        
            ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
        }

        KeWaitForSingleObject( &StackOverflowFallbackSerialEvent,
                               Executive,
                               KernelMode,
                               FALSE,
                               NULL );

        StackOverflowItem = &StackOverflowFallback;
    }
    
    //
    //  Fill in the fields in the new item
    //

    StackOverflowItem->Context              = Context;
    StackOverflowItem->Event                = Event;
    StackOverflowItem->StackOverflowRoutine = StackOverflowRoutine;

    ExInitializeWorkItem( &StackOverflowItem->Item,
                          &FsRtlStackOverflowRead,
                          StackOverflowItem );

    //
    //  Safely add it to the overflow queue
    //

    KeInsertQueue( &FsRtlWorkerQueues[PagingFile],
                   &StackOverflowItem->Item.List );

    //
    //  And return to our caller
    //

    return;
}


//
//  Local Support Routine
//

VOID
FsRtlStackOverflowRead (
    IN PVOID Context
    )

/*++

Routine Description:

    This routine processes all of the stack overflow request posted by
    the various file systems

Arguments:

Return Value:

    None.

--*/

{
    PSTACK_OVERFLOW_ITEM StackOverflowItem;

    //
    //  Since stack overflow reads are always recursive, set the
    //  TopLevelIrp field appropriately so that recursive reads
    //  from this point will not think they are top level.
    //

    PsGetCurrentThread()->TopLevelIrp = FSRTL_FSP_TOP_LEVEL_IRP;

    //
    //  Get a pointer to the stack overflow item and then call
    //  the callback routine to do the work
    //

    StackOverflowItem = (PSTACK_OVERFLOW_ITEM)Context;

    (StackOverflowItem->StackOverflowRoutine)(StackOverflowItem->Context,
                                              StackOverflowItem->Event);

    //
    //  Deallocate the work item, or simply return the serial item.
    //
    
    if (StackOverflowItem == &StackOverflowFallback) {

        KeSetEvent( &StackOverflowFallbackSerialEvent, 0, FALSE );
    
    } else {
        
        ExFreePool( StackOverflowItem );
    }

    PsGetCurrentThread()->TopLevelIrp = (ULONG_PTR)NULL;
}

VOID
FsRtlWorkerThread(
    IN PVOID StartContext
    )

{
    PLIST_ENTRY Entry;
    PWORK_QUEUE_ITEM WorkItem;
    ULONG PagingFile = (ULONG)(ULONG_PTR)StartContext;

    //
    //  Set our priority to low realtime, or +1 for PagingFile.
    //

    (VOID)KeSetPriorityThread( &PsGetCurrentThread()->Tcb,
                               LOW_REALTIME_PRIORITY + PagingFile );

    //
    // Loop forever waiting for a work queue item, calling the processing
    // routine, and then waiting for another work queue item.
    //

    do {

        //
        // Wait until something is put in the queue.
        //
        // By specifying a wait mode of KernelMode, the thread's kernel stack is
        // NOT swappable
        //

        Entry = KeRemoveQueue(&FsRtlWorkerQueues[PagingFile], KernelMode, NULL);
        WorkItem = CONTAINING_RECORD(Entry, WORK_QUEUE_ITEM, List);

        //
        // Execute the specified routine.
        //

        (WorkItem->WorkerRoutine)(WorkItem->Parameter);
        if (KeGetCurrentIrql() != 0) {
            KeBugCheckEx(
                IRQL_NOT_LESS_OR_EQUAL,
                (ULONG_PTR)WorkItem->WorkerRoutine,
                (ULONG_PTR)KeGetCurrentIrql(),
                (ULONG_PTR)WorkItem->WorkerRoutine,
                (ULONG_PTR)WorkItem
                );
        }

    } while(TRUE);
}

#if defined(_AMD64_)

VOID
FsRtlpNopStackOverflowRoutine (
    IN PVOID Context,
    IN PKEVENT Event
    )

/*++

Routine Description:

    This routine serves as a "dummy" stack overflow routine for the initial
    "prefetchw flushing" invocation of FsRtlPostStackOverflow().

Arguments:

    Context - Provides the context associated with the stack overflow condition.
              Not used.

    Event - Provides the event associated with the stack overflow condition.
            Not used.

Return Value:

    None.

--*/

{
    UNREFERENCED_PARAMETER(Context);
    UNREFERENCED_PARAMETER(Event);

    return;
}

#endif

