/*++ BUILD Version: 0000    // Increment this if a change has global effects

Copyright (c) 1994  Microsoft Corporation

Module Name:

    ndistapi.c

Abstract:

    This module contains the NdisTapi.sys implementation

Author:

    Dan Knudson (DanKn)    20-Feb-1994

Notes:

    (Future/outstanding issues)

    - stuff marked with "PnP" needs to be rev'd for plug 'n play support

Revision History:

--*/



#include "ndis.h"
#include "stdarg.h"
#include "stdio.h"
#include "ntddndis.h"
#include "ndistapi.h"
#include "private.h"
#include "intrface.h"


NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT  DriverObject,
    IN PUNICODE_STRING RegistryPath
    );

VOID
NdisTapiCancel(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );

NTSTATUS
NdisTapiCleanup(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );

NTSTATUS
NdisTapiDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );

VOID
NdisTapiUnload(
    IN PDRIVER_OBJECT DriverObject
    );


#if DBG
VOID
DbgPrt(
    IN LONG  DbgLevel,
    IN PUCHAR DbgMessage,
    IN ...
    );
#endif

VOID
DoProviderInitComplete(
    PPROVIDER_REQUEST  ProviderRequest,
    NDIS_STATUS Status
    );

ULONG
GetLineEvents(
    PVOID   EventBuffer,
    ULONG   BufferSize
    );

BOOLEAN
SyncInitAllProviders(
    void
    );

VOID
DoIrpMjCloseWork(
    PIRP    Irp
    );

NDIS_STATUS
SendProviderInitRequest(
    PPROVIDER_INFO  Provider
    );

NDIS_STATUS
SendProviderShutdown(
    PPROVIDER_INFO  Provider,
    PKIRQL          oldIrql
    );

VOID
NdisTapiIndicateStatus(
    IN  ULONG_PTR   DriverHandle,
    IN  PVOID       StatusBuffer,
    IN  UINT        StatusBufferSize
    );

VOID
DoLineOpenCompleteWork(
    PNDISTAPI_REQUEST   ndisTapiRequest,
    PPROVIDER_INFO      provider
    );

VOID
DoLineOpenWork(
    PNDISTAPI_REQUEST   ndisTapiRequest,
    PPROVIDER_INFO      provider
    );

NDIS_STATUS
VerifyProvider(
    PNDISTAPI_REQUEST   ndisTapiRequest,
    PPROVIDER_INFO      *provider
    );

NDIS_STATUS
VerifyLineClose(
    PNDISTAPI_REQUEST ndisTapiRequest,
    PPROVIDER_INFO     provider
    );

NTSTATUS
DoIoctlConnectWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    );

NTSTATUS
DoIoctlQuerySetWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    );

NTSTATUS
DoGetProviderEventsWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    );

NTSTATUS
DoLineCreateWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    );

//
// Use the alloc_text pragma to specify the driver initialization routines
// (they can be paged out).
//

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

NPAGED_LOOKASIDE_LIST  ProviderEventLookaside;

NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT  DriverObject,
    IN PUNICODE_STRING RegistryPath
    )

/*++

Routine Description:

    Installable driver initialization entry point.
    This entry point is called directly by the I/O system.

Arguments:

    DriverObject - pointer to the driver object

    RegistryPath - pointer to a unicode string representing the path
                   to driver-specific key in the registry

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise

--*/

{

    PDEVICE_OBJECT  deviceObject        = NULL;
    NTSTATUS        ntStatus;
    WCHAR           deviceNameBuffer[]  = L"\\Device\\NdisTapi";
    UNICODE_STRING  deviceNameUnicodeString;
    UNICODE_STRING  registryPath;


    DBGOUT ((2, "DriverEntry: enter"));

    //
    // Create a NON-EXCLUSIVE device, i.e. multiple threads at a time
    // can send i/o requests.
    //

    RtlInitUnicodeString (&deviceNameUnicodeString, deviceNameBuffer);

    ntStatus = IoCreateDevice(
        DriverObject,
        sizeof (KMDD_DEVICE_EXTENSION),
        &deviceNameUnicodeString,
        FILE_DEVICE_NDISTAPI,
        0,
        FALSE,
        &deviceObject
        );


    if (NT_SUCCESS(ntStatus))
    {
        //
        // Init the global & sero the extension
        //

        DeviceExtension =
            (PKMDD_DEVICE_EXTENSION) deviceObject->DeviceExtension;

        RtlZeroMemory(
            DeviceExtension,
            sizeof (KMDD_DEVICE_EXTENSION)
            );


        //
        // Create a NULL-terminated registry path & retrieve the registry
        // params (EventDataQueueLength)
        //

        registryPath.Buffer = ExAllocatePoolWithTag(
            PagedPool,
            RegistryPath->Length + sizeof(UNICODE_NULL),
            'IPAT'
            );

        if (!registryPath.Buffer)
        {
            DBGOUT((1, "DriverEntry: ExAllocPool for szRegistryPath failed"));

            ntStatus = STATUS_UNSUCCESSFUL;

            goto DriverEntry_err;
        }
        else
        {
            registryPath.Length = RegistryPath->Length;
            registryPath.MaximumLength =
                registryPath.Length + sizeof(UNICODE_NULL);

            RtlZeroMemory(
                registryPath.Buffer,
                registryPath.MaximumLength
                    );

            RtlMoveMemory(
                registryPath.Buffer,
                RegistryPath->Buffer,
                RegistryPath->Length
                );
        }

        ExFreePool (registryPath.Buffer);


        InitializeListHead(&DeviceExtension->ProviderEventList);

        ExInitializeNPagedLookasideList(&ProviderEventLookaside,
                                        NULL,
                                        NULL,
                                        0,
                                        sizeof(PROVIDER_EVENT),
                                        'IPAT',
                                        0);


        DeviceExtension->DeviceObject       = deviceObject;
        DeviceExtension->Status             = NDISTAPI_STATUS_DISCONNECTED;
        DeviceExtension->NdisTapiNumDevices = 0;
        DeviceExtension->htCall             = 0x80000001;

        KeInitializeSpinLock (&DeviceExtension->SpinLock);

        InitializeListHead(&DeviceExtension->ProviderRequestList);

        //
        // Create dispatch points for device control, create, close.
        //

        DriverObject->MajorFunction[IRP_MJ_CREATE]         =
        DriverObject->MajorFunction[IRP_MJ_CLOSE]          =
        DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = NdisTapiDispatch;
        DriverObject->MajorFunction[IRP_MJ_CLEANUP]        = NdisTapiCleanup;
        DriverObject->DriverUnload                         = NdisTapiUnload;
    }


    if (!NT_SUCCESS(ntStatus)) {

DriverEntry_err:

        //
        // Something went wrong, so clean up
        //

        DBGOUT((0, "init failed"));

        if (deviceObject)
        {

        while (!(IsListEmpty(&DeviceExtension->ProviderEventList))) {
            PPROVIDER_EVENT ProviderEvent;

            ProviderEvent = (PPROVIDER_EVENT)
                RemoveHeadList(&DeviceExtension->ProviderEventList);

            ExFreeToNPagedLookasideList(&ProviderEventLookaside, ProviderEvent);
        }

            IoDeleteDevice (deviceObject);
        }
    }


    DBGOUT ((2, "DriverEntry: exit"));

    return ntStatus;
}



