//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1998 - 1999
//
//  File:       thread.c
//
//--------------------------------------------------------------------------

//
// This file contains functions associated with ParClass worker threads
//

#include "pch.h"

VOID
PptPdoThread(
    IN PVOID Context
    )

/*++

Routine Description:

    This is the parallel thread routine.  Loops performing I/O operations.

Arguments:

    Context -- Really the extension

Return Value:

    None

--*/

{
    
    PPDO_EXTENSION  pdx = Context;
    KIRQL           OldIrql;
    NTSTATUS        Status;
    LARGE_INTEGER   Timeout;
    PIRP            CurrentIrp;

    DD((PCE)pdx,DDT,"PptPdoThread - %s - enter\n",pdx->Location);

    do {

        Timeout = pdx->IdleTimeout;

        Status  = KeWaitForSingleObject( &pdx->RequestSemaphore, UserRequest, KernelMode, FALSE, &Timeout );
        
        if( Status == STATUS_TIMEOUT ) {

            if( pdx->P12843DL.bEventActive ) {

                // Dot4.sys has a worker thread blocked on this event
                // waiting for us to signal if the peripheral has data
                // available for dot4 to read. When we signal this
                // event dot4.sys generates a read request to retrieve
                // the data from the peripheral.

                if( ParHaveReadData( pdx ) ) {
                    // the peripheral has data - signal dot4.sys
                    DD((PCE)pdx,DDT,"PptPdoThread: Signaling Event [%x]\n", pdx->P12843DL.Event);
                    KeSetEvent(pdx->P12843DL.Event, 0, FALSE);
                }
            }

            if( pdx->QueryNumWaiters( pdx->PortContext ) != 0 ) {

                // someone else is waiting on the port - give up the
                // port - we'll attempt to reaquire the port later
                // when we have a request to process

                ParTerminate(pdx);
                ParFreePort(pdx);
                continue;
            }

        } // endif STATUS_TIMEOUT


        // wait here if PnP has paused us (e.g., QUERY_STOP, STOP, QUERY_REMOVE)
        KeWaitForSingleObject(&pdx->PauseEvent, Executive, KernelMode, FALSE, 0);

        if ( pdx->TimeToTerminateThread ) {

            // A dispatch thread has signalled us that we should clean
            // up any communication with our peripheral and then
            // terminate self. The dispatch thread is blocked waiting
            // for us to terminate self.

            if( pdx->Connected ) {

                // We currently have the port acquired and have the
                // peripheral negotiated into an IEEE mode. Terminate
                // the peripheral back to Compatibility mode forward
                // idle and release the port.

                ParTerminate( pdx );
                ParFreePort( pdx );
            }

            // terminate self

            PsTerminateSystemThread( STATUS_SUCCESS );
        }


        //
        // process the next request from the work queue - use the
        // Cancel SpinLock to protect the queue
        //

        IoAcquireCancelSpinLock(&OldIrql);

        ASSERT(!pdx->CurrentOpIrp);

        while (!IsListEmpty(&pdx->WorkQueue)) {

            // get next IRP from our list of work items
            PLIST_ENTRY HeadOfList;
            HeadOfList = RemoveHeadList(&pdx->WorkQueue);
            CurrentIrp = CONTAINING_RECORD(HeadOfList, IRP, Tail.Overlay.ListEntry);

            // we have started processing, this IRP can no longer be cancelled
#pragma warning( push ) 
#pragma warning( disable : 4054 4055 )
            IoSetCancelRoutine(CurrentIrp, NULL);
#pragma warning( pop ) 
            ASSERT(NULL == CurrentIrp->CancelRoutine);
            ASSERT(!CurrentIrp->Cancel);

            pdx->CurrentOpIrp = CurrentIrp;

            IoReleaseCancelSpinLock(OldIrql);

            //
            // Do the Io - PptPdoStartIo will exectute and complete the IRP: pdx->CurrentIrp
            //
            PptPdoStartIo(pdx);

            if( pdx->P12843DL.bEventActive ) {

                // Dot4.sys has a worker thread blocked on this event
                // waiting for us to signal if the peripheral has data
                // available for dot4 to read. When we signal this
                // event dot4.sys generates a read request to retrieve
                // the data from the peripheral.

                if( ParHaveReadData( pdx ) ) {
                    // the peripheral has data - signal dot4.sys
                    DD((PCE)pdx,DDT,"PptPdoThread: Signaling Eventb [%x]\n", pdx->P12843DL.Event);
                    KeSetEvent(pdx->P12843DL.Event, 0, FALSE);
                }
            }

            // wait here if PnP has paused us (e.g., QUERY_STOP, STOP, QUERY_REMOVE)
            KeWaitForSingleObject(&pdx->PauseEvent, Executive, KernelMode, FALSE, 0);

            IoAcquireCancelSpinLock(&OldIrql);
        }
        IoReleaseCancelSpinLock(OldIrql);

    } while (TRUE);
}

