/*
 * UNIMODEM "Fakemodem" controllerless driver illustrative example
 *
 * (C) 2000 Microsoft Corporation
 * All Rights Reserved
 *
 */

#include "fakemodem.h"

#if DBG
ULONG DebugFlags=255;
#endif

UNICODE_STRING   DriverEntryRegPath;

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


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

    RTL_QUERY_REGISTRY_TABLE paramTable[3];
    ULONG zero = 0;
    ULONG debugLevel = 0;
    ULONG shouldBreak = 0;
    PWCHAR path;

    D_INIT(DbgPrint("FAKEMODEM: DriverEntry\n");)

    // Since the registry path parameter is a "counted" UNICODE string, it
    // might not be zero terminated.  For a very short time allocate memory
    // to hold the registry path zero terminated so that we can use it to
    // delve into the registry.

    path = ALLOCATE_PAGED_POOL(RegistryPath->Length+sizeof(WCHAR));

    if (path != NULL) 
    {
        RtlZeroMemory(&paramTable[0],sizeof(paramTable));
        RtlZeroMemory(path,RegistryPath->Length+sizeof(WCHAR));
        RtlMoveMemory(path,RegistryPath->Buffer,RegistryPath->Length);

        paramTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
        paramTable[0].Name = L"BreakOnEntry";
        paramTable[0].EntryContext = &shouldBreak;
        paramTable[0].DefaultType = REG_DWORD;
        paramTable[0].DefaultData = &zero;
        paramTable[0].DefaultLength = sizeof(ULONG);

        paramTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT;
        paramTable[1].Name = L"DebugFlags";
        paramTable[1].EntryContext = &debugLevel;
        paramTable[1].DefaultType = REG_DWORD;
        paramTable[1].DefaultData = &zero;
        paramTable[1].DefaultLength = sizeof(ULONG);


        // If the Debugflags registry key is not set then
        // provide full debugging information

        if (!NT_SUCCESS(RtlQueryRegistryValues(
                        RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, 
                        path, &paramTable[0], NULL, NULL))) 
        {
            shouldBreak = 0;
            debugLevel = 255;
        }

        FREE_POOL(path);
    }

#if DBG
    DebugFlags = debugLevel;
#endif

    if (shouldBreak) 
    {
        DbgBreakPoint();
    }

    // Pnp driver entry point

    DriverObject->DriverExtension->AddDevice = FakeModemAddDevice;

    // Initialize the driver object with driver's entry points

    DriverObject->DriverUnload = FakeModemUnload;

    DriverObject->MajorFunction[IRP_MJ_CREATE]            = FakeModemOpen;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]             = FakeModemClose;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP]           = FakeModemCleanup;
    DriverObject->MajorFunction[IRP_MJ_WRITE]             = FakeModemWrite;
    DriverObject->MajorFunction[IRP_MJ_READ]              = FakeModemRead;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]    = FakeModemIoControl;
    DriverObject->MajorFunction[IRP_MJ_PNP]               = FakeModemPnP;
    DriverObject->MajorFunction[IRP_MJ_POWER]             = FakeModemPower;

    D_INIT(DbgPrint("FAKEMODEM: End of DriverEntry\n");)

    return STATUS_SUCCESS;
}

VOID
FakeModemUnload(
    IN PDRIVER_OBJECT DriverObject
    )
{
    D_INIT(DbgPrint("FAKEMODEM: FakeModemUnload()\n");)

    return;
}