VOID
NdisTapiCancel(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

{
    KIRQL   oldIrql;

    DBGOUT((2,"NdisTapiCancel: enter"));


    //
    // Release the cancel spinlock
    //

    IoReleaseCancelSpinLock (Irp->CancelIrql);


    //
    // Acquire the SpinLock & check to see if we're canceling a
    // pending get-events Irp
    //

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    do {

        DeviceExtension->IrpsCanceledCount++;

        if (Irp == DeviceExtension->EventsRequestIrp) {
            DeviceExtension->EventsRequestIrp = NULL;
            DeviceExtension->Flags |= EVENTIRP_CANCELED;
            break;
        }

        //
        // Try to remove request from our special
        // user-mode requests dev queue
        //
        if (!IsListEmpty(&DeviceExtension->ProviderRequestList)) {
            PLIST_ENTRY Entry;

            Entry = DeviceExtension->ProviderRequestList.Flink;

            while (Entry != &DeviceExtension->ProviderRequestList) {
                PPROVIDER_REQUEST   pReq;

                pReq = (PPROVIDER_REQUEST)Entry;

                if (pReq->Irp == Irp) {
                    RemoveEntryList(&pReq->Linkage);
                    DeviceExtension->RequestCount--;
                    DeviceExtension->Flags |= REQUESTIRP_CANCELED;
                    break;
                }

                Entry = Entry->Flink;
            }

            if (Entry == &DeviceExtension->ProviderRequestList) {
                DBGOUT((1,"NdisTapiCancel: Irp %p not in device queue?!?", Irp));
                DeviceExtension->Flags |= CANCELIRP_NOTFOUND;
            }
        }

    } while (FALSE);

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    //
    // Complete the request with STATUS_CANCELLED.
    //

    Irp->IoStatus.Status      = STATUS_CANCELLED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest (Irp, IO_NO_INCREMENT);

    DBGOUT((2,"NdisTapiCancel: completing irp=%p", Irp));
}



NTSTATUS
NdisTapiCleanup(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    This routine is the dispatch routine for cleanup requests.
    All requests queued are completed with STATUS_CANCELLED.

Arguments:

    DeviceObject - Pointer to device object.

    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/

{
    KIRQL   oldIrql;
    PNDISTAPI_REQUEST   ndisTapiRequest;
    PKDEVICE_QUEUE_ENTRY    packet;


    DBGOUT((2,"NdisTapiCleanup: enter"));


    //
    // Sync access to EventsRequestIrp by acquiring SpinLock
    //
    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    DeviceExtension->Flags |= CLEANUP_INITIATED;

    //
    // Check to see if there's a get-events request pending that needs
    // completing
    //
    if ((DeviceExtension->EventsRequestIrp != NULL) &&
        (DeviceExtension->EventsRequestIrp->Tail.Overlay.OriginalFileObject ==
        Irp->Tail.Overlay.OriginalFileObject)) {
        PIRP    LocalIrp;

        //
        // Acquire the cancel spinlock, remove the request from the
        // cancellable state, and free the cancel spinlock.
        //

        LocalIrp = DeviceExtension->EventsRequestIrp;
        if (IoSetCancelRoutine (LocalIrp, NULL) != NULL) {
            DeviceExtension->EventsRequestIrp = NULL;
            LocalIrp->IoStatus.Status      = STATUS_CANCELLED;
            LocalIrp->IoStatus.Information = 0;
            DeviceExtension->IrpsCanceledCount++;
            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
            DBGOUT((2,"NdisTapiCleanup: Completing EventRequestIrp %p", LocalIrp));
            IoCompleteRequest (LocalIrp, IO_NO_INCREMENT);
            KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);
        }
    }

    //
    // Cancel all outstanding QUERY/SET_INFO requests
    //
    if (!IsListEmpty(&DeviceExtension->ProviderRequestList)) {
        PPROVIDER_REQUEST   pReq;

        pReq = (PPROVIDER_REQUEST)
            DeviceExtension->ProviderRequestList.Flink;

        //
        // Until we have walked the entire list
        //
        while ((PVOID)pReq != (PVOID)&DeviceExtension->ProviderRequestList) {
            PIRP    LocalIrp;

            LocalIrp = pReq->Irp;

            //
            // If the current entry's irp has a fileobject that is
            // the same as the cleanup irp's fileobject then remove it
            // from the list and cancel it
            //
            if (LocalIrp->Tail.Overlay.OriginalFileObject ==
                Irp->Tail.Overlay.OriginalFileObject) {

                //
                // Remove the IRP from the cancelable state
                //

                if (IoSetCancelRoutine (LocalIrp, NULL) == NULL) {
                    //
                    // The irp has been canceled.  Let
                    // cancel routine cleanup.
                    //
                    pReq = 
                        (PPROVIDER_REQUEST)pReq->Linkage.Flink;

                    continue;
                }

                RemoveEntryList(&pReq->Linkage);
                DeviceExtension->RequestCount--;

                //
                // Set the status & info size values appropriately, & complete
                // the request
                //

                ndisTapiRequest = LocalIrp->AssociatedIrp.SystemBuffer;
                ndisTapiRequest->ulReturnValue = (ULONG) NDIS_STATUS_FAILURE;

                LocalIrp->IoStatus.Status = STATUS_CANCELLED;
                LocalIrp->IoStatus.Information = 0;
                DeviceExtension->IrpsCanceledCount++;

                KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
                DBGOUT((2,"NdisTapiCleanup: Completing ProviderRequestIrp %p", LocalIrp));
                IoCompleteRequest (LocalIrp, IO_NO_INCREMENT);
                KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

                pReq = (PPROVIDER_REQUEST)
                    DeviceExtension->ProviderRequestList.Flink;

            } else {
                pReq = (PPROVIDER_REQUEST)
                    pReq->Linkage.Flink;
            }
        }
    }

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    //
    // Complete the cleanup request with STATUS_SUCCESS.
    //
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest (Irp, IO_NO_INCREMENT);

    DBGOUT((2,"NdisTapiCleanup: exit"));

    return(STATUS_SUCCESS);
}




NTSTATUS
NdisTapiDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp
    )

/*++

Routine Description:

    Process the IRPs sent to this device.

Arguments:

    DeviceObject - pointer to a device object

    Irp          - pointer to an I/O Request Packet

Return Value:


--*/

{
    NTSTATUS    NtStatus;
    PVOID               ioBuffer;
    ULONG               inputBufferLength;
    ULONG               outputBufferLength;
    PIO_STACK_LOCATION  irpStack;

    //
    // Get a pointer to the current location in the Irp. This is where
    //     the function codes and parameters are located.
    //
    irpStack = IoGetCurrentIrpStackLocation (Irp);

    //
    // Get the pointer to the input/output buffer and it's length
    //
    ioBuffer = 
        Irp->AssociatedIrp.SystemBuffer;

    inputBufferLength = 
        irpStack->Parameters.DeviceIoControl.InputBufferLength;

    outputBufferLength = 
        irpStack->Parameters.DeviceIoControl.OutputBufferLength;

    switch (irpStack->MajorFunction) {
        case IRP_MJ_CREATE:
            DBGOUT ((2, "IRP_MJ_CREATE, Irp=%p", Irp));

            InterlockedIncrement(&DeviceExtension->RefCount);
            NtStatus = Irp->IoStatus.Status 
                = STATUS_SUCCESS;
            Irp->IoStatus.Information = 0;
            break;

        case IRP_MJ_CLOSE:
            DBGOUT ((2, "IRP_MJ_CLOSE, Irp=%p", Irp));

            DoIrpMjCloseWork(Irp);
            NtStatus = STATUS_SUCCESS;
            break;

        case IRP_MJ_DEVICE_CONTROL:

            switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
                case IOCTL_NDISTAPI_CONNECT:
                    DBGOUT ((2, "IOCTL_NDISTAPI_CONNECT, Irp=%p", Irp));
    
                    NtStatus = 
                        DoIoctlConnectWork(Irp,
                                           ioBuffer,
                                           inputBufferLength,
                                           outputBufferLength);
                    break;
    
                case IOCTL_NDISTAPI_QUERY_INFO:
                case IOCTL_NDISTAPI_SET_INFO:
                    DBGOUT ((2, "IOCTL_NDISTAPI_QUERY/SET_INFO, Irp=%p", Irp));
    
                    NtStatus = 
                        DoIoctlQuerySetWork(Irp,
                                            ioBuffer,
                                            inputBufferLength,
                                            outputBufferLength);
                    break;
    
                case IOCTL_NDISTAPI_GET_LINE_EVENTS:
                    DBGOUT ((2, "IOCTL_NDISTAPI_GET_LINE_EVENTS, Irp=%p", Irp));

                    NtStatus = 
                        DoGetProviderEventsWork(Irp,
                                                ioBuffer,
                                                inputBufferLength,
                                                outputBufferLength);
                    break;
    
                case IOCTL_NDISTAPI_CREATE:
                    DBGOUT ((2, "IOCTL_NDISTAPI_CREATE, Irp=%p", Irp));

                    NtStatus = 
                        DoLineCreateWork(Irp,
                                         ioBuffer,
                                         inputBufferLength,
                                         outputBufferLength);
                    break;

                default:
                    DBGOUT ((2, "Unknown IRP_MJ_DEVICE_CONTROL, Irp=%p", Irp));

                    NtStatus = Irp->IoStatus.Status = 
                        STATUS_INVALID_PARAMETER;
                    Irp->IoStatus.Information = 0;
                    break;
            }
            break;
    }

    if (NtStatus == STATUS_PENDING) {
        return (STATUS_PENDING);
    }

    ASSERT(NtStatus == Irp->IoStatus.Status);

    //
    // Unmark the irp pending since we are completing the
    // the irp below.
    //
    irpStack->Control &= ~SL_PENDING_RETURNED;

    IoCompleteRequest (Irp, IO_NO_INCREMENT);

    DBGOUT((3, "NdisTapiDispatch: completed Irp=%p", Irp));

    return NtStatus;
}

VOID
NdisTapiUnload(
    IN PDRIVER_OBJECT DriverObject
    )

/*++

Routine Description:

    Free all the allocated resources, etc.

Arguments:

    DriverObject - pointer to a driver object

Return Value:


--*/

{
    KIRQL                   oldIrql;
    PPROVIDER_INFO provider, nextProvider;


    DBGOUT ((2, "NdisTapiUnload: enter"));

    //
    // Delete the device object & sundry resources
    //

    while (!(IsListEmpty(&DeviceExtension->ProviderEventList))) {
        PPROVIDER_EVENT ProviderEvent;

        ProviderEvent = (PPROVIDER_EVENT)
            RemoveHeadList(&DeviceExtension->ProviderEventList);

        ExFreeToNPagedLookasideList(&ProviderEventLookaside, ProviderEvent);
    }

    ExDeleteNPagedLookasideList(&ProviderEventLookaside);

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    provider = DeviceExtension->Providers;

    while (provider != NULL)
    {
        nextProvider = provider->Next;

        ExFreePool (provider);

        provider = nextProvider;
    }

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    IoDeleteDevice (DriverObject->DeviceObject);

    DBGOUT ((2, "NdisTapiUnload: exit"));

    return;
}


VOID
NdisTapiRegisterProvider(
    IN  NDIS_HANDLE                 ProviderHandle,
    IN  PNDISTAPI_CHARACTERISTICS   Chars
    )
/*++

Routine Description:

    This func gets called by Ndis as a result of a Mac driver
    registering for Connection Wrapper services.

Arguments:



Return Value:


--*/