NTSTATUS
ParCreateSystemThread(
    PPDO_EXTENSION Pdx
    )

{
    NTSTATUS        Status;
    HANDLE          ThreadHandle;
    OBJECT_ATTRIBUTES objAttrib;

    DD((PCE)Pdx,DDT,"ParCreateSystemThread - %s - enter\n",Pdx->Location);

    // Start the thread - save referenced pointer to thread in our extension
    InitializeObjectAttributes( &objAttrib, NULL, OBJ_KERNEL_HANDLE, NULL, NULL );
    Status = PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, &objAttrib, NULL, NULL, PptPdoThread, Pdx );
    if (!NT_ERROR(Status)) {
        // We've got the thread.  Now get a pointer to it.
        Status = ObReferenceObjectByHandle( ThreadHandle, THREAD_ALL_ACCESS, NULL, KernelMode, &Pdx->ThreadObjectPointer, NULL );
        if (NT_ERROR(Status)) {
            Pdx->TimeToTerminateThread = TRUE;
            KeReleaseSemaphore( &Pdx->RequestSemaphore, 0, 1, FALSE );
        } else {
            // Now that we have a reference to the thread we can simply close the handle.
            ZwClose(ThreadHandle);
        }
        DD((PCE)Pdx,DDT,"ParCreateSystemThread - %s - SUCCESS\n",Pdx->Location);
    } else {
        DD((PCE)Pdx,DDT,"ParCreateSystemThread - %s FAIL - status = %x\n",Pdx->Location, Status);
    }
    return Status;
}

VOID
PptPdoStartIo(
    IN  PPDO_EXTENSION   Pdx
    )

/*++

Routine Description:

    This routine starts an I/O operation for the driver and
    then returns

Arguments:

    Pdx - The parallel device extension

Return Value:

    None

--*/

{
    PIRP                    Irp;
    PIO_STACK_LOCATION      IrpSp;
    KIRQL                   CancelIrql;

    Irp = Pdx->CurrentOpIrp;
    IrpSp = IoGetCurrentIrpStackLocation(Irp);

    Irp->IoStatus.Information = 0;

    if (!Pdx->Connected && !ParAllocPort(Pdx)) {
        // #pragma message( "dvrh Left bad stuff in thread.c") 
        DD((PCE)Pdx,DDE,"PptPdoStartIo - %s - threads are hosed\n",Pdx->Location);
        //        __asm int 3   
        //
        // If the allocation didn't succeed then fail this IRP.
        //
        goto CompleteIrp;
    }

    switch (IrpSp->MajorFunction) {

        case IRP_MJ_WRITE:
            ParWriteIrp(Pdx);
            break;

        case IRP_MJ_READ:
            ParReadIrp(Pdx);
            break;

        default:
            ParDeviceIo(Pdx);
            break;
    }

    if (!Pdx->Connected && !Pdx->AllocatedByLockPort) {
    
        // if we're not connected in a 1284 mode, then release host port
        // otherwise let the watchdog timer do it.

        ParFreePort(Pdx);
    }

CompleteIrp:

    IoAcquireCancelSpinLock(&CancelIrql);
    Pdx->CurrentOpIrp = NULL;
    IoReleaseCancelSpinLock(CancelIrql);

    P4CompleteRequest( Irp, Irp->IoStatus.Status, Irp->IoStatus.Information );

    return;
}