/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    ntinit.c

Abstract:

    NT specific routines for loading and configuring the
    automatic connection notification driver (acd.sys).

Author:

    Anthony Discolo (adiscolo)  18-Apr-1995

Revision History:

--*/
#include <ndis.h>
#include <cxport.h>
#include <tdi.h>
#include <tdikrnl.h>
#include <tdistat.h>
#include <tdiinfo.h>
#include <acd.h>

#include "acdapi.h"
#include "acddefs.h"
#include "mem.h"
#include "debug.h"


//
// Global variables
//
#if DBG
ULONG AcdDebugG = 0x0;    // see debug.h for flags
#endif

PDRIVER_OBJECT pAcdDriverObjectG;
PDEVICE_OBJECT pAcdDeviceObjectG;
PACD_DISABLED_ADDRESSES pDisabledAddressesG = NULL;


HANDLE hSignalNotificationThreadG;

BOOLEAN AcdStopThread = FALSE; // Set to TRUE to stop system thread
PETHREAD NotificationThread;
BOOLEAN fAcdEnableRedirNotifs = FALSE;

extern LONG lOutstandingRequestsG;

//
// Imported routines
//
VOID
AcdNotificationRequestThread(
    PVOID context
    );

//
// External function prototypes
//
NTSTATUS
AcdDispatch(
    IN PDEVICE_OBJECT pDeviceObject,
    IN PIRP           pIrp
    );

VOID
AcdConnectionTimer(
    IN PDEVICE_OBJECT pDeviceObject,
    IN PVOID          pContext
    );

//
// Internal function prototypes
//
NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT  pDriverObject,
    IN PUNICODE_STRING pRegistryPath
    );

BOOLEAN
GetComputerName(
    IN PUCHAR szName,
    IN USHORT cbName
    );

VOID
AcdUnload(
    IN PDRIVER_OBJECT pDriverObject
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, AcdUnload)
#endif // ALLOC_PRAGMA


NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT  pDriverObject,
    IN PUNICODE_STRING pRegistryPath
    )

/*++

DESCRIPTION
    Initialization routine for the network connection notification driver.
    It creates the device object and initializes the driver.

ARGUMENTS
    pDriverObject: a pointer to the driver object created by the system.

    pRegistryPath - the name of the configuration node in the registry.

RETURN VALUE
    The final status from the initialization operation.

--*/