{
    KIRQL           oldIrql;
    BOOLEAN         sendRequest = FALSE;
    NDIS_STATUS     ndisStatus;
    PPROVIDER_INFO  provider, newProvider;


    DBGOUT ((2, "NdisTapiRegisterProvider: enter"));

    //
    // Grab the spin lock & add the new provider, and see whether to
    // send the provider an init request
    //
    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    //
    // See if this provider has already registered once.
    //
    provider = DeviceExtension->Providers;

    while (provider != NULL) {
        if (provider->Status == PROVIDER_STATUS_OFFLINE &&

            RtlCompareMemory(
                &provider->Guid,
                &Chars->Guid,
                sizeof(provider->Guid)) == sizeof(provider->Guid)) {
            DBGOUT((
                1,
                "Found a provider %p for Guid %4.4x-%2.2x-%2.2x-%1.1x%1.1x%1.1x%1.1x%1.1x%1.1x%1.1x%1.1x",
                provider,
                provider->Guid.Data1,
                provider->Guid.Data2,
                provider->Guid.Data3,
                provider->Guid.Data4[0],
                provider->Guid.Data4[1],
                provider->Guid.Data4[2],
                provider->Guid.Data4[3],
                provider->Guid.Data4[4],
                provider->Guid.Data4[5],
                provider->Guid.Data4[6],
                provider->Guid.Data4[7]
                ));

            DBGOUT((
                1,
                "numDevices %d BaseID %d",
                provider->NumDevices,
                provider->DeviceIDBase
                ));

            provider->Status = PROVIDER_STATUS_PENDING_REINIT;
            provider->ProviderHandle = ProviderHandle;
            provider->RequestProc = Chars->RequestProc;
            provider->MediaType = Chars->MediaType;
            break;
        }

        provider = provider->Next;
    }

    if (provider == NULL) {
        //
        // Create a new provider instance
        //

        newProvider = ExAllocatePoolWithTag(
                NonPagedPoolCacheAligned,
                sizeof(PROVIDER_INFO),
                'IPAT'
                );

        if (!newProvider) {
            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
            return;
        }

        RtlZeroMemory(newProvider, sizeof(PROVIDER_INFO));

        newProvider->Status         = PROVIDER_STATUS_PENDING_INIT;
        newProvider->ProviderHandle = ProviderHandle;
        newProvider->RequestProc    = Chars->RequestProc;

        RtlMoveMemory(
            &newProvider->Guid,
            &Chars->Guid,
            sizeof(newProvider->Guid)
            );

        newProvider->MediaType      = Chars->MediaType;
        newProvider->Next           = NULL;

        DBGOUT((
            1,
            "New provider for Guid %4.4x-%2.2x-%2.2x-%1.1x%1.1x%1.1x%1.1x%1.1x%1.1x%1.1x%1.1x",
            newProvider->Guid.Data1,
            newProvider->Guid.Data2,
            newProvider->Guid.Data3,
            newProvider->Guid.Data4[0],
            newProvider->Guid.Data4[1],
            newProvider->Guid.Data4[2],
            newProvider->Guid.Data4[3],
            newProvider->Guid.Data4[4],
            newProvider->Guid.Data4[5],
            newProvider->Guid.Data4[6],
            newProvider->Guid.Data4[7]
            ));

        //
        // Add the new provider, and see whether to send the 
        // provider an init request
        //

        if ((provider = DeviceExtension->Providers) == NULL) {
            DeviceExtension->Providers = newProvider;
        }
        else {
            while (provider->Next != NULL) {
                provider = provider->Next;
            }

            provider->Next = newProvider;
        }

        provider = newProvider;
    }


    //
    // The only case where we want to send off an init request to the
    // provider directly is when we are currently connected to TAPI,
    // and even then only when there are no other inits pending (since
    // we must synchronize inits due to calculation of DeviceIDBase)
    //

    if (DeviceExtension->Status == NDISTAPI_STATUS_CONNECTED) {
        //
        // TAPI is up.
        //
        // If TAPI already knows about this provider
        // go ahead and init the provider with it's current
        // DeviceIDBase.
        //
        // If TAPI does not know about this provider we
        // need to give TAPI an indication of a new device
        // coming on line.
        //
        if (provider->Status == PROVIDER_STATUS_PENDING_REINIT) {

            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

            ndisStatus = 
                SendProviderInitRequest (provider);

            if (ndisStatus == NDIS_STATUS_PENDING) {
                //
                // Wait for completion routine to get called
                //

                KeWaitForSingleObject (&provider->SyncEvent,
                                       Executive,
                                       KernelMode,
                                       FALSE,
                                       (PTIME) NULL);
            }

            KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

            //
            // Get tapi to reset the state of these lines by
            // forcing a line_close...
            //
            if (provider->DeviceInfo != NULL) {
                PDEVICE_INFO    DeviceInfo;
                ULONG           i;
                
                for(i = 0, DeviceInfo = provider->DeviceInfo;
                    i < provider->NumDevices;
                    i++, DeviceInfo++) {
                    NDIS_TAPI_EVENT NdisTapiEvent;


                    RtlZeroMemory (&NdisTapiEvent, sizeof(NDIS_TAPI_EVENT));

                    if (DeviceInfo->htLine != (HTAPI_LINE)NULL)
                    {
                        NdisTapiEvent.htLine = DeviceInfo->htLine;
                        NdisTapiEvent.ulMsg = LINE_CLOSE;

                        KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

                        NdisTapiIndicateStatus((ULONG_PTR) provider,
                                               &NdisTapiEvent,
                                               sizeof (NDIS_TAPI_EVENT));

                        KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

                        DeviceInfo->htLine = (HTAPI_LINE)NULL;
                        DeviceInfo->hdLine = (HDRV_LINE)NULL;
                    }
                }
            }

            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

        } else {

            NDIS_TAPI_EVENT NdisTapiEvent;

            ASSERT(provider->Status == PROVIDER_STATUS_PENDING_INIT);

            provider->Status = PROVIDER_STATUS_PENDING_LINE_CREATE;

            //
            // If there are no providers in the middle of doing
            // line_create's then we will kick off creates for this
            // provider.
            //
            // If we already have a line create pending on a provider
            // then we will wait until all of its line creates have
            // finished before we start sending them from
            // this one.
            //
            if (!(DeviceExtension->Flags & PENDING_LINECREATE)) {

                //
                // Do a LINE_CREATE so that we can get the starting
                // BaseID for this provider.  When TAPI calls us back
                // with ProviderCreateLineDevice we will have the
                // BaseDeviceID to use for this provider and we will
                // then init the provider.  Once we find out how many
                // devices the provider has we will alert TAPI of the
                // additional devices.
                //
                RtlZeroMemory(&NdisTapiEvent, sizeof(NDIS_TAPI_EVENT));

                provider->TempID = (ULONG_PTR)provider;

                DBGOUT((-1, 
                        "LINE_CREATE %d for provider %p",
                        provider->CreateCount,
                        provider->TempID
                        ));

                NdisTapiEvent.ulMsg = LINE_CREATE;
                NdisTapiEvent.ulParam1 = 0;
                NdisTapiEvent.ulParam2 = provider->TempID;
                NdisTapiEvent.ulParam3 = 0;

                DeviceExtension->Flags |= PENDING_LINECREATE;

                KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

                NdisTapiIndicateStatus((ULONG_PTR)provider, &NdisTapiEvent, sizeof(NDIS_TAPI_EVENT));
            }
            else
            {
                KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
            }
        }

        KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);
    }

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    ObReferenceObject(DeviceExtension->DeviceObject);
}



VOID
NdisTapiDeregisterProvider(
    IN  NDIS_HANDLE ProviderHandle
    )

/*++

Routine Description:

    This func...

    Note that this func does not send the provider a shutdown message,
    as an implicit shutdown is assumed when the provider deegisters.

Arguments:



Return Value:


--*/

{
    KIRQL           oldIrql;
    BOOLEAN         sendShutdownMsg = FALSE;
    PPROVIDER_INFO  provider, previousProvider;


    DBGOUT ((2, "NdisTapiDeregisterProvider: enter"));

    //
    // Grab the spin lock protecting the device extension
    //
    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    //
    // Find the provider instance corresponding to ProviderHandle
    //

    previousProvider = NULL;
    provider = DeviceExtension->Providers;

    while (provider != NULL &&
           provider->ProviderHandle != ProviderHandle) {
        
        previousProvider = provider;

        provider = provider->Next;
    }

    if (provider == NULL) {
        KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
        return;
    }

    if (provider->Status == PROVIDER_STATUS_ONLINE) {
        DeviceExtension->NdisTapiNumDevices -= provider->NumDevices;
    }

    //
    // Send the ProviderShutdown only if the provider
    // is not in PROVIDER_STATUS_OFFLINE. Otherwise
    // DoIrpMjCloseWork can end up sending 
    // Providershutdown on a removed adapter.
    //
    if(provider->Status != PROVIDER_STATUS_OFFLINE)
    {
        SendProviderShutdown (provider, &oldIrql);
        provider->Status = PROVIDER_STATUS_OFFLINE;
    }

    //
    // Do the right thing according to the current NdisTapi state
    //

    switch (DeviceExtension->Status)
    {
        case NDISTAPI_STATUS_CONNECTED:
        {
                UINT    i;

        //
        // Mark provider as offline
        //
        provider->Status = PROVIDER_STATUS_OFFLINE;
        provider->ProviderHandle = NULL;

#if 0
        if (provider->DeviceInfo != NULL) {
            PDEVICE_INFO    DeviceInfo;

            for(
                i = 0, DeviceInfo = provider->DeviceInfo;
                i < provider->NumDevices;
                i++, DeviceInfo++
                )
            {
                NDIS_TAPI_EVENT NdisTapiEvent;


                RtlZeroMemory (&NdisTapiEvent, sizeof(NDIS_TAPI_EVENT));

                if (DeviceInfo->htLine != (HTAPI_LINE)NULL)
                {
                    NdisTapiEvent.htLine = DeviceInfo->htLine;
                    NdisTapiEvent.ulMsg = LINE_CLOSE;

                    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

                    NdisTapiIndicateStatus((ULONG_PTR) provider,
                                           &NdisTapiEvent,
                                           sizeof (NDIS_TAPI_EVENT));

                    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

                    DeviceInfo->htLine = (HTAPI_LINE)NULL;
                }
            }
        }
#endif

        // PnP: what if providerInfo->State == PROVIDER_INIT_PENDING
        // PnP: what if providerInfo->State == PROVIDER_OFFLINE

        break;

        }

    case NDISTAPI_STATUS_DISCONNECTING:
    case NDISTAPI_STATUS_DISCONNECTED:

        //
        // Fix up pointers, remove provider from list
        //
        if (previousProvider == NULL) {
            DeviceExtension->Providers = provider->Next;
        } else {
            previousProvider->Next = provider->Next;
        }

        ExFreePool (provider);

        break;

    case NDISTAPI_STATUS_CONNECTING:

        // PnP: implement

        break;

    } // switch

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    ObDereferenceObject(DeviceExtension->DeviceObject);

    DBGOUT((2, "NdisTapiDeregisterProvider: exit"));
}