NTSTATUS
FakeModemAddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT Pdo
    )
{
    NTSTATUS           status=STATUS_SUCCESS;
    PDEVICE_OBJECT     Fdo;
    PDEVICE_EXTENSION  DeviceExtension;
    UNICODE_STRING     DeviceName;

    D_INIT(DbgPrint("FAKEMODEM: Fakemodem Add Device\n");)

    // Create our functional device object (FDO)
    
    status=IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), NULL, 
            FILE_DEVICE_SERIAL_PORT, FILE_AUTOGENERATED_DEVICE_NAME, 
            FALSE, &Fdo);

    if (status != STATUS_SUCCESS) 
    {
        return status;
    }

    Fdo->Flags |= DO_BUFFERED_IO;
    DeviceExtension = Fdo->DeviceExtension;
    DeviceExtension->DeviceObject = Fdo;

    // Attach our FDO to the PDO supplied
    
    DeviceExtension->LowerDevice = IoAttachDeviceToDeviceStack(Fdo, Pdo);
    if (NULL == DeviceExtension->LowerDevice) 
    {
        // Could not attach

        IoDeleteDevice(Fdo);
        return STATUS_UNSUCCESSFUL;
    }

    //  Try to create a ComX for it. don't care if it fails
    //  modem.sys creates a name for device that unimodem will use
    

    FakeModemHandleSymbolicLink(Pdo, TRUE, 
            &DeviceExtension->InterfaceNameString, Fdo);

    //  Initialise the spinlock
    
    KeInitializeSpinLock(&DeviceExtension->SpinLock);

    //  Initialize the device extension

    DeviceExtension->ReferenceCount=1;
    DeviceExtension->Removing=FALSE;
    DeviceExtension->Started=FALSE;
    DeviceExtension->OpenCount=0;
    KeInitializeEvent(&DeviceExtension->RemoveEvent, NotificationEvent, FALSE);

    //  Initialize the read and write queues

    InitializeListHead(&DeviceExtension->ReadQueue);
    DeviceExtension->CurrentReadIrp=NULL;

    InitializeListHead(&DeviceExtension->WriteQueue);
    DeviceExtension->CurrentWriteIrp=NULL;

    InitializeListHead(&DeviceExtension->MaskQueue);
    DeviceExtension->CurrentMaskIrp=NULL;

    // Clear this flag so the device object can be used

    Fdo->Flags &= ~(DO_DEVICE_INITIALIZING);

    return STATUS_SUCCESS;

}

NTSTATUS 
GetRegistryKeyValue (
    IN HANDLE Handle,
    IN PWCHAR KeyNameString,
    IN ULONG KeyNameStringLength,
    IN PVOID Data,
    IN ULONG DataLength
    )
{
    UNICODE_STRING keyName;
    ULONG length;
    PKEY_VALUE_FULL_INFORMATION fullInfo;

    NTSTATUS ntStatus = STATUS_NO_MEMORY;

    RtlInitUnicodeString (&keyName, KeyNameString);

    length = sizeof(KEY_VALUE_FULL_INFORMATION) + KeyNameStringLength + 
        DataLength;

    fullInfo = ExAllocatePool(PagedPool, length);

    if (fullInfo) 
    {
        ntStatus = ZwQueryValueKey(Handle, &keyName, 
                KeyValueFullInformation, fullInfo, length, &length);

        if (NT_SUCCESS(ntStatus)) 
        {
            // If there is enough room in the data buffer, copy the output

            if (DataLength >= fullInfo->DataLength) 
            {
                RtlCopyMemory(Data, 
                        ((PUCHAR) fullInfo) + fullInfo->DataOffset, 
                        fullInfo->DataLength);
            }
        }

        ExFreePool(fullInfo);
    }

    return ntStatus;
}

NTSTATUS
FakeModemHandleSymbolicLink(
    PDEVICE_OBJECT      Pdo,
    BOOLEAN             Create,
    PUNICODE_STRING     InterfaceName,
    PDEVICE_OBJECT      Fdo
    )