{
    NTSTATUS        status;
    UNICODE_STRING  deviceName;
    ULONG           i;
    OBJECT_ATTRIBUTES objectAttributes;
    IO_STATUS_BLOCK ioStatusBlock;
    PDEVICE_OBJECT pDeviceObject;
    PFILE_OBJECT pFileObject;
    PACD_DISABLED_ADDRESS pDisabledAddress = NULL;

    //
    // Initialize the spin lock.
    //
    KeInitializeSpinLock(&AcdSpinLockG);
    //
    // Initialize the notification and completion
    // connection queues.
    //
    InitializeListHead(&AcdNotificationQueueG);
    InitializeListHead(&AcdCompletionQueueG);
    InitializeListHead(&AcdConnectionQueueG);
    InitializeListHead(&AcdDriverListG);
    lOutstandingRequestsG = 0;
    //
    // Initialize our zone allocator.
    //
    status = InitializeObjectAllocator();
    if(!NT_SUCCESS(status))
    {
#if DBG
        DbgPrint("AcdDriverEntry: InitializeObjectAllocator"
                 " failed. (status=0x%x)\n",
                 status);
#endif

        return status;
    }
    //
    // Create the device object.
    //
    pAcdDriverObjectG = pDriverObject;
    RtlInitUnicodeString(&deviceName, ACD_DEVICE_NAME);
    status = IoCreateDevice(
               pDriverObject,
               0,
               &deviceName,
               FILE_DEVICE_ACD,
               0,
               FALSE,
               &pAcdDeviceObjectG);

    if (!NT_SUCCESS(status)) {
        DbgPrint(
          "AcdDriverEntry: IoCreateDevice failed (status=0x%x)\n",
          status);
        FreeObjectAllocator();
        return status;
    }
    //
    // Initialize the driver object.
    //
    pDriverObject->DriverUnload = AcdUnload;
    for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
        pDriverObject->MajorFunction[i] = AcdDispatch;
    pDriverObject->FastIoDispatch = NULL;
    //
    // Initialize the connection timer.  This is
    // used to make sure pending requests aren't
    // blocked forever because the user-space
    // process died trying to make a connection.
    //
    IoInitializeTimer(pAcdDeviceObjectG, AcdConnectionTimer, NULL);

    {
        RTL_QUERY_REGISTRY_TABLE QueryTable[2];
        PWSTR EnableRedirNotifs = L"EnableRedirNotifications";
        PWSTR ParameterKey = L"RasAcd\\Parameters";
        ULONG ulEnableRedirNotifs = 0;
    
        //
        // Read the registry key that enables redir notifications
        //
        RtlZeroMemory(&QueryTable, sizeof(QueryTable));
        QueryTable[0].QueryRoutine = NULL;
        QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
        QueryTable[0].Name = EnableRedirNotifs;
        QueryTable[0].EntryContext = (PVOID)&ulEnableRedirNotifs;
        QueryTable[0].DefaultType = 0;
        status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
                                        ParameterKey,
                                        &QueryTable[0],
                                        NULL,
                                        NULL);

        if((status == STATUS_SUCCESS) && (ulEnableRedirNotifs != 0))
        {
            fAcdEnableRedirNotifs = TRUE;
        }

        // KdPrint(("AcdDriverEntry: EnableRedirNotifs=%d\n", fAcdEnableRedirNotifs));
        
        status = STATUS_SUCCESS;
    }                                    
    
    
    //
    // Create the worker thread.  We need
    // a thread because these operations can occur at
    // DPC irql.
    //
    KeInitializeEvent(
      &AcdRequestThreadEventG,
      NotificationEvent,
      FALSE);
    status = PsCreateSystemThread(
        &hSignalNotificationThreadG,
        THREAD_ALL_ACCESS,
        NULL,
        NULL,
        NULL,
        AcdNotificationRequestThread,
        NULL);
    if (!NT_SUCCESS(status)) {
        DbgPrint(
          "AcdDriverEntry: PsCreateSystemThread failed (status=0x%x)\n",
          status);
        IoDeleteDevice(pAcdDeviceObjectG);
        FreeObjectAllocator();
        return status;
    }

    //
    // Allocate memory for keeping track of disabled addresses
    //
    ALLOCATE_MEMORY(sizeof(ACD_DISABLED_ADDRESSES), pDisabledAddressesG);

    if(pDisabledAddressesG == NULL)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;
        IoDeleteDevice(pAcdDeviceObjectG);
        FreeObjectAllocator();
        return status;
    }

    ALLOCATE_MEMORY(sizeof(ACD_DISABLED_ADDRESS), pDisabledAddress);

    if(pDisabledAddress == NULL)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;
        IoDeleteDevice(pAcdDeviceObjectG);
        FREE_MEMORY(pDisabledAddressesG);
        FreeObjectAllocator();
        return status;
    }

    RtlZeroMemory(pDisabledAddressesG, sizeof(ACD_DISABLED_ADDRESSES));
    RtlZeroMemory(pDisabledAddress, sizeof(ACD_DISABLED_ADDRESS));

    InitializeListHead(&pDisabledAddressesG->ListEntry);
    InsertTailList(&pDisabledAddressesG->ListEntry, &pDisabledAddress->ListEntry);
    pDisabledAddressesG->ulNumAddresses = 1;
    
    pDisabledAddressesG->ulMaxAddresses = 10;
    
    //
    // If this fails then we have no way to wait for the thread to terminate
    //
    status = ObReferenceObjectByHandle (hSignalNotificationThreadG,
                                        0,
                                        NULL,
                                        KernelMode,
                                        &NotificationThread,
                                        NULL);
    ASSERT (NT_SUCCESS (status));
    return STATUS_SUCCESS;
} // DriverEntry



VOID
AcdUnload(
    IN PDRIVER_OBJECT pDriverObject
    )
{
    NTSTATUS status;

    //
    // Terminate the system thread and wait for it to exit
    //
    AcdStopThread = TRUE;
    KeSetEvent(&AcdRequestThreadEventG, 0, FALSE); // Wake the thread so it sees to exit
    //
    // Wait for the thread to leave the drivers address space.
    //
    KeWaitForSingleObject (NotificationThread, Executive, KernelMode, FALSE, 0);

    ObDereferenceObject (NotificationThread);
    ZwClose (hSignalNotificationThreadG);
    //
    // Make sure to unlink all driver
    // blocks before unloading!
    //
    IoDeleteDevice(pAcdDeviceObjectG);
    
    if(pDisabledAddressesG)
    {
        PLIST_ENTRY pListEntry;
        PACD_DISABLED_ADDRESS pDisabledAddress;
        
        while(!IsListEmpty(&pDisabledAddressesG->ListEntry))
        {
            pListEntry = RemoveHeadList(&pDisabledAddressesG->ListEntry);
            pDisabledAddress = 
            CONTAINING_RECORD(pListEntry, ACD_DISABLED_ADDRESS, ListEntry);

            FREE_MEMORY(pDisabledAddress);
        }
        
        FREE_MEMORY(pDisabledAddressesG);
        pDisabledAddressesG = NULL;
    }        

    //
    // Free zone allocator.
    //
    FreeObjectAllocator();

} // AcdUnload