VOID
NdisTapiIndicateStatus(
    IN  ULONG_PTR   DriverHandle,
    IN  PVOID       StatusBuffer,
    IN  UINT        StatusBufferSize
    )

/*++

Routine Description:

    This func gets called by Ndis when a miniport driver calls
    NdisIndicateStatus to notify us of an async event
    (i.e. new call, call state chg, dev state chg, etc.)

Arguments:



Return Value:


--*/

{
    PIRP    irp;
    KIRQL   oldIrql;
    ULONG   bytesInQueue;
    ULONG   bytesToMove;
    ULONG   moveSize;
    BOOLEAN satisfiedPendingEventsRequest = FALSE;
    PNDIS_TAPI_EVENT    ndisTapiEvent;
    PNDISTAPI_EVENT_DATA    ndisTapiEventData;


    DBGOUT((2,"NdisTapiIndicateStatus: enter"));


    bytesInQueue = StatusBufferSize;

    moveSize = 0;


    //
    // Sync event buf access by acquiring SpinLock
    //

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    //
    // The very first thing to do is check if this is a LINE_NEWCALL
    // indication.  If so, we need to generate a unique tapi call
    // handle, which will be both returned to the calling miniport
    // (for use in subsequent status indications) and passed up to
    // the tapi server.
    //
    // The algorithim for computing a unique "htCall" is to start
    // at the value 0x80000001, and perpetually increment by 2.  
    // Keeping the low bit set will allow the user-mode TAPI component
    // we talk to to distinguish between these incoming call handles
    // and outgoing call handles, the latter of which will always
    // have the low bit zero'd (since they're really pointers to heap).
    // We are again going to use the space between 0x80000001 and 0xFFFFFFFF
    // to identify our call handle.  This allows for a maximum of 1GB of
    // calls to be active at a time.  This is done to avoid a conflict
    // with ndiswan's connection table index.  A bug in the ddk doc's
    // had users providing the connectionid instead of ndiswan's context
    // in the line get id oid.  Ndiswan has to check both of these and
    // now that they overlap it can cause problems.  NdisWan will use
    // 0x00000000 - 0x80000000 for it's context values.
    //
    // In <= NT 4.0, valid values used to range between 0x80000000
    // and 0xffffffff, as we relied on the fact that user-mode
    // addresses always had the low bit zero'd.  (Not a valid
    // assumption anymore!)
    //

    ndisTapiEvent = StatusBuffer;

    if (ndisTapiEvent->ulMsg == LINE_NEWCALL)
    {
        ndisTapiEvent->ulParam2 = DeviceExtension->htCall;

        DeviceExtension->htCall++;
        DeviceExtension->htCall++;

        if (DeviceExtension->htCall < 0x80000000) {
            DeviceExtension->htCall = 0x80000001;
        }
    }


    //
    // Check of there is an outstanding request to satisfy
    //

    if (DeviceExtension->EventsRequestIrp) {

        ASSERT(IsListEmpty(&DeviceExtension->ProviderEventList));

        //
        // Acquire the cancel spinlock, remove the request from the
        // cancellable state, and free the cancel spinlock.
        //

        irp = DeviceExtension->EventsRequestIrp;

        if (IoSetCancelRoutine(irp, NULL) != NULL) {
            DeviceExtension->EventsRequestIrp = NULL;


            //
            // Copy as much of the input data possible from the input data
            // queue to the SystemBuffer to satisfy the read.
            //

            ndisTapiEventData = irp->AssociatedIrp.SystemBuffer;

            bytesToMove = ndisTapiEventData->ulTotalSize;

            moveSize = (bytesInQueue < bytesToMove) ? bytesInQueue : bytesToMove;

            RtlMoveMemory (
                ndisTapiEventData->Data,
                (PCHAR) StatusBuffer,
                moveSize
                );


            //
            // Set the flag so that we start the next packet and complete
            // this read request (with STATUS_SUCCESS) prior to return.
            //

            ndisTapiEventData->ulUsedSize = moveSize;

            irp->IoStatus.Status = STATUS_SUCCESS;

            irp->IoStatus.Information = sizeof(NDISTAPI_EVENT_DATA) + moveSize - 1;

            satisfiedPendingEventsRequest = TRUE;
        }

    } else {

        do {
            PPROVIDER_EVENT ProviderEvent;

            ProviderEvent =
                ExAllocateFromNPagedLookasideList(&ProviderEventLookaside);

            if (ProviderEvent == NULL) {
                break;
            }

            RtlMoveMemory(&ProviderEvent->Event, StatusBuffer, sizeof(NDIS_TAPI_EVENT));

            InsertTailList(&DeviceExtension->ProviderEventList,
                           &ProviderEvent->Linkage);

            DeviceExtension->EventCount++;

        } while ( FALSE );
    }

    //
    // Release the spinlock
    //

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);


    //
    // If we satisfied an outstanding get events request then complete it
    //

    if (satisfiedPendingEventsRequest) {
        IoCompleteRequest (irp, IO_NO_INCREMENT);

        DBGOUT((2, "NdisTapiIndicateStatus: completion req %p", irp));
    }


    DBGOUT((2,"NdisTapiIndicateStatus: exit"));

    return;
}

VOID
NdisTapiCompleteRequest(
    IN  NDIS_HANDLE     NdisHandle,
    IN  PNDIS_REQUEST   NdisRequest,
    IN  NDIS_STATUS     NdisStatus
    )

/*++

Routine Description:

    This func gets called by Ndis as a result of a Mac driver
    calling NdisCompleteRequest of one of our requests.

Arguments:



Return Value:


--*/

{
    PIRP                    Irp;
    KIRQL                   oldIrql;
    ULONG                   requestID;
    PNDISTAPI_REQUEST       ndisTapiRequest;
    PPROVIDER_REQUEST       providerRequest;
    PPROVIDER_REQUEST       tempReq;
    PIO_STACK_LOCATION      irpStack;

    DBGOUT ((2, "NdisTapiCompleteRequest: enter"));

    providerRequest =
        CONTAINING_RECORD(NdisRequest, PROVIDER_REQUEST, NdisRequest);

    do {
        if (providerRequest->Flags & INTERNAL_REQUEST) {

            //
            // This request originated from NdisTapi.sys
            //
            switch (NdisRequest->DATA.SET_INFORMATION.Oid) {
                case OID_TAPI_PROVIDER_INITIALIZE:
                    DBGOUT((3,
                            "NdisTapiCompleteRequest: ProviderInit - Provider=%p, reqID=%x, Status=%x",
                            providerRequest->Provider,
                            providerRequest->RequestID,
                            NdisStatus));

                    switch (DeviceExtension->Status) {
                        case NDISTAPI_STATUS_CONNECTED:
                        case NDISTAPI_STATUS_CONNECTING:

                            DoProviderInitComplete (providerRequest, NdisStatus);
                            break;

                        case NDISTAPI_STATUS_DISCONNECTED:
                        case NDISTAPI_STATUS_DISCONNECTING:
                        default:
                            break;

                    }
                    break;

                case OID_TAPI_PROVIDER_SHUTDOWN:
                    DBGOUT((3,
                            "NdisTapiCompleteRequest: ProviderShutdown - Provider=%p, reqID=%x, Status=%x",
                            providerRequest->Provider,
                            providerRequest->RequestID,
                            NdisStatus));
                    break;

                default:
                    DBGOUT((1, "NdisTapiCompleteRequest: unrecognized Oid"));

                    break;
            }

            break;
        }

        //
        // This is a request originating from TAPI
        //


        //
        // Acquire the SpinLock since we're going to be removing a
        // TAPI request from the queue, and it might not be the request
        // we're looking for. The primary concern is that we could (if
        // the request we're really looking for has been removed) remove
        // a synchrously-completed request that is about to be removed &
        // completed in NdisTapiDispatch, in which case we want to stick
        // the request back in the queue before NdisTapiDispatch tries
        // to remove it.
        //
        KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

        tempReq = 
            (PPROVIDER_REQUEST)DeviceExtension->ProviderRequestList.Flink;

        while ((PVOID)tempReq != (PVOID)&DeviceExtension->ProviderRequestList) {
            if (tempReq == providerRequest) {
                break;
            }

            tempReq = 
                (PPROVIDER_REQUEST)tempReq->Linkage.Flink;
        }

        if (tempReq != providerRequest) {
#if DBG
            DbgPrint("NDISTAPI: NdisTapiCompleteRequest: Request %p not found!\n", 
                providerRequest);
#endif
            DeviceExtension->MissingRequests++;

            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
            break;
        }

        Irp = providerRequest->Irp;

        ndisTapiRequest = Irp->AssociatedIrp.SystemBuffer;

        ASSERT(providerRequest->RequestID == 
            *((ULONG *)ndisTapiRequest->Data));

        //
        // Remove the IRP from the cancelable state
        //
        if (IoSetCancelRoutine(Irp, NULL) == NULL) {
            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
            break;
        }

        RemoveEntryList(&providerRequest->Linkage);
        DeviceExtension->RequestCount--;

        KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

        DBGOUT((3,
                "NdisTapiCompleteRequest: Irp=%p, Oid=%x, devID=%d, reqID=%x, Status=%x",
                Irp,
                ndisTapiRequest->Oid,
                ndisTapiRequest->ulDeviceID,
                *((ULONG *)ndisTapiRequest->Data),
                  NdisStatus));

        //
        // Copy the relevant info back to the IRP
        //

        irpStack = IoGetCurrentIrpStackLocation (Irp);

        //
        // If this was a succesful QUERY_INFO request copy all the
        // data back to the tapi request buf & set
        // Irp->IoStatus.Information appropriately. Otherwise, we
        // just need to pass back the return value. Also mark irp
        // as successfully completed (regardless of actual op result)
        //

        if ((NdisRequest->RequestType == NdisRequestQueryInformation) &&
            (NdisStatus == NDIS_STATUS_SUCCESS)) {

            RtlMoveMemory(ndisTapiRequest->Data,
                NdisRequest->DATA.QUERY_INFORMATION.InformationBuffer,
                ndisTapiRequest->ulDataSize);

            Irp->IoStatus.Information =
                irpStack->Parameters.DeviceIoControl.OutputBufferLength;
        } else {

            Irp->IoStatus.Information = sizeof (ULONG);
        }

        if((NdisRequest->RequestType == NdisRequestQueryInformation) &&
          (NdisRequest->DATA.QUERY_INFORMATION.Oid == OID_TAPI_OPEN)) {
        
            DoLineOpenCompleteWork(ndisTapiRequest,
                            providerRequest->Provider);
        }
        

        Irp->IoStatus.Status = STATUS_SUCCESS;

        ndisTapiRequest->ulReturnValue = NdisStatus;

        IoCompleteRequest (Irp, IO_NO_INCREMENT);

    } while (FALSE);

    ExFreePool (providerRequest);

    DBGOUT ((2, "NdisTapiCompleteRequest: exit"));
}