{

    UNICODE_STRING     SymbolicLink;
    ULONG              StringLength;
    NTSTATUS           Status;
    WCHAR              ComPort[80];
    HANDLE             keyHandle;
    RTL_QUERY_REGISTRY_TABLE paramTable[1];

    D_INIT(DbgPrint("FAKEMODEM: HandleSymbolicLink\n");)

    Status = IoOpenDeviceRegistryKey(Pdo, PLUGPLAY_REGKEY_DEVICE, 
            STANDARD_RIGHTS_READ, &keyHandle);

    SymbolicLink.Length=0;
    SymbolicLink.MaximumLength=sizeof(WCHAR)*256;
    SymbolicLink.Buffer=ExAllocatePool(PagedPool,
            SymbolicLink.MaximumLength+sizeof(WCHAR));

    if (SymbolicLink.Buffer == NULL) 
    {
        ZwClose(keyHandle);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(SymbolicLink.Buffer, SymbolicLink.MaximumLength);
    RtlAppendUnicodeToString(&SymbolicLink, L"\\");
    RtlAppendUnicodeToString(&SymbolicLink, OBJECT_DIRECTORY);
    RtlAppendUnicodeToString(&SymbolicLink, L"\\");

    Status=GetRegistryKeyValue(keyHandle, L"PortName", 
            sizeof(L"PortName"), ComPort, sizeof(ComPort));

    D_INIT(DbgPrint("FAKEMODEM: PortName %ws\n",ComPort);)

    if (Status != STATUS_SUCCESS) 
    {
        ExFreePool(SymbolicLink.Buffer);
        ZwClose(keyHandle);
        return Status;
    }

    RtlAppendUnicodeToString(&SymbolicLink, ComPort);

    ZwClose(keyHandle);

    if (Create) 
    {
        UNICODE_STRING     PdoName;

        PdoName.Length=0;
        PdoName.MaximumLength=sizeof(WCHAR)*256;
        PdoName.Buffer=ExAllocatePool(PagedPool,
                PdoName.MaximumLength+sizeof(WCHAR));

        if (PdoName.Buffer == NULL) 
        {
            ExFreePool(SymbolicLink.Buffer);

            return STATUS_INSUFFICIENT_RESOURCES;
        }

        RtlZeroMemory(PdoName.Buffer,PdoName.MaximumLength);

        Status=IoGetDeviceProperty(Pdo, DevicePropertyPhysicalDeviceObjectName,
                (ULONG)PdoName.MaximumLength, PdoName.Buffer, &StringLength);

        if (!NT_SUCCESS(Status)) 
        {
            D_INIT(DbgPrint("FAKEMODEM: IoGetDeviceProperty() failed %08lx\n",
                        Status);)

            ExFreePool(SymbolicLink.Buffer);

            return Status;
        }

        PdoName.Length+=(USHORT)StringLength-sizeof(UNICODE_NULL);
        D_INIT(DbgPrint("FAKEMODEM: PdoName: %ws\n",PdoName.Buffer);)
        Status=IoCreateSymbolicLink(&SymbolicLink, &PdoName);
        Status=IoRegisterDeviceInterface(Pdo, &GUID_CLASS_MODEM, NULL, 
                InterfaceName);

        if (NT_SUCCESS(Status))
        {
            IoSetDeviceInterfaceState(InterfaceName, TRUE);
        } else
        {
            D_INIT(DbgPrint("FAKEMODEM: IoRegisterDeviceInterface() failed %08lx\n",Status);)
        }


        Status = RtlWriteRegistryValue(RTL_REGISTRY_DEVICEMAP, L"SERIALCOMM",
                                     PdoName.Buffer, REG_SZ, ComPort,
                                     (wcslen(ComPort) + 1) * sizeof(WCHAR));

        if (!NT_SUCCESS(Status))
        {
            D_INIT(DbgPrint("FAKEMODEM: RtlWriteRegistryValue() failed %08lx\n",Status);)
            ExFreePool(SymbolicLink.Buffer);
            ExFreePool(PdoName.Buffer);

            return Status;
        }
        
        ExFreePool(PdoName.Buffer);

    } else {

        Status=IoDeleteSymbolicLink(&SymbolicLink);

        D_INIT(DbgPrint("FAKEMODEM: Deleted symbolic link\n");)

    }

    ExFreePool(SymbolicLink.Buffer);

    D_INIT(DbgPrint("FAKEMODEM: End of handle symbolic link\n");)
    return Status;

}

NTSTATUS
QueryDeviceCaps(
    PDEVICE_OBJECT    Pdo,
    PDEVICE_CAPABILITIES   Capabilities
    )

{

    PDEVICE_OBJECT       deviceObject=Pdo;
    PIRP                 irp;
    PIO_STACK_LOCATION   NextSp;
    KEVENT               Event;
    NTSTATUS             Status;

    // Get a pointer to the top most device object in the stack of 
    // devices, beginning with the deviceObject.

    while (deviceObject->AttachedDevice) 
    {
        deviceObject = deviceObject->AttachedDevice;
    }

    // Begin by allocating the IRP for this request.  Do not charge 
    // quota to the current process for this IRP.

    irp = IoAllocateIrp(
#if DBG
            (UCHAR)(deviceObject->StackSize+1),
#else
            deviceObject->StackSize,
#endif
            FALSE);

    if (irp == NULL)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

#if DBG
    {
        //  Setup a current stack location, so the debug code can see the 
        //  MJ value

        PIO_STACK_LOCATION   irpSp=IoGetNextIrpStackLocation(irp);

        irpSp->MajorFunction=IRP_MJ_PNP;
        IoSetNextIrpStackLocation(irp);
    }
#endif

    irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
    irp->IoStatus.Information = 0;

    RtlZeroMemory(Capabilities,sizeof(DEVICE_CAPABILITIES));

    Capabilities->Size=sizeof(DEVICE_CAPABILITIES);
    Capabilities->Version=1;
    Capabilities->Address=-1;
    Capabilities->UINumber=-1;

    // Get a pointer to the stack location of the first driver which will be
    // invoked.  This is where the function codes and parameters are set.

    NextSp = IoGetNextIrpStackLocation(irp);

    NextSp->MajorFunction=IRP_MJ_PNP;
    NextSp->MinorFunction=IRP_MN_QUERY_CAPABILITIES;
    NextSp->Parameters.DeviceCapabilities.Capabilities=Capabilities;

    Status=WaitForLowerDriverToCompleteIrp(deviceObject, irp, FALSE );

    IoFreeIrp(irp);

    return Status;

}

NTSTATUS
ModemSetRegistryKeyValue(
        IN PDEVICE_OBJECT   Pdo,
        IN ULONG            DevInstKeyType,
        IN PWCHAR           KeyNameString,
        IN ULONG            DataType,
        IN PVOID            Data,
        IN ULONG            DataLength)
{

    NTSTATUS ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    HANDLE Handle;
    UNICODE_STRING keyName;

    PAGED_CODE();

    D_ERROR(DbgPrint("MODEM: Current IRQL %d\n",KeGetCurrentIrql());)

    ntStatus = IoOpenDeviceRegistryKey(Pdo, DevInstKeyType, KEY_ALL_ACCESS, 
            &Handle);

    if (NT_SUCCESS(ntStatus))
    {
        RtlInitUnicodeString(&keyName,KeyNameString);

        ntStatus = ZwSetValueKey(Handle, &keyName, 0, DataType, Data, 
                DataLength);

        if (!NT_SUCCESS(ntStatus))
        {
            D_ERROR(DbgPrint("MODEM: Could not set value, %08lx\n",ntStatus);)
        }

    } else
    {
        ZwClose(Handle);

        D_ERROR(DbgPrint("MODEM: Could not open dev registry key, %08lx\n",
                    ntStatus);)
    }


    return ntStatus;
}

NTSTATUS 
ModemGetRegistryKeyValue (
    IN PDEVICE_OBJECT   Pdo,
    IN ULONG            DevInstKeyType,
    IN PWCHAR KeyNameString,
    IN PVOID Data,
    IN ULONG DataLength
    )
{
    UNICODE_STRING              keyName;
    ULONG                       length;
    PKEY_VALUE_PARTIAL_INFORMATION     PartialInfo;

    NTSTATUS                    ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    HANDLE                      Handle;

    PAGED_CODE();


    ntStatus = IoOpenDeviceRegistryKey(Pdo, DevInstKeyType, 
            STANDARD_RIGHTS_READ, &Handle);

    if (NT_SUCCESS(ntStatus)) 
    {

        RtlInitUnicodeString (&keyName, KeyNameString);

        length = sizeof(KEY_VALUE_FULL_INFORMATION) + DataLength;

        PartialInfo = ALLOCATE_PAGED_POOL(length);

        if (PartialInfo) 
        {
            ntStatus = ZwQueryValueKey (Handle, &keyName, 
                    KeyValuePartialInformation, PartialInfo, length, &length);

            if (NT_SUCCESS(ntStatus)) 
            {
                //
                // If there is enough room in the data buffer, copy the output
                //

                if (DataLength >= PartialInfo->DataLength) 
                {
                    RtlCopyMemory (Data, PartialInfo->Data, 
                            PartialInfo->DataLength);
                }
            } else 
            {

                D_ERROR(DbgPrint("MODEM: could not query value, %08lx\n",
                            ntStatus);)
            }

            FREE_POOL(PartialInfo);
        }

        ZwClose(Handle);

    } else {

        D_ERROR(DbgPrint("MODEM: could open device reg key, %08lx\n",ntStatus);)
    }

    return ntStatus;
}