#if DBG
VOID
DbgPrt(
    IN LONG  DbgLevel,
    IN PUCHAR DbgMessage,
    IN ...
    )

/*++

Routine Description:

    Formats the incoming debug message & calls DbgPrint

Arguments:

    DbgLevel   - level of message verboseness

    DbgMessage - printf-style format string, followed by appropriate
                 list of arguments

Return Value:


--*/

{
    if (DbgLevel <= NdisTapiDebugLevel)
    {
        char    buf[256] = "NDISTAPI: ";
        va_list ap;

        va_start (ap, DbgMessage);

        vsprintf (&buf[10], DbgMessage, ap);

        strcat (buf, "\n");

        DbgPrint (buf);

        va_end(ap);
    }

    return;
}
#endif // DBG


VOID
DoProviderInitComplete(
    PPROVIDER_REQUEST  ProviderRequest,
    NDIS_STATUS Status
    )

/*++

Routine Description:



Arguments:

    ProviderInitRequest - pointer successfully completed init request

Return Value:



Note:

--*/

{
    PPROVIDER_INFO                  provider = ProviderRequest->Provider;
    PNDIS_TAPI_PROVIDER_INITIALIZE  providerInitData =
        (PNDIS_TAPI_PROVIDER_INITIALIZE) ProviderRequest->Data;
    KIRQL OldIrql;
    BOOLEAN fFreeDeviceInfo = FALSE;

    DBGOUT ((2, "DoProviderInitComplete: enter"));

    //
    // Wrap this in an exception handler in case the provider was
    // removed during an async completion
    //
    try
    {
        if (Status == NDIS_STATUS_SUCCESS) {

            provider->ProviderID = (ULONG)providerInitData->ulProviderID;
            
            // Just in case the provider reports a bigger ulNumLineDevs
            if(providerInitData->ulNumLineDevs > provider->NumDevices)
            {
                fFreeDeviceInfo = TRUE;
            }
            
            provider->NumDevices = providerInitData->ulNumLineDevs;

            KeAcquireSpinLock(&DeviceExtension->SpinLock, &OldIrql);

            DeviceExtension->NdisTapiNumDevices += provider->NumDevices;

            KeReleaseSpinLock(&DeviceExtension->SpinLock, OldIrql);

            provider->Status = PROVIDER_STATUS_ONLINE;

            if(provider->DeviceInfo && fFreeDeviceInfo)
            {
                ExFreePool (provider->DeviceInfo);
                provider->DeviceInfo = NULL;
            }
            
            if (provider->DeviceInfo == NULL) {
                provider->DeviceInfo = (PDEVICE_INFO)
                    ExAllocatePoolWithTag(
                        NonPagedPool,
                        sizeof(DEVICE_INFO) * provider->NumDevices,
                        'IPAT'
                        );
            }

            if (provider->DeviceInfo != NULL) {
                PDEVICE_INFO    DeviceInfo;
                UINT    i;

                RtlZeroMemory(
                    provider->DeviceInfo,
                    sizeof(DEVICE_INFO) * provider->NumDevices
                    );

                for(i = 0, DeviceInfo = provider->DeviceInfo;
                    i < provider->NumDevices;
                    i++, DeviceInfo++) {
                    DeviceInfo->DeviceID = provider->DeviceIDBase + i;
                }
            }
        }

        //
        // Set the event which sync's miniport inits
        //

        KeSetEvent(&provider->SyncEvent,
                   0,
                   FALSE);

        DBGOUT((3,
                "providerID = 0x%x, numDevices = %d, BaseID = %d",
                provider->ProviderID,
                provider->NumDevices,
                provider->DeviceIDBase));
    }
    except (EXCEPTION_EXECUTE_HANDLER)
    {
        DBGOUT((1, "DoProviderInitComplete: provider invalid"));
    }

    DBGOUT ((2, "DoProviderInitComplete: exit"));
}


ULONG
GetLineEvents(
    PCHAR   EventBuffer,
    ULONG   BufferSize
    )

/*++

Routine Description:



Arguments:



Return Value:



Note:

    Assumes DeviceExtension->SpinLock held by caller.

--*/

{
    ULONG   BytesLeft;
    ULONG   BytesMoved;
    ULONG   EventCount;

    BytesLeft = BufferSize;
    BytesMoved = 0;
    EventCount = 0;

    while (!(IsListEmpty(&DeviceExtension->ProviderEventList))) {
        PPROVIDER_EVENT ProviderEvent;

        if (BytesLeft < sizeof(NDIS_TAPI_EVENT)) {
            break;
        }

        ProviderEvent = (PPROVIDER_EVENT)
            RemoveHeadList(&DeviceExtension->ProviderEventList);

        EventCount++;

        RtlMoveMemory(EventBuffer + BytesMoved,
                      (PUCHAR)&ProviderEvent->Event,
                      sizeof(NDIS_TAPI_EVENT));

        BytesMoved += sizeof(NDIS_TAPI_EVENT);
        BytesLeft -= sizeof(NDIS_TAPI_EVENT);

        ExFreeToNPagedLookasideList(&ProviderEventLookaside,
                                    ProviderEvent);
    }

    DeviceExtension->EventCount -= EventCount;

    DBGOUT((3, "GetLineEvents: Returned %d Events", EventCount));

    return (BytesMoved);
}


NDIS_STATUS
SendProviderInitRequest(
    PPROVIDER_INFO  Provider
    )

/*++

Routine Description:



Arguments:

    Provider - pointer to a PROVIDER_INFO representing provider to initialize

Return Value:



Note:

--*/

{
    KIRQL   oldIrql;
    NDIS_STATUS ndisStatus;
    PNDIS_REQUEST   NdisRequest;
    PPROVIDER_INFO  tmpProvider;
    PPROVIDER_REQUEST   providerRequest;
    PNDIS_TAPI_PROVIDER_INITIALIZE  providerInitData;

    DBGOUT ((2, "SendProviderInitRequest: enter"));

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    //
    // Determine the DeviceIDBase to be used for this provider
    //
    if (Provider->Status == PROVIDER_STATUS_PENDING_INIT) {

        Provider->DeviceIDBase = DeviceExtension->ProviderBaseID;
        tmpProvider = DeviceExtension->Providers;

        while (tmpProvider != NULL) {
            if (tmpProvider->Status != PROVIDER_STATUS_PENDING_INIT) {
                Provider->DeviceIDBase += tmpProvider->NumDevices;
            }

            tmpProvider = tmpProvider->Next;
        }
    }


    //
    // Create a provider init request
    //
    providerRequest = ExAllocatePoolWithTag(
        NonPagedPoolCacheAligned,
        sizeof(PROVIDER_REQUEST) + sizeof(NDIS_TAPI_PROVIDER_INITIALIZE) -
            sizeof(ULONG),
        'IPAT'
        );

    if (!providerRequest) {
        KeReleaseSpinLock(&DeviceExtension->SpinLock, oldIrql);

        return NDIS_STATUS_RESOURCES;
    }


    providerRequest->Irp = NULL;
    providerRequest->Flags = INTERNAL_REQUEST;
    providerRequest->Provider = Provider;
    NdisRequest = &providerRequest->NdisRequest;

    NdisRequest->RequestType = 
        NdisRequestQueryInformation;

    NdisRequest->DATA.SET_INFORMATION.Oid =
        OID_TAPI_PROVIDER_INITIALIZE;

    NdisRequest->DATA.SET_INFORMATION.InformationBuffer =
        providerRequest->Data;

    NdisRequest->DATA.SET_INFORMATION.InformationBufferLength =
        sizeof(NDIS_TAPI_PROVIDER_INITIALIZE);

    providerInitData                 =
        (PNDIS_TAPI_PROVIDER_INITIALIZE) providerRequest->Data;

    providerRequest->RequestID =
        providerInitData->ulRequestID = ++DeviceExtension->ulRequestID;

    providerInitData->ulDeviceIDBase = Provider->DeviceIDBase;

    KeInitializeEvent(&Provider->SyncEvent,
                      SynchronizationEvent,
                      FALSE);

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    //
    // Send the request
    //
    ndisStatus=
        (*Provider->RequestProc)
            (Provider->ProviderHandle,NdisRequest);

    if (ndisStatus != NDIS_STATUS_PENDING) {
        DoProviderInitComplete (providerRequest, ndisStatus);
        ExFreePool (providerRequest);
    }

    DBGOUT ((2, "SendProviderInitRequest: exit status %x", ndisStatus));

    return ndisStatus;
}


NDIS_STATUS
SendProviderShutdown(
    PPROVIDER_INFO  Provider,
    PKIRQL          oldIrql
    )

/*++

Routine Description:



Arguments:



Return Value:

    A pointer to the next provider in the global providers list

Note:

    Assumes DeviceExtension->SpinLock held by caller.

--*/

{
    NDIS_STATUS ndisStatus;
    PNDIS_REQUEST   NdisRequest;
    PPROVIDER_REQUEST   providerRequest;
    PNDIS_TAPI_PROVIDER_SHUTDOWN    providerShutdownData;

    DBGOUT ((2, "SendProviderShutdown: Provider=%p", Provider));

    //
    // Create a provider init request
    //
    providerRequest = 
        ExAllocatePoolWithTag(NonPagedPoolCacheAligned,
            sizeof(PROVIDER_REQUEST) + sizeof(NDIS_TAPI_PROVIDER_SHUTDOWN) -
            sizeof(ULONG),
            'IPAT');

    if (!providerRequest) {
        return NDIS_STATUS_RESOURCES;
    }

    providerRequest->Irp = NULL;
    providerRequest->Flags = INTERNAL_REQUEST;
    providerRequest->Provider = Provider;
    NdisRequest = &providerRequest->NdisRequest;

    NdisRequest->RequestType = 
        NdisRequestSetInformation;

    NdisRequest->DATA.SET_INFORMATION.Oid =
        OID_TAPI_PROVIDER_SHUTDOWN;

    NdisRequest->DATA.SET_INFORMATION.InformationBuffer =
        providerRequest->Data;

    NdisRequest->DATA.SET_INFORMATION.InformationBufferLength =
        sizeof(NDIS_TAPI_PROVIDER_SHUTDOWN);

    providerShutdownData =
        (PNDIS_TAPI_PROVIDER_SHUTDOWN)providerRequest->Data;

    providerRequest->RequestID =
        providerShutdownData->ulRequestID = ++DeviceExtension->ulRequestID;

    KeReleaseSpinLock (&DeviceExtension->SpinLock, *oldIrql);

    //
    // Send the request
    //
    ndisStatus = 
        (*Provider->RequestProc)
            (Provider->ProviderHandle, NdisRequest);

    //
    // If request was completed synchronously then free the request
    // (otherwise it will get freed when the completion proc is called)
    //
    if (ndisStatus != NDIS_STATUS_PENDING) {
        ExFreePool (providerRequest);
    }

    DBGOUT ((2, "SendProviderShutdown: Status=%x", ndisStatus));

    KeAcquireSpinLock (&DeviceExtension->SpinLock, oldIrql);

    return ndisStatus;
}


BOOLEAN
SyncInitAllProviders(
    void
    )

/*++

Routine Description:

    This functions walks the list of registered providers and sends
    init requests to the providers in the PENDING_INIT state

Arguments:

    (none)

Return Value:

    TRUE if all registered providers initialized, or
    FALSE if there are more providers to initialze

Note:

--*/

{
    ULONG           numDevices = 0;
    NDIS_STATUS     ndisStatus;
    PPROVIDER_INFO  provider;
    KIRQL           oldIrql;


    DBGOUT((2, "SyncInitAllProviders: enter"));

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    provider = DeviceExtension->Providers;

    while (provider != NULL) {
        if (provider->Status == PROVIDER_STATUS_PENDING_INIT ||
            provider->Status == PROVIDER_STATUS_PENDING_REINIT ||
            provider->Status == PROVIDER_STATUS_PENDING_LINE_CREATE) {

            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

            ndisStatus = SendProviderInitRequest (provider);

            if (ndisStatus == NDIS_STATUS_PENDING) {
                //
                // Wait for completion routine to get called
                //

                KeWaitForSingleObject (&provider->SyncEvent,
                                       Executive,
                                       KernelMode,
                                       FALSE,
                                       (PTIME) NULL
                                       );

            }

            KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);
        }

        provider = provider->Next;
    }


    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    DBGOUT((2, "SyncInitAllProviders: exit"));

    return TRUE;
}

VOID
DoIrpMjCloseWork(
    PIRP    Irp
    )
{
    KIRQL               oldIrql;

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    if (InterlockedDecrement(&DeviceExtension->RefCount) == 0) {

        if (DeviceExtension->Status == NDISTAPI_STATUS_CONNECTED) {
            PPROVIDER_INFO provider;

            DeviceExtension->Status =
                NDISTAPI_STATUS_DISCONNECTING;

            //
            // Send the providers a shutdown request
            //

            provider = DeviceExtension->Providers;

            while (provider != NULL) {

                switch (provider->Status) {
                    case PROVIDER_STATUS_ONLINE:

                        DeviceExtension->NdisTapiNumDevices -= provider->NumDevices;
                        SendProviderShutdown (provider, &oldIrql);

                        //
                        // fall thru...
                        //
                    case PROVIDER_STATUS_PENDING_INIT:
                    case PROVIDER_STATUS_PENDING_REINIT:

                        //
                        // Reset provider status
                        //
                        provider->Status = PROVIDER_STATUS_PENDING_INIT;
                        break;

                    case PROVIDER_STATUS_OFFLINE:
                        break;

                }

                provider = provider->Next;
            }

            DeviceExtension->Status = NDISTAPI_STATUS_DISCONNECTED;

            ASSERT(DeviceExtension->NdisTapiNumDevices == 0);
        }
    }

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    Irp->IoStatus.Status      = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
}

NTSTATUS
DoIoctlConnectWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    )
{
    KIRQL   oldIrql;
    ULONG   InfoSize;
    NTSTATUS    NtStatus;

    //
    // Someone's connecting. Make sure they passed us a valid
    // info buffer
    //
    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    do {

        if ((inputBufferLength < 2*sizeof(ULONG)) ||
            (outputBufferLength < sizeof(ULONG))) {

            DBGOUT ((3, "IOCTL_NDISTAPI_CONNECT: buffer too small"));
            NtStatus = STATUS_BUFFER_TOO_SMALL;
            InfoSize = 0;
            break;
        }

        if (DeviceExtension->Status == NDISTAPI_STATUS_DISCONNECTED) {

            DeviceExtension->Status = NDISTAPI_STATUS_CONNECTING;

            DeviceExtension->ProviderBaseID =
                *((ULONG *) ioBuffer);

            DBGOUT ((1, "ProviderBaseID %d",
                     DeviceExtension->ProviderBaseID));
            //
            // Synchronously init all providers
            //
            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

            SyncInitAllProviders();

            KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);
        }

        //
        // Return the number of line devs
        //
        {
            ULONG OfflineCount;
            PPROVIDER_INFO provider;

            //
            // Since some providers might be temporarily offline
            // we need to tell tapi about them even though they
            // are not currently useable.  This keeps the tapi
            // deviceid space consistent.
            //
            OfflineCount = 0;

            provider = DeviceExtension->Providers;
            while (provider != NULL) {
                if (provider->Status == PROVIDER_STATUS_OFFLINE) {
                    OfflineCount += provider->NumDevices;
                }
                provider = provider->Next;
            }

            *((ULONG *) ioBuffer)=
                DeviceExtension->NdisTapiNumDevices + OfflineCount;
        }

        DeviceExtension->Status = NDISTAPI_STATUS_CONNECTED;

        KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

        InfoSize = sizeof(ULONG);
        NtStatus = STATUS_SUCCESS;

    } while (FALSE);

    Irp->IoStatus.Status = NtStatus;
    Irp->IoStatus.Information = InfoSize;

    return (NtStatus);
}

NTSTATUS
DoIoctlQuerySetWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    )
{
    KIRQL   oldIrql;
    ULONG   InfoSize;
    NTSTATUS    NtStatus;
    PPROVIDER_INFO  provider;
    NDIS_STATUS     ndisStatus;
    PNDIS_REQUEST   NdisRequest;
    PNDISTAPI_REQUEST   ndisTapiRequest;
    PPROVIDER_REQUEST   providerRequest;
    PIO_STACK_LOCATION  irpStack;

    do {
        ndisTapiRequest = ioBuffer;
        NtStatus = STATUS_SUCCESS;
        InfoSize = 0;

        //
        // Make sure input & output buffers are large enough
        //
        if ((inputBufferLength < sizeof (NDISTAPI_REQUEST))  ||

            (ndisTapiRequest->ulDataSize > 0x10000000) ||

            (inputBufferLength < (sizeof (NDISTAPI_REQUEST) +
                ndisTapiRequest->ulDataSize - sizeof (UCHAR)) ||

            (outputBufferLength < (sizeof (NDISTAPI_REQUEST) +
                ndisTapiRequest->ulDataSize - sizeof (UCHAR))))) {
            DBGOUT((-1, "NdisTapiDispatch: buffer to small!"));
            NtStatus = STATUS_BUFFER_TOO_SMALL;
            break;
        }

        //
        // Verify we're connected, then check the device ID of the
        // incoming request against our list of online devices
        //
        ndisStatus = 
            VerifyProvider(ndisTapiRequest, &provider);

        if (ndisStatus != NDIS_STATUS_SUCCESS) {
            ndisTapiRequest->ulReturnValue = ndisStatus;
            InfoSize = sizeof(ULONG);
            break;
        }

        //
        // If this is a line_close, check to see if the line has
        // been opened before sending a line close oid
        //
        if(ndisTapiRequest->Oid == OID_TAPI_CLOSE) {

            ndisStatus = VerifyLineClose(ndisTapiRequest, provider);

            if(ndisStatus != NDIS_STATUS_SUCCESS)
            {
                ndisTapiRequest->ulReturnValue = ndisStatus;
                InfoSize = sizeof(ULONG);
                break;
            }
            
        }
        

        //
        // Create the providerRequest & submit it
        //
        providerRequest = 
            ExAllocatePoolWithTag(NonPagedPoolCacheAligned,
                sizeof(PROVIDER_REQUEST) + 
                ndisTapiRequest->ulDataSize -
                sizeof(ULONG),
                'IPAT');

        if (providerRequest == NULL) {
            DBGOUT((-1, "NdisTapiDispatch: unable to alloc request buf"));

            ndisTapiRequest->ulReturnValue = NDIS_STATUS_RESOURCES;
            InfoSize = sizeof (ULONG);
            break;
        }

        if (ndisTapiRequest->Oid == OID_TAPI_OPEN) {
            DoLineOpenWork(ndisTapiRequest, provider);
        }

        KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

        providerRequest->Flags = 0;
        providerRequest->Irp = Irp;
        providerRequest->Provider = provider;
        providerRequest->RequestID = 
            *((ULONG *)ndisTapiRequest->Data) = ++DeviceExtension->ulRequestID;

        RtlMoveMemory(providerRequest->Data, 
            ndisTapiRequest->Data, ndisTapiRequest->ulDataSize);

        NdisRequest = &providerRequest->NdisRequest;

        irpStack = IoGetCurrentIrpStackLocation (Irp);

        NdisRequest->RequestType =
            (irpStack->Parameters.DeviceIoControl.IoControlCode == 
             IOCTL_NDISTAPI_QUERY_INFO) ? NdisRequestQueryInformation : 
            NdisRequestSetInformation;

        NdisRequest->DATA.SET_INFORMATION.Oid =
            ndisTapiRequest->Oid;

        NdisRequest->DATA.SET_INFORMATION.InformationBuffer =
            providerRequest->Data;

        NdisRequest->DATA.SET_INFORMATION.InformationBufferLength =
            ndisTapiRequest->ulDataSize;

        DBGOUT((3,
                "DoIoctlQuerySetWork: Oid=%x, devID=%d, reqID=%x",
                ndisTapiRequest->Oid,
                ndisTapiRequest->ulDeviceID,
                *((ULONG *)ndisTapiRequest->Data)));

        //
        // Queue up this TAPI request in our request list.
        //
        InsertTailList(&DeviceExtension->ProviderRequestList, 
                       &providerRequest->Linkage);
        DeviceExtension->RequestCount++;

        KeReleaseSpinLock(&DeviceExtension->SpinLock, oldIrql);

        //
        // Mark the TAPI request pending and set the cancel routine        
        //
        IoMarkIrpPending(Irp);
        Irp->IoStatus.Status = STATUS_PENDING;
        IoSetCancelRoutine (Irp, NdisTapiCancel);

        //
        // Call the provider's request proc
        //
        ndisStatus = 
            (*provider->RequestProc)
                (provider->ProviderHandle, NdisRequest);

        //
        // If PENDING was returned then just exit & let the completion
        // routine handle the request completion
        //
        // NOTE: If pending was returned then the request may have
        //       already been completed, so DO NOT touch anything
        //       in the Irp (don't reference the pointer, etc.)
        //

        if (ndisStatus == NDIS_STATUS_PENDING) {
            DBGOUT((1, "DoIoctlQuerySetWork: exit Irp=%p, Status=%x",
                    Irp, STATUS_PENDING));

            return (STATUS_PENDING);
        }

        //
        // The provider request completed synchronously, so remove
        // the TAPI request from the device queue. We need to
        // synchronize access to this queue with the
        // SpinLock.
        //
        KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);
        do {
            PPROVIDER_REQUEST   pReq;

            pReq = (PPROVIDER_REQUEST)
                DeviceExtension->ProviderRequestList.Flink;

            while ((PVOID)pReq != (PVOID)&DeviceExtension->ProviderRequestList) {
                if (pReq == providerRequest) {
                    break;
                }

                pReq = (PPROVIDER_REQUEST)
                    pReq->Linkage.Flink;
            }

            if (pReq != providerRequest) {
                DBGOUT((0, "DoIoctlQuerySetWork - Request %p not found!", 
                    providerRequest));
                KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
                return (STATUS_PENDING);
            }

            Irp = providerRequest->Irp;

            ndisTapiRequest = Irp->AssociatedIrp.SystemBuffer;

            ASSERT(providerRequest->RequestID == 
                *((ULONG *)ndisTapiRequest->Data));

            //
            // Remove the IRP from the cancelable state
            //
            if (IoSetCancelRoutine(Irp, NULL) == NULL) {
                DBGOUT((0, "DoIoctlQuerySetWork - Irp %p has been canceled!", Irp));
                KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
                return (STATUS_PENDING);
            }

            RemoveEntryList(&providerRequest->Linkage);
            DeviceExtension->RequestCount--;

        } while (FALSE);
        KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

        //
        // If this was a succesful QUERY_INFO request copy all the
        // data back to the tapi request buf & set
        // Irp->IoStatus.Information appropriately. Otherwise, we
        // just need to pass back the return value.
        //

        if ((irpStack->Parameters.DeviceIoControl.IoControlCode == 
             IOCTL_NDISTAPI_QUERY_INFO) &&
            (ndisStatus == NDIS_STATUS_SUCCESS)) {

            RtlMoveMemory(ndisTapiRequest->Data,
                          providerRequest->Data,
                          ndisTapiRequest->ulDataSize);

            InfoSize =
                irpStack->Parameters.DeviceIoControl.OutputBufferLength;

        } else {
            InfoSize = sizeof (ULONG);
        }

        ndisTapiRequest->ulReturnValue = ndisStatus;

        //
        // Free the providerRequest
        //
        ExFreePool (providerRequest);

    } while (FALSE);

    Irp->IoStatus.Status = NtStatus;
    Irp->IoStatus.Information = InfoSize;

    DBGOUT((1, "DoIoctlQuerySetWork: exit Irp=%p, Status=%x",
            Irp, NtStatus));

    return (NtStatus);
}

VOID
DoLineOpenCompleteWork(
    PNDISTAPI_REQUEST ndisTapiRequest,
    PPROVIDER_INFO provider
    )
{
    DBGOUT((2, "DoLineOpenCompleteWork: Open Completed"));
    
    //
    // Now stash the hdLine for this deviceid
    //
    if (provider->DeviceInfo != NULL) {
        UINT    i;
        PDEVICE_INFO    DeviceInfo;
        PNDIS_TAPI_OPEN TapiOpen;

        TapiOpen = (PNDIS_TAPI_OPEN) ndisTapiRequest->Data;
        for(i = 0, DeviceInfo = provider->DeviceInfo;
            i < provider->NumDevices;
            i++, DeviceInfo++) {
            if (DeviceInfo->DeviceID == TapiOpen->ulDeviceID) {

                DeviceInfo->hdLine = TapiOpen->hdLine;

                DBGOUT((2, "Complete for open. stashing hdline=0x%x for device %d",
                            DeviceInfo->hdLine, DeviceInfo->DeviceID));
                
                break;
            }
        }
    }
}

VOID
DoLineOpenWork(
    PNDISTAPI_REQUEST   ndisTapiRequest,
    PPROVIDER_INFO      provider
    )
{
    KIRQL   oldIrql;
    PNDIS_TAPI_OPEN TapiOpen;
    PNDISTAPI_OPENDATA  OpenData;

    TapiOpen = (PNDIS_TAPI_OPEN) ndisTapiRequest->Data;

    if (ndisTapiRequest->ulDataSize >= sizeof(NDIS_TAPI_OPEN) +
                                       sizeof(NDISTAPI_OPENDATA)) {

        OpenData = (PNDISTAPI_OPENDATA)
            ((PUCHAR)ndisTapiRequest->Data + sizeof(NDIS_TAPI_OPEN));

        RtlMoveMemory(&OpenData->Guid, 
            &provider->Guid, sizeof(OpenData->Guid));

        OpenData->MediaType = provider->MediaType;
    }

    //
    // Now stash the htLine for this deviceid
    //
    if (provider->DeviceInfo != NULL) {
        UINT    i;
        PDEVICE_INFO    DeviceInfo;

        for(i = 0, DeviceInfo = provider->DeviceInfo;
            i < provider->NumDevices;
            i++, DeviceInfo++) {
            if (DeviceInfo->DeviceID == TapiOpen->ulDeviceID) {

                DeviceInfo->htLine = TapiOpen->htLine;

                DBGOUT((
                    1,
                    "Stash htLine - provider %p DeviceID %d htLine %x",
                        provider,
                        DeviceInfo->DeviceID,
                        DeviceInfo->htLine));
            }
        }
    }
}

NDIS_STATUS
VerifyLineClose(
    PNDISTAPI_REQUEST   ndisTapiRequest,
    PPROVIDER_INFO      provider
    )
{
    NDIS_STATUS ndisStatus = NDIS_STATUS_SUCCESS;
    
    if (provider->DeviceInfo != NULL) {
        UINT    i;
        PDEVICE_INFO    DeviceInfo;
        PNDIS_TAPI_CLOSE TapiClose;

        TapiClose = (PNDIS_TAPI_CLOSE) ndisTapiRequest->Data;
        for(i = 0, DeviceInfo = provider->DeviceInfo;
            i < provider->NumDevices;
            i++, DeviceInfo++) {
            if (DeviceInfo->hdLine == TapiClose->hdLine) {
                break;
            }
        }

        if(i == provider->NumDevices)
        {
            DBGOUT((2,"LINE_CLOSE: didn't find hdLine=0x%x",
                    TapiClose->hdLine));
            ndisStatus = NDISTAPIERR_DEVICEOFFLINE;
        }
        else
        {
            DBGOUT((2, "LINE_CLOSE: found hdLine=0x%x",
                        TapiClose->hdLine));
        }
    }

    return ndisStatus;
}

NDIS_STATUS
VerifyProvider(
    PNDISTAPI_REQUEST   ndisTapiRequest,
    PPROVIDER_INFO      *provider
    )
{
    KIRQL   oldIrql;
    PPROVIDER_INFO  pp;
    NDIS_STATUS     Status;
    ULONG           targetDeviceID;

    Status = NDIS_STATUS_SUCCESS;
    *provider = NULL;

    targetDeviceID = ndisTapiRequest->ulDeviceID;

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    do {

        if (DeviceExtension->Status != NDISTAPI_STATUS_CONNECTED) {
            DBGOUT((3, "VerifyProvider: unconnected, returning err"));

            Status = NDISTAPIERR_UNINITIALIZED;
            break;
        }

        pp = DeviceExtension->Providers;

        while (pp != NULL) {

            if ((pp->Status == PROVIDER_STATUS_ONLINE) &&
                (targetDeviceID >= pp->DeviceIDBase) &&
                (targetDeviceID <
                     pp->DeviceIDBase + pp->NumDevices)
                ) {

                break;
            }

            pp = pp->Next;
        }

        if (pp == NULL ||
            pp->ProviderHandle == NULL) {
            //
            // Set Irp->IoStatus.Information large enough that err code
            // gets copied back to user buffer
            //
            DBGOUT((3, "VerifyProvider: dev offline, returning err"));

            Status = NDISTAPIERR_DEVICEOFFLINE;
            break;
        }

        *provider = pp;

    } while (FALSE);

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    return (Status);
}

NTSTATUS
DoGetProviderEventsWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    )
{
    KIRQL   oldIrql;
    ULONG   InfoSize;
    NTSTATUS    NtStatus;
    PNDISTAPI_EVENT_DATA    ndisTapiEventData;

    ndisTapiEventData = ioBuffer;
    NtStatus = STATUS_SUCCESS;
    InfoSize = 0;

    //
    // Sync event buf access by acquiring SpinLock
    //
    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    do {

        if ((inputBufferLength < sizeof (NDISTAPI_EVENT_DATA))  ||
            (outputBufferLength < sizeof(NDISTAPI_EVENT_DATA)) ||
            ((outputBufferLength - 
             FIELD_OFFSET(NDISTAPI_EVENT_DATA, Data[0])) <
             ndisTapiEventData->ulTotalSize)) {

            NtStatus = STATUS_BUFFER_TOO_SMALL;
            break;
        }

        if (DeviceExtension->Status != NDISTAPI_STATUS_CONNECTED) {
            DBGOUT((3, "DoGetProviderEventsWork: Status!=NDIS_STATUS_CONNECTED!"));
            NtStatus = STATUS_UNSUCCESSFUL;
            break;
        }

        if (DeviceExtension->EventsRequestIrp != NULL) {
#if DBG
            DbgPrint("NDISTAPI: Attempt to set duplicate EventIrp o:%p, d:%p\n",
                DeviceExtension->EventsRequestIrp, Irp);
#endif
            NtStatus = STATUS_UNSUCCESSFUL;
            break;
        }

        //
        // Inspect DeviceExtension to see if there's any data available
        //
        if (DeviceExtension->EventCount == 0) {

            //
            // Hold the request pending.  It remains in the cancelable
            // state.  When new line event input is received
            // (NdisTapiIndicateStatus) or generated (i.e.
            // LINEDEVSTATE_REINIT) the data will get copied & the
            // request completed.
            //
            ASSERT(DeviceExtension->EventsRequestIrp == NULL);

            DeviceExtension->EventsRequestIrp = Irp;

            IoMarkIrpPending(Irp);
            Irp->IoStatus.Status = STATUS_PENDING;
            IoSetCancelRoutine (Irp, NdisTapiCancel);

            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

            DBGOUT((3, "DoGetProviderEventsWork: Pending Irp=%p", Irp));

            return(STATUS_PENDING);
        }

        //
        // There's line event data queued in our ring buffer. Grab as
        // much as we can & complete the request.
        //
        ndisTapiEventData->ulUsedSize = 
            GetLineEvents(ndisTapiEventData->Data,
                          ndisTapiEventData->ulTotalSize);

        InfoSize = 
            ndisTapiEventData->ulUsedSize + sizeof(NDISTAPI_EVENT_DATA) - 1;

        DBGOUT((3, "GetLineEvents: SyncComplete Irp=%p", Irp));

    } while (FALSE);

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);
    Irp->IoStatus.Status = NtStatus;
    Irp->IoStatus.Information = InfoSize;

    return (NtStatus);
}

NTSTATUS
DoLineCreateWork(
    PIRP    Irp,
    PVOID   ioBuffer,
    ULONG   inputBufferLength,
    ULONG   outputBufferLength
    )
{
    KIRQL   oldIrql;
    ULONG   InfoSize;
    NTSTATUS    NtStatus;
    PPROVIDER_INFO  provider;
    PNDISTAPI_CREATE_INFO   CreateInfo;

    InfoSize = 0;
    NtStatus = STATUS_SUCCESS;

    KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

    do {

        if (inputBufferLength < sizeof(CreateInfo)) {
            DBGOUT ((3, "IOCTL_NDISTAPI_CREATE: buffer too small"));
            NtStatus = STATUS_BUFFER_TOO_SMALL;
            break;
        }

        if (DeviceExtension->Status != NDISTAPI_STATUS_CONNECTED) {
            DBGOUT((3, "IOCTL_NDISTAPI_CREATE: while unconnected, returning err"));
            NtStatus = STATUS_UNSUCCESSFUL;
            break;
        }

        CreateInfo = (PNDISTAPI_CREATE_INFO)ioBuffer;

        provider = DeviceExtension->Providers;

        while (provider != NULL) {
            if (provider->TempID == CreateInfo->TempID) {
                break;
            }
            provider = provider->Next;
        }

        if (provider == NULL) {
            DBGOUT((0, "IOCTL_NDISTAPI_CREATE: Provider not found %x", 
                    CreateInfo->TempID));
            NtStatus = STATUS_UNSUCCESSFUL;
            break;
        }

        if (provider->Status == PROVIDER_STATUS_OFFLINE) {
            DBGOUT((0,  "IOCTL_CREATE - Provider %p invalid state %x", 
                    provider, provider->Status));
            NtStatus = STATUS_UNSUCCESSFUL;
            break;
        }

        DBGOUT((1, "IOCTL_NDISTAPI_CREATE: provider %p ID %d", 
                provider, CreateInfo->DeviceID));

        if (provider->CreateCount == 0) {
            NDIS_STATUS     ndisStatus;

            //
            // Set the base ID
            //
            provider->DeviceIDBase =
                CreateInfo->DeviceID;

            //
            // Init the provider
            //

            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

            ndisStatus = SendProviderInitRequest (provider);

            if (ndisStatus == NDIS_STATUS_PENDING) {
                //
                // Wait for completion routine to get called
                //

                KeWaitForSingleObject (&provider->SyncEvent,
                                       Executive,
                                       KernelMode,
                                       FALSE,
                                       (PTIME) NULL);
            }

            KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);
        }

        ASSERT(CreateInfo->DeviceID ==
            (provider->DeviceIDBase + provider->CreateCount));

        provider->CreateCount++;

        ASSERT(provider->CreateCount <= provider->NumDevices);

        if (provider->CreateCount == provider->NumDevices) {

            //
            // We have finished all of the line_creates for this
            // provider so find the next provider that needs to be
            // kick started.
            //
            provider = provider->Next;

            while (provider != NULL) {

                if (provider->Status == 
                    PROVIDER_STATUS_PENDING_LINE_CREATE) {
                    break;
                }

                provider = provider->Next;
            }
        }

        if (provider != NULL) {

            NDIS_TAPI_EVENT NdisTapiEvent;

            //
            // Do a LINE_CREATE for all additional devices
            // on this provider
            //
            RtlZeroMemory(&NdisTapiEvent, sizeof(NDIS_TAPI_EVENT));

            provider->TempID = (ULONG_PTR)provider;

            DBGOUT((
                -1,
                "LINE_CREATE %d for provider %p",
                provider->CreateCount,
                provider->TempID
                ));

            NdisTapiEvent.ulMsg = LINE_CREATE;
            NdisTapiEvent.ulParam1 = 0;
            NdisTapiEvent.ulParam2 = provider->TempID;
            NdisTapiEvent.ulParam3 = 0;

            KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

            NdisTapiIndicateStatus((ULONG_PTR) provider,
                                   &NdisTapiEvent,
                                   sizeof (NDIS_TAPI_EVENT));

            KeAcquireSpinLock (&DeviceExtension->SpinLock, &oldIrql);

        } else {

            DeviceExtension->Flags &= ~PENDING_LINECREATE;
        }

        InfoSize = sizeof(NDISTAPI_CREATE_INFO);

    } while (FALSE);

    KeReleaseSpinLock (&DeviceExtension->SpinLock, oldIrql);

    Irp->IoStatus.Status = NtStatus;
    Irp->IoStatus.Information = InfoSize;

    return (NtStatus);
}