/*++

Copyright (c) 1993 Microsoft Corporation

Module Name:

    parlink.c

Abstract:

    This module contains the code for the parallel parlink driver.

    The purpose of the PARLINK driver is to supply peer to peer
    bidirectional communication along the parallel port.  The
    target application for this driver is RAS.  Before this driver
    existed RAS would allow a computer with a serial port to
    connect to another computer that was connected to the network and
    thus connect the networkless computer to the network.  This driver
    makes this capability possible on the parallel port which is faster
    than serial.  A minimal set of serial ioctls have been implemented
    in this driver to support RAS's use so that RAS may treat the
    parallel port in the same way as it treats the serial port when
    providing this function.

Author:

    Norbert P. Kusters 12-Nov-1993

Environment:

    Kernel mode

Revision History :

--*/

#include "ntddk.h"
#include "parallel.h"
#include "parlink.h"
#include "parlog.h"
#include "ntddser.h"

#ifdef POOL_TAGGING
#ifdef ExAllocatePool
#undef ExAllocatePool
#endif
#define ExAllocatePool(a,b) ExAllocatePoolWithTag(a,b,'LraP')
#endif


#if DBG
ULONG PlDebugLevel = 0;
#endif

static const PHYSICAL_ADDRESS PhysicalZero = {0};

#define MAX_LPT_NUMBER          100

#define PL_CHECK_LOOP           50000
#define PL_SLOW_CHECK_LOOP      100000

#define MAX_WRITE_RETRY_COUNT   3

#define PL_CHECK_INPUT(input,checkvalue)    \
        ((UCHAR) ((input)&0xF8) == (checkvalue))

#define PACKET_ACKNOWLEDGE(b)   PL_CHECK_INPUT(b,0xB0)
#define INCOMING_PACKET(b)      PL_CHECK_INPUT(b,0xA8)
#define DCD_DEAD(b)             PL_CHECK_INPUT(b,0x90)
#define LOW_NIBBLE_ACK(b)       PL_CHECK_INPUT(b,0x60)
#define HIGH_NIBBLE_ACK(b)      PL_CHECK_INPUT(b,0x98)

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

BOOLEAN
PlMakeNames(
    IN  ULONG           ParallelPortNumber,
    OUT PUNICODE_STRING PortName,
    OUT PUNICODE_STRING ClassName
    );

VOID
PlInitializeDeviceObject(
    IN  PDRIVER_OBJECT  DriverObject,
    IN  ULONG           ParallelPortNumber
    );

NTSTATUS
PlGetPortInfoFromPortDevice(
    IN OUT  PDEVICE_EXTENSION   Extension
    );

BOOLEAN
PlCreateSymbolicLink(
    IN OUT  PDEVICE_EXTENSION   Extension,
    IN      PUNICODE_STRING     DeviceName
    );

VOID
PlReadDpc(
    IN  PKDPC   ReadDpc,
    IN  PVOID   Extension,
    IN  PVOID   SystemArgument1,
    IN  PVOID   SystemArgument2
    );

VOID
PlAllocPort(
    IN  PDEVICE_EXTENSION   Extension
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,DriverEntry)
#pragma alloc_text(INIT,PlInitializeDeviceObject)
#pragma alloc_text(INIT,PlMakeNames)
#pragma alloc_text(INIT,PlGetPortInfoFromPortDevice)
#pragma alloc_text(INIT,PlCreateSymbolicLink)
#endif

NTSTATUS
DriverEntry(
    IN  PDRIVER_OBJECT  DriverObject,
    IN  PUNICODE_STRING RegistryPath
    )

/*++

Routine Description:

    This routine is called at system initialization time to initialize
    this driver.

Arguments:

    DriverObject    - Supplies the driver object.

    RegistryPath    - Supplies the registry path for this driver.

Return Value:

    STATUS_SUCCESS          - We could initialize at least one device.
    STATUS_NO_SUCH_DEVICE   - We could not initialize even one device.

--*/

{
    ULONG       i;

    for (i = 0; i < IoGetConfigurationInformation()->ParallelCount; i++) {
        PlInitializeDeviceObject(DriverObject, i);
    }

    if (!DriverObject->DeviceObject) {
        return STATUS_NO_SUCH_DEVICE;
    }

    //
    // Initialize the Driver Object with driver's entry points
    //

    DriverObject->MajorFunction[IRP_MJ_CREATE] = PlCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = PlCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP] = PlCleanup;
    DriverObject->MajorFunction[IRP_MJ_READ] = PlReadWrite;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = PlReadWrite;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = PlDeviceControl;

    DriverObject->DriverUnload = PlUnload;

    return STATUS_SUCCESS;
}

VOID
PlLogError(
    IN  PDRIVER_OBJECT      DriverObject,
    IN  PDEVICE_OBJECT      DeviceObject OPTIONAL,
    IN  PHYSICAL_ADDRESS    P1,
    IN  PHYSICAL_ADDRESS    P2,
    IN  ULONG               SequenceNumber,
    IN  UCHAR               MajorFunctionCode,
    IN  UCHAR               RetryCount,
    IN  ULONG               UniqueErrorValue,
    IN  NTSTATUS            FinalStatus,
    IN  NTSTATUS            SpecificIOStatus
    )

/*++

Routine Description:

    This routine allocates an error log entry, copies the supplied data
    to it, and requests that it be written to the error log file.

Arguments:

    DriverObject        - Supplies a pointer to the driver object for the
                            device.

    DeviceObject        - Supplies a pointer to the device object associated
                            with the device that had the error, early in
                            initialization, one may not yet exist.

    P1,P2               - Supplies the physical addresses for the controller
                            ports involved with the error if they are available
                            and puts them through as dump data.

    SequenceNumber      - Supplies a ulong value that is unique to an IRP over
                            the life of the irp in this driver - 0 generally
                            means an error not associated with an irp.

    MajorFunctionCode   - Supplies the major function code of the irp if there
                            is an error associated with it.

    RetryCount          - Supplies the number of times a particular operation
                            has been retried.

    UniqueErrorValue    - Supplies a unique long word that identifies the
                            particular call to this function.

    FinalStatus         - Supplies the final status given to the irp that was
                            associated with this error.  If this log entry is
                            being made during one of the retries this value
                            will be STATUS_SUCCESS.

    SpecificIOStatus    - Supplies the IO status for this particular error.

Return Value:

    None.

--*/

{
    PIO_ERROR_LOG_PACKET    errorLogEntry;
    PVOID                   objectToUse;
    SHORT                   dumpToAllocate;

    if (ARGUMENT_PRESENT(DeviceObject)) {
        objectToUse = DeviceObject;
    } else {
        objectToUse = DriverObject;
    }

    dumpToAllocate = 0;

    if (P1.LowPart != 0 || P1.HighPart != 0) {
        dumpToAllocate = (SHORT) sizeof(PHYSICAL_ADDRESS);
    }

    if (P2.LowPart != 0 || P2.HighPart != 0) {
        dumpToAllocate += (SHORT) sizeof(PHYSICAL_ADDRESS);
    }

    errorLogEntry = IoAllocateErrorLogEntry(objectToUse,
            (UCHAR) (sizeof(IO_ERROR_LOG_PACKET) + dumpToAllocate));

    if (!errorLogEntry) {
        return;
    }

    errorLogEntry->ErrorCode = SpecificIOStatus;
    errorLogEntry->SequenceNumber = SequenceNumber;
    errorLogEntry->MajorFunctionCode = MajorFunctionCode;
    errorLogEntry->RetryCount = RetryCount;
    errorLogEntry->UniqueErrorValue = UniqueErrorValue;
    errorLogEntry->FinalStatus = FinalStatus;
    errorLogEntry->DumpDataSize = dumpToAllocate;

    if (dumpToAllocate) {

        RtlCopyMemory(errorLogEntry->DumpData, &P1, sizeof(PHYSICAL_ADDRESS));

        if (dumpToAllocate > sizeof(PHYSICAL_ADDRESS)) {

            RtlCopyMemory(((PUCHAR) errorLogEntry->DumpData) +
                          sizeof(PHYSICAL_ADDRESS), &P2,
                          sizeof(PHYSICAL_ADDRESS));
        }
    }

    IoWriteErrorLogEntry(errorLogEntry);
}

VOID
PlInitializeDeviceObject(
    IN  PDRIVER_OBJECT  DriverObject,
    IN  ULONG           ParallelPortNumber
    )

/*++

Routine Description:

    This routine is called for every parallel port in the system.  It
    will create a class device upon connecting to the port device
    corresponding to it.

Arguments:

    DriverObject        - Supplies the driver object.

    ParallelPortNumber  - Supplies the number for this port.

Return Value:

    None.

--*/

{
    UNICODE_STRING      portName, className;
    NTSTATUS            status;
    PDEVICE_OBJECT      deviceObject;
    PDEVICE_EXTENSION   extension;
    PFILE_OBJECT        fileObject;


    // Cobble together the port and class device names.

    if (!PlMakeNames(ParallelPortNumber, &portName, &className)) {

        PlDump(
            PLERRORS,
            ("PARLINK: Could not form Unicode name string.\n")
            );

        PlLogError(DriverObject, NULL, PhysicalZero, PhysicalZero, 0, 0, 0, 1,
                   STATUS_SUCCESS, PAR_INSUFFICIENT_RESOURCES);

        return;
    }


    // Create the device object.

    status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION),
                            &className, FILE_DEVICE_PARALLEL_PORT, 0, FALSE,
                            &deviceObject);
    if (!NT_SUCCESS(status)) {

        PlDump(
            PLERRORS,
            ("PARLINK: Could not create a device for %wZ\n",
             &className)
            );

        PlLogError(DriverObject, NULL, PhysicalZero, PhysicalZero, 0, 0, 0, 2,
                   STATUS_SUCCESS, PAR_INSUFFICIENT_RESOURCES);

        goto Cleanup;
    }


    // Now that the device has been created,
    // set up the device extension.

    extension = deviceObject->DeviceExtension;
    RtlZeroMemory(extension, sizeof(DEVICE_EXTENSION));

    extension->DeviceObject = deviceObject;
    deviceObject->Flags |= DO_BUFFERED_IO;

    status = IoGetDeviceObjectPointer(&portName, FILE_READ_ATTRIBUTES,
                                      &fileObject,
                                      &extension->PortDeviceObject);
    if (!NT_SUCCESS(status)) {

        PlDump(
            PLERRORS,
            ("PARLINK: Unable to get device object pointer for port object.\n")
            );

        PlLogError(DriverObject, deviceObject, PhysicalZero, PhysicalZero,
                   0, 0, 0, 3, STATUS_SUCCESS, PAR_INSUFFICIENT_RESOURCES);

        IoDeleteDevice(deviceObject);

        goto Cleanup;
    }

    ObDereferenceObject(fileObject);

    extension->DeviceObject->StackSize =
            extension->PortDeviceObject->StackSize + 1;

    status = PlGetPortInfoFromPortDevice(extension);
    if (!NT_SUCCESS(status)) {

        PlDump(
            PLERRORS,
            ("PARLINK: Can't get port info from port device.\n")
            );

        PlLogError(DriverObject, deviceObject, PhysicalZero, PhysicalZero,
                   0, 0, 0, 4, STATUS_SUCCESS, PAR_INSUFFICIENT_RESOURCES);

        IoDeleteDevice(deviceObject);

        goto Cleanup;
    }


    // Set up the current irp and work queue.

    InitializeListHead(&extension->ReadQueue);
    InitializeListHead(&extension->WriteQueue);
    extension->NeedPortAllocation = TRUE;
    extension->PortAllocateIrp = NULL;
    InitializeListHead(&extension->WaitQueue);

    extension->WriteRetryCount = 0;


    // Set up the read/write timer dpc.

    KeInitializeTimer(&extension->ReadTimer);
    KeInitializeDpc(&extension->ReadDpc, PlReadDpc, extension);
    extension->ReadDpcTime.QuadPart = -(50*10*1000);


    KeInitializeSpinLock(&extension->ControlLock);


    // Set up the unload dependency tracking mechanism.

    extension->UnloadDependenciesCount = 0;
    KeInitializeEvent(&extension->UnloadOk, NotificationEvent, TRUE);


    // Set up some state information.

    extension->IsDcdUp = FALSE;
    extension->WaitMask = 0;


    // Set up the symbolic link for windows apps.

    if (!PlCreateSymbolicLink(extension, &className)) {

        PlDump(
            PLERRORS,
            ("PARLINK: Couldn't create the symbolic link\n"
             "-------  for port %wZ\n",
             &className)
            );

        PlLogError(DriverObject, deviceObject, PhysicalZero, PhysicalZero,
                   0, 0, 0, 5, STATUS_SUCCESS, PAR_INSUFFICIENT_RESOURCES);

        goto Cleanup;
    }

Cleanup:
    ExFreePool(portName.Buffer);
    ExFreePool(className.Buffer);
}

BOOLEAN
PlMakeNames(
    IN  ULONG           ParallelPortNumber,
    OUT PUNICODE_STRING PortName,
    OUT PUNICODE_STRING ClassName
    )

/*++

Routine Description:

    This routine generates the names \Device\ParallelPortN and
    \Device\ParallelLinkN.

Arguments:

    ParallelPortNumber  - Supplies the port number.

    PortName            - Returns the port name.

    ClassName           - Returns the class name.

Return Value:

    FALSE   - Failure.
    TRUE    - Success.

--*/
{
    UNICODE_STRING  prefix, digits;
    WCHAR           digitsBuffer[10];
    UNICODE_STRING  portSuffix, classSuffix;
    NTSTATUS        status;

    // Put together local variables for constructing names.

    RtlInitUnicodeString(&prefix, L"\\Device\\");
    RtlInitUnicodeString(&portSuffix, DD_PARALLEL_PORT_BASE_NAME_U);
    RtlInitUnicodeString(&classSuffix, L"ParallelLink");
    digits.Length = 0;
    digits.MaximumLength = 20;
    digits.Buffer = digitsBuffer;
    status = RtlIntegerToUnicodeString(ParallelPortNumber, 10, &digits);
    if (!NT_SUCCESS(status)) {
        return FALSE;
    }

    // Make the port name.

    PortName->Length = 0;
    PortName->MaximumLength = prefix.Length + portSuffix.Length +
                              digits.Length + sizeof(WCHAR);
    PortName->Buffer = ExAllocatePool(PagedPool, PortName->MaximumLength);
    if (!PortName->Buffer) {
        return FALSE;
    }
    RtlZeroMemory(PortName->Buffer, PortName->MaximumLength);
    RtlAppendUnicodeStringToString(PortName, &prefix);
    RtlAppendUnicodeStringToString(PortName, &portSuffix);
    RtlAppendUnicodeStringToString(PortName, &digits);


    // Make the class name.

    ClassName->Length = 0;
    ClassName->MaximumLength = prefix.Length + classSuffix.Length +
                               digits.Length + sizeof(WCHAR);
    ClassName->Buffer = ExAllocatePool(PagedPool, ClassName->MaximumLength);
    if (!ClassName->Buffer) {
        ExFreePool(PortName->Buffer);
        return FALSE;
    }
    RtlZeroMemory(ClassName->Buffer, ClassName->MaximumLength);
    RtlAppendUnicodeStringToString(ClassName, &prefix);
    RtlAppendUnicodeStringToString(ClassName, &classSuffix);
    RtlAppendUnicodeStringToString(ClassName, &digits);

    return TRUE;
}

NTSTATUS
PlGetPortInfoFromPortDevice(
    IN OUT  PDEVICE_EXTENSION   Extension
    )

/*++

Routine Description:

    This routine will request the port information from the port driver
    and fill it in the device extension.

Arguments:

    Extension   - Supplies the device extension.

Return Value:

    STATUS_SUCCESS  - Success.
    !STATUS_SUCCESS - Failure.

--*/

{
    KEVENT                      event;
    PIRP                        irp;
    PARALLEL_PORT_INFORMATION   portInfo;
    IO_STATUS_BLOCK             ioStatus;
    NTSTATUS                    status;

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    irp = IoBuildDeviceIoControlRequest(IOCTL_INTERNAL_GET_PARALLEL_PORT_INFO,
                                        Extension->PortDeviceObject,
                                        NULL, 0, &portInfo,
                                        sizeof(PARALLEL_PORT_INFORMATION),
                                        TRUE, &event, &ioStatus);

    if (!irp) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    status = IoCallDriver(Extension->PortDeviceObject, irp);

    if (!NT_SUCCESS(status)) {
        return status;
    }

    status = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

    if (!NT_SUCCESS(status)) {
        return status;
    }

    Extension->OriginalController = portInfo.OriginalController;
    Extension->Controller = portInfo.Controller;
    Extension->SpanOfController = portInfo.SpanOfController;
    Extension->FreePort = portInfo.FreePort;
    Extension->FreePortContext = portInfo.Context;

    if (Extension->SpanOfController < PARALLEL_REGISTER_SPAN) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    return status;
}

BOOLEAN
PlCreateSymbolicLink(
    IN OUT  PDEVICE_EXTENSION   Extension,
    IN      PUNICODE_STRING     DeviceName
    )

/*++

Routine Description:

    This routine creates a symbolic link from \DosDevices\LPT(M) to
    \Device\ParallelLinkN where M is the next available LPT.

Arguments:

    Extension   - Supplies the device extension.

    DeviceName  - Supplies the device name.

Return Value:

    FALSE   - Failure.
    TRUE    - Success.

--*/

{
    UNICODE_STRING  prefix, digits, linkName;
    WCHAR           digitsBuffer[10];
    NTSTATUS        status;
    ULONG           i;

    Extension->CreatedSymbolicLink = FALSE;

    RtlInitUnicodeString(&prefix, L"\\DosDevices\\BLPT");
    digits.Length = 0;
    digits.MaximumLength = 20;
    digits.Buffer = digitsBuffer;

    linkName.MaximumLength = prefix.Length +
                             3*sizeof(WCHAR);   // 2 digits + '\0'
    linkName.Buffer = ExAllocatePool(PagedPool, linkName.MaximumLength);
    if (!linkName.Buffer) {
        return FALSE;
    }
    RtlZeroMemory(linkName.Buffer, linkName.MaximumLength);

    for (i = 1; i < MAX_LPT_NUMBER; i++) {

        status = RtlIntegerToUnicodeString(i, 10, &digits);
        if (!NT_SUCCESS(status)) {
            ExFreePool(linkName.Buffer);
            return FALSE;
        }

        linkName.Length = 0;
        RtlAppendUnicodeStringToString(&linkName, &prefix);
        RtlAppendUnicodeStringToString(&linkName, &digits);

        status = IoCreateUnprotectedSymbolicLink(&linkName, DeviceName);

        if (NT_SUCCESS(status)) {
            Extension->CreatedSymbolicLink = TRUE;
            Extension->SymbolicLinkName = linkName;
            break;
        }
    }

    if (i >= MAX_LPT_NUMBER) {
        ExFreePool(linkName.Buffer);
        return FALSE;
    }

    return TRUE;
}

NTSTATUS
PlCreateClose(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This routine is the dispatch for create and close requests.  This
    request completes successfully.

Arguments:

    DeviceObject    - Supplies the device object.
    Irp             - Supplies the I/O request packet.

Return Value:

    STATUS_SUCCESS

--*/

{
    PlDump(PLIRPPATH, ("PARPORT:  In create or close with IRP: %x\n", Irp));

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

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

VOID
PlCancelRoutine(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This routine is used to cancel a queued IRP.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet being cancelled.

Return Value:

    None.

--*/

{
    RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
    IoReleaseCancelSpinLock(Irp->CancelIrql);

    Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = STATUS_CANCELLED;

    PlDump(PLIRPPATH, ("Completing irp with cancel status.\n"));

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

VOID
PlCancelIrp(
    IN      PDEVICE_OBJECT  DeviceObject,
    IN OUT  PIRP            Irp,
    IN      KIRQL           CancelIrql
    )

/*++

Routine Description:

    This routine cancels the given irp assuming that the cancel
    spin lock is already held.

Arguments:

    DeviceObject    - Supplies the device object.
    Irp             - Supplies the irp to cancel.
    CancelIrql      - Supplies the cancel irql.

Return Value:

    None.

--*/

{
    PDRIVER_CANCEL  cancelRoutine;

    Irp->Cancel = TRUE;
    if (cancelRoutine = Irp->CancelRoutine) {
        Irp->CancelIrql = CancelIrql;
        Irp->CancelRoutine = NULL;
        cancelRoutine(DeviceObject, Irp);
    } else {
        IoReleaseCancelSpinLock(CancelIrql);
    }
}

VOID
PlCancelQueue(
    IN OUT  PDEVICE_OBJECT  DeviceObject,
    IN OUT  PLIST_ENTRY     Queue,
    IN OUT  PKIRQL          CancelIrql
    )

/*++

Routine Description:

    This routine cancels all of the IRPs in the given queue.

Arguments:

    Queue       - Supplies the queue with the IRPs to cancel.
    CancelIrql  - Supplies the current cancel IRQL.

Return Value:

    None.

Notes:

    Must be called while holding the cancel spin lock.

--*/

{
    PIRP    currentIrp;

    while (!IsListEmpty(Queue)) {

        currentIrp = CONTAINING_RECORD(Queue->Blink, IRP,
                                       Tail.Overlay.ListEntry);

        PlCancelIrp(DeviceObject, currentIrp, *CancelIrql);
        IoAcquireCancelSpinLock(CancelIrql);
    }
}

NTSTATUS
PlCleanup(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This routine cancels all of the IRPs currently queued on
    the given device.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the cleanup IRP.

Return Value:

    STATUS_SUCCESS  - Success.

--*/

{
    PDEVICE_EXTENSION   extension;
    KIRQL               cancelIrql;
    PIRP                currentIrp;

    extension = DeviceObject->DeviceExtension;

    IoAcquireCancelSpinLock(&cancelIrql);

    if (extension->PortAllocateIrp) {
        currentIrp = extension->PortAllocateIrp;
        extension->PortAllocateIrp = NULL;
        PlCancelIrp(DeviceObject, currentIrp, cancelIrql);
        IoAcquireCancelSpinLock(&cancelIrql);
    }

    PlCancelQueue(DeviceObject, &extension->ReadQueue, &cancelIrql);

    PlCancelQueue(DeviceObject, &extension->WriteQueue, &cancelIrql);

    PlCancelQueue(DeviceObject, &extension->WaitQueue, &cancelIrql);

    IoReleaseCancelSpinLock(cancelIrql);

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

    PlDump(PLIRPPATH, ("Completing cleanup irp.\n"));

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

VOID
PlIncrementUnloadDependencies(
    IN OUT  PDEVICE_EXTENSION   Extension
    )

/*++

Routine Description:

    This routine increments the number of unload dependencies.

Arguments:

    Extension   - Supplies the device extension.

Return Value:

    None.

--*/

{
    KIRQL   oldIrql;

    KeAcquireSpinLock(&Extension->ControlLock, &oldIrql);
    if (++(Extension->UnloadDependenciesCount) == 1) {
        KeResetEvent(&Extension->UnloadOk);
    }
    KeReleaseSpinLock(&Extension->ControlLock, oldIrql);
}

BOOLEAN
PlDecrementUnloadDependencies(
    IN OUT  PDEVICE_EXTENSION   Extension
    )

/*++

Routine Description:

    This routine decrments the number of unload dependencies and
    return TRUE if the dependencies count reached zero.  This
    routine does not set the 'UnloadOk' event.  This is the responsibility
    of the caller if this routine returns TRUE.

Arguments:

    Extension   - Supplies the device extension.

Return Value:

    FALSE   - The dependencies count did not reach zero.
    TRUE    - The dependencies count reached zero.

--*/

{
    KIRQL   oldIrql;
    BOOLEAN r;

    KeAcquireSpinLock(&Extension->ControlLock, &oldIrql);
    if (--(Extension->UnloadDependenciesCount) == 0) {
        r = TRUE;
    } else {
        r = FALSE;
    }
    KeReleaseSpinLock(&Extension->ControlLock, oldIrql);

    return r;
}

VOID
PlFreePort(
    IN  PDEVICE_EXTENSION   Extension
    )

/*++

Routine Description:

    This routine calls the internal free port ioctl.  This routine
    should be called before completing an IRP that has allocated
    the port.

Arguments:

    Extension   - Supplies the device extension.

Return Value:

    None.

--*/

{
    Extension->FreePort(Extension->FreePortContext);
}

VOID
PlCompleteReadFromPort(
    IN OUT  PDEVICE_EXTENSION   Extension,
    IN OUT  PIRP                Irp
    )

/*++

Routine Description:

    This routine completes the given read irp from the data out
    on the port.

Arguments:

    Extension   - Supplies the device extension.
    Irp         - Supplies the read irp.

Return Value:

    None.

Notes:

    This needs to run at DISPATCH_LEVEL.

--*/

{
    PIO_STACK_LOCATION  irpSp;
    PUCHAR              p, pend;
    UCHAR               input, endofpacket;
    ULONG               i;

    // Acknowledge that we're ready to receive packet.

    WriteOutput(Extension->Controller, 0x06);
    PlDump(PLINFO, ("Just put out a 0x06\n"));

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    p = Irp->AssociatedIrp.SystemBuffer;
    pend = p + irpSp->Parameters.Read.Length;
    input = 0;
    Irp->IoStatus.Status = STATUS_TIMEOUT;

    for (;;) {

        // Try to pick up the low nibble.  Check for end of packet.

        endofpacket = ((~input)|0xC0)&0xF8;

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

            input = ReadInput(Extension->Controller);
            if (PL_CHECK_INPUT(input, endofpacket)) {

                PlDump(PLINFO, ("Got end of packet.\n"));
                i = PL_CHECK_LOOP;
                Irp->IoStatus.Status = STATUS_SUCCESS;
                break;
            } else if (!(input&((UCHAR) 0x80)) &&
                       input == ReadInput(Extension->Controller) &&
                       input == ReadInput(Extension->Controller) &&
                       input == ReadInput(Extension->Controller)) {

                break;
            }

            KeStallExecutionProcessor(1);
        }

        if (i == PL_CHECK_LOOP) {
            PlDump(PLERRORS, ("Could not get low nibble, got %x\n", input));
            break;
        }

        if (p == pend) {
            break;
        }

        // acknowledge receipt of low nibble.
        WriteOutput(Extension->Controller, 0xFC);
        PlDump(PLINFO, ("Just put out a 0xFC\n"));

        *p = (input>>3);

        // Try to pick up the high nibble.

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

            input = ReadInput(Extension->Controller);
            if (input&((UCHAR) 0x80) &&
                input == ReadInput(Extension->Controller) &&
                input == ReadInput(Extension->Controller) &&
                input == ReadInput(Extension->Controller)) {

                break;
            }

            KeStallExecutionProcessor(1);
        }

        if (i == PL_CHECK_LOOP) {
            PlDump(PLERRORS, ("Could not get high nibble, got %x\n", input));
            break;
        }

        // acknowledge receipt of high nibble.
        WriteOutput(Extension->Controller, 0x03);
        PlDump(PLINFO, ("Just put out a 0x03\n"));

        *p |= ((input<<1)&0xF0);
        p++;
    }


    // Say that we're idle here.
    WriteOutput(Extension->Controller, 0x0A);
    PlDump(PLINFO, ("Just put out a 0x0A\n"));


    // Complete the irp with what we got.  Or fail the irp if
    // we didn't get anything.

    if (Irp->IoStatus.Status == STATUS_SUCCESS) {
        Irp->IoStatus.Information = p - (PUCHAR) Irp->AssociatedIrp.SystemBuffer;
    } else if (p == pend) {
        Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
    }

    PlDump(PLIRPPATH, ("Completing read irp from port.\n"));
}

BOOLEAN
PlCompleteWriteToPort(
    IN OUT  PDEVICE_EXTENSION   Extension,
    IN OUT  PIRP                Irp
    )

/*++

Routine Description:

    This routine completes the given write irp to the port.

Arguments:

    Extension   - Supplies the device extension.
    Irp         - Supplies the read irp.

Return Value:

    FALSE   - The write operation failed.
    TRUE    - The write operation succeeded.

Notes:

    This routine must run at DISPATCH_LEVEL.

--*/

{
    PIO_STACK_LOCATION  irpSp;
    PUCHAR              p;
    ULONG               i, j;
    UCHAR               input;

    // Say that we're ready to transfer.

    WriteOutput(Extension->Controller, 0x05);
    PlDump(PLINFO, ("Just put out a 0x05\n"));

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

        input = ReadInput(Extension->Controller);
        if (PACKET_ACKNOWLEDGE(input)) {
            break;
        }

        KeStallExecutionProcessor(1);
    }

    if (i == PL_SLOW_CHECK_LOOP) {

        PlDump(PLERRORS, ("Could not get packet ack, got %x\n", input));
        return FALSE;
    }

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    p = Irp->AssociatedIrp.SystemBuffer;

    for (i = 0; i < irpSp->Parameters.Write.Length; i++) {

        WriteOutput(Extension->Controller, p[i]|0x10);

        for (j = 0; j < PL_CHECK_LOOP; j++) {

            input = ReadInput(Extension->Controller);
            if (LOW_NIBBLE_ACK(input)) {
                break;
            }

            KeStallExecutionProcessor(1);
        }

        if (j == PL_CHECK_LOOP) {
            PlDump(PLERRORS, ("Could not get low nibble ack, got %x\n", input));
            break;
        }

        WriteOutput(Extension->Controller, (p[i]>>4)&0x0F);

        for (j = 0; j < PL_CHECK_LOOP; j++) {

            input = ReadInput(Extension->Controller);
            if (HIGH_NIBBLE_ACK(input)) {
                break;
            }

            KeStallExecutionProcessor(1);
        }

        if (j == PL_CHECK_LOOP) {
            PlDump(PLERRORS, ("Could not get low nibble ack, got %x\n", input));
            break;
        }
    }

    if (i != irpSp->Parameters.Write.Length) {

        // Say that we're idle again.
        WriteOutput(Extension->Controller, 0x0A);
        PlDump(PLINFO, ("Just put out a 0x0A\n"));

        PlDump(PLIRPPATH, ("write failed.\n"));
        return FALSE;
    }

    // Since the whole packet was sent, put out the end packet signal.
    WriteOutput(Extension->Controller, (((~p[i-1])>>4)|0x08)&0x0F);

    Irp->IoStatus.Information = irpSp->Parameters.Write.Length;
    Irp->IoStatus.Status = STATUS_SUCCESS;
    PlDump(PLIRPPATH, ("Completing successful write to port.\n"));

    return TRUE;
}

VOID
PlCompleteAllWaits(
    IN OUT  PDEVICE_EXTENSION   Extension,
    IN      ULONG               EventMask
    )

/*++

Routine Description:

    This routine completes all of the wait IRPs with the given
    event mask.

Arguments:

    Extension   - Supplies the device extension.
    EventMask   - Supplies the event that occurred.

Return Value:

    None.

--*/

{
    LIST_ENTRY  list;
    KIRQL       cancelIrql;
    PLIST_ENTRY head;
    PIRP        irp;
    PULONG      p;

    InitializeListHead(&list);


    // Take all of them at once while holding the cancel spin lock ...

    IoAcquireCancelSpinLock(&cancelIrql);

    while (!IsListEmpty(&Extension->WaitQueue)) {

        head = RemoveHeadList(&Extension->WaitQueue);
        irp = CONTAINING_RECORD(head, IRP, Tail.Overlay.ListEntry);
        IoSetCancelRoutine(irp, NULL);

        InsertTailList(&list, head);
    }

    IoReleaseCancelSpinLock(cancelIrql);


    // ... then complete all of them.

    while (!IsListEmpty(&list)) {

        head = RemoveHeadList(&list);
        irp = CONTAINING_RECORD(head, IRP, Tail.Overlay.ListEntry);

        p = irp->AssociatedIrp.SystemBuffer;
        *p = EventMask;

        irp->IoStatus.Information = sizeof(ULONG);
        irp->IoStatus.Status = STATUS_SUCCESS;

        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }
}

VOID
PlSetDcd(
    IN OUT  PDEVICE_EXTENSION   Extension,
    IN      BOOLEAN             IsDcdUp
    )

/*++

Routine Description:

    This routine sets the DCD active variable in the extension
    and return the wait irps if the DCD changed state.

Arguments:

    Extension   - Supplies the device extension.
    IsDcdUp     - Supplies the new DCD state.

Return Value:

    None.

--*/

{
    KIRQL   oldIrql;
    BOOLEAN completeWait;

    KeAcquireSpinLock(&Extension->ControlLock, &oldIrql);
    if (IsDcdUp == Extension->IsDcdUp) {
        completeWait = FALSE;
    } else {
        Extension->IsDcdUp = IsDcdUp;
        if (Extension->WaitMask & SERIAL_EV_RLSD) {
            completeWait = TRUE;
        } else {
            completeWait = FALSE;
        }
    }
    KeReleaseSpinLock(&Extension->ControlLock, oldIrql);

    if (completeWait) {
        PlCompleteAllWaits(Extension, SERIAL_EV_RLSD);
    }
}

NTSTATUS
PlAllocPortCompletionRoutine(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp,
    IN  PVOID           Extension
    )

/*++

Routine Description:

    This is the completion routine for an alloc port request.  This
    routine is called when this driver owns the parallel port.  This
    routine will examine the work queue and attempt to satisfy a
    read or write request.

Arguments:

    DeviceObject    - Supplies the device object.
    Irp             - Supplies the I/O request packet.
    Extension       - Supplies the device extension.

Return Value:

    STATUS_MORE_PROCESSING_REQUIRED

--*/

{
    PDEVICE_EXTENSION   extension;
    NTSTATUS            status;
    KIRQL               oldIrql, cancelIrql;
    PIRP                irp;
    PLIST_ENTRY         head;
    BOOLEAN             spawnDpc;
    BOOLEAN             writeSucceeded;
    UCHAR               input;

    extension = Extension;
    status = Irp->IoStatus.Status;
    IoAcquireCancelSpinLock(&cancelIrql);
    extension->PortAllocateIrp = NULL;
    IoReleaseCancelSpinLock(cancelIrql);
    IoFreeIrp(Irp);

    if (!NT_SUCCESS(status)) {
        PlDump(PLERRORS, ("Allocate port ioctl failed.\n"));

        PlLogError(DeviceObject->DriverObject, DeviceObject,
                   extension->OriginalController, PhysicalZero,
                   0, 0, 0, 6, STATUS_SUCCESS, PAR_PORT_ALLOCATE_FAILED);

        if (PlDecrementUnloadDependencies(extension)) {
            KeSetEvent(&extension->UnloadOk, 0, FALSE);
        }
        return STATUS_MORE_PROCESSING_REQUIRED;
    }


    // Check to see if the overflow bytes can be used to satisfy a read
    // request.

    KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);

    input = ReadInput(extension->Controller);

    if (DCD_DEAD(input)) {
        PlSetDcd(extension, FALSE);
    }

    IoAcquireCancelSpinLock(&cancelIrql);

    if (!IsListEmpty(&extension->ReadQueue) && INCOMING_PACKET(input)) {
        head = RemoveHeadList(&extension->ReadQueue);
        irp = CONTAINING_RECORD(head, IRP, Tail.Overlay.ListEntry);
        IoSetCancelRoutine(irp, NULL);
        IoReleaseCancelSpinLock(cancelIrql);
        PlCompleteReadFromPort(extension, irp);
        KeLowerIrql(oldIrql);

        if (irp->IoStatus.Status == STATUS_SUCCESS) {
            PlSetDcd(extension, TRUE);
        }

        IoCompleteRequest(irp, IO_PARALLEL_INCREMENT);
        PlFreePort(extension);
        PlAllocPort(extension);
        return STATUS_MORE_PROCESSING_REQUIRED;
    }

    if (!IsListEmpty(&extension->WriteQueue)) {
        head = RemoveHeadList(&extension->WriteQueue);
        irp = CONTAINING_RECORD(head, IRP, Tail.Overlay.ListEntry);
        IoSetCancelRoutine(irp, NULL);
        IoReleaseCancelSpinLock(cancelIrql);

        writeSucceeded = PlCompleteWriteToPort(extension, irp);
        KeLowerIrql(oldIrql);

        if (writeSucceeded) {

            // If the call succeeded then we're done.

            extension->WriteRetryCount = 0;
            IoCompleteRequest(irp, IO_PARALLEL_INCREMENT);

        } else if (++(extension->WriteRetryCount) >= MAX_WRITE_RETRY_COUNT) {

            // If the call failed and this is this is the third time
            // a write fails without a successful write then
            // return STATUS_TIMEOUT and lower DCD.

            PlSetDcd(extension, FALSE);
            extension->WriteRetryCount = 0;
            irp->IoStatus.Status = STATUS_TIMEOUT;
            PlDump(PLIRPPATH, ("Completing write irp with timeout status.\n"));
            IoCompleteRequest(irp, IO_NO_INCREMENT);

        } else {

            // The write timed out.  Try it again later.

            IoAcquireCancelSpinLock(&cancelIrql);
            if (irp->Cancel) {
                IoReleaseCancelSpinLock(cancelIrql);
                irp->IoStatus.Status = STATUS_CANCELLED;
                IoCompleteRequest(irp, IO_NO_INCREMENT);
            } else {
                InsertHeadList(&extension->WriteQueue,
                               &irp->Tail.Overlay.ListEntry);
                IoSetCancelRoutine(irp, PlCancelRoutine);
                IoReleaseCancelSpinLock(cancelIrql);
            }
        }

        PlFreePort(extension);
        PlAllocPort(extension);
        return STATUS_MORE_PROCESSING_REQUIRED;
    }

    extension->NeedPortAllocation = TRUE;
    if (IsListEmpty(&extension->ReadQueue)) {
        spawnDpc = FALSE;
    } else {
        PlIncrementUnloadDependencies(extension);
        spawnDpc = TRUE;
    }
    IoReleaseCancelSpinLock(cancelIrql);
    KeLowerIrql(oldIrql);

    if (spawnDpc) {
        KeSetTimer(&extension->ReadTimer, extension->ReadDpcTime,
                   &extension->ReadDpc);
    }

    PlFreePort(extension);
    return STATUS_MORE_PROCESSING_REQUIRED;
}

VOID
PlAllocPort(
    IN  PDEVICE_EXTENSION   Extension
    )

/*++

Routine Description:

    This routine calls the internal alloc port ioctl.

Arguments:

    Extension   - Supplies the device extension.

Return Value:

    The IRP that was created for the alloc request.

--*/

{
    PIRP                irp;
    KIRQL               cancelIrql;
    PIO_STACK_LOCATION  irpSp;

    irp = IoAllocateIrp(Extension->DeviceObject->StackSize, FALSE);

    if (!irp) {
        PlDump(PLERRORS, ("Couldn't allocate irp for alloc port ioctl.\n"));
        if (PlDecrementUnloadDependencies(Extension)) {
            KeSetEvent(&Extension->UnloadOk, 0, FALSE);
        }
        return;
    }

    irpSp = IoGetNextIrpStackLocation(irp);
    irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    irpSp->Parameters.DeviceIoControl.IoControlCode =
            IOCTL_INTERNAL_PARALLEL_PORT_ALLOCATE;

    IoSetCompletionRoutine(irp, PlAllocPortCompletionRoutine, Extension,
                           TRUE, TRUE, TRUE);

    IoAcquireCancelSpinLock(&cancelIrql);
    Extension->PortAllocateIrp = irp;
    IoReleaseCancelSpinLock(cancelIrql);

    IoCallDriver(Extension->PortDeviceObject, irp);
}

VOID
PlReadDpc(
    IN  PKDPC   ReadDpc,
    IN  PVOID   Extension,
    IN  PVOID   SystemArgument1,
    IN  PVOID   SystemArgument2
    )

/*++

Routine Description:

    This routine is a timer dpc that is set to wake up because
    the other computer wasn't ready for transfer.  This routine
    will transfer if possible.

Arguments:

    ReadDpc         - Supplies this dpc.
    Extension       - Supplies the device extension.
    SystemArgument1 - Not used.
    SystemArgument2 - Not used.

Return Value:

    None.

--*/
{
    PDEVICE_EXTENSION   extension;
    KIRQL               cancelIrql;
    BOOLEAN             needAlloc;

    extension = Extension;
    IoAcquireCancelSpinLock(&cancelIrql);
    if (needAlloc = extension->NeedPortAllocation) {
        PlIncrementUnloadDependencies(extension);
        extension->NeedPortAllocation = FALSE;
    }
    IoReleaseCancelSpinLock(cancelIrql);

    if (needAlloc) {
        PlAllocPort(extension);
    }

    if (PlDecrementUnloadDependencies(extension)) {
        KeSetEvent(&extension->UnloadOk, 0, FALSE);
    }
}

NTSTATUS
PlReadWrite(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This routine implements the read and write dispatch.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the read IRP.

Return Value:

    STATUS_PENDING  - The request is pending.
    STATUS_SUCCESS  - The request was completed successfully.

--*/

{
    PIO_STACK_LOCATION  irpSp;
    PDEVICE_EXTENSION   extension;
    KIRQL               cancelIrql;
    BOOLEAN             needAlloc;

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    extension = DeviceObject->DeviceExtension;

    Irp->IoStatus.Information = 0;

    // Check for the trivial cases.

    if ((irpSp->MajorFunction == IRP_MJ_READ &&
         !irpSp->Parameters.Read.Length) ||
        (irpSp->MajorFunction == IRP_MJ_WRITE &&
         !irpSp->Parameters.Write.Length)) {

        Irp->IoStatus.Status = STATUS_SUCCESS;
        PlDump(PLIRPPATH, ("Completing irp with length zero.\n"));
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_SUCCESS;
    }


    // Writes of more that one packet are not allowed.

    if (irpSp->MajorFunction == IRP_MJ_WRITE &&
        irpSp->Parameters.Write.Length > 2048) {

        Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
        PlDump(PLIRPPATH, ("Completing write with invalid parameter.\n"));
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_INVALID_PARAMETER;
    }


    // If the case is not trivial then queue it up and
    // determine whether or not there needs to be an
    // alloc sent down to the port driver.

    IoAcquireCancelSpinLock(&cancelIrql);

    if (Irp->Cancel) {
        IoReleaseCancelSpinLock(cancelIrql);
        Irp->IoStatus.Status = STATUS_CANCELLED;
        PlDump(PLIRPPATH, ("Completing irp as cancelled from dispatch.\n"));
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_CANCELLED;
    }

    IoSetCancelRoutine(Irp, PlCancelRoutine);

    IoMarkIrpPending(Irp);

    if (irpSp->MajorFunction == IRP_MJ_READ) {
        InsertTailList(&extension->ReadQueue, &Irp->Tail.Overlay.ListEntry);
    } else {
        InsertTailList(&extension->WriteQueue, &Irp->Tail.Overlay.ListEntry);
    }

    if (needAlloc = extension->NeedPortAllocation) {
        PlIncrementUnloadDependencies(extension);
        extension->NeedPortAllocation = FALSE;
    }

    IoReleaseCancelSpinLock(cancelIrql);

    if (needAlloc) {
        PlAllocPort(extension);
    }

    return STATUS_PENDING;
}

NTSTATUS
PlDeviceControlCompletionRoutine(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp,
    IN  PVOID           Extension
    )

/*++

Routine Description:

    This is the completion routine for an alloc port request that was
    made with a device control request.  This routine will attempt
    to satisfy the device control request and then free the port.

Arguments:

    DeviceObject    - Supplies the device object.
    Irp             - Supplies the I/O request packet.
    Extension       - Supplies the device extension.

Return Value:

    STATUS_SUCCESS

--*/

{
    PIO_STACK_LOCATION  irpSp;
    PDEVICE_EXTENSION   extension;

    irpSp = IoGetCurrentIrpStackLocation(Irp);
    extension = Extension;
    Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = STATUS_SUCCESS;

    switch (irpSp->Parameters.DeviceIoControl.IoControlCode) {

        case IOCTL_SERIAL_SET_DTR:
            // Put out an idle signal.
            WriteOutput(extension->Controller, 0x0A);
            break;

        case IOCTL_SERIAL_CLR_DTR:
            // Put out a dead signal.
            WriteOutput(extension->Controller, 0x02);

            PlSetDcd(extension, FALSE);
            break;

        default:
            Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
            break;

    }

    PlFreePort(extension);

    return Irp->IoStatus.Status;
}

NTSTATUS
PlDeviceControl(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )

/*++

Routine Description:

    This is the device control dispatch.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the read IRP.

Return Value:

    STATUS_PENDING  - The request is pending.
    STATUS_SUCCESS  - The request was completed successfully.

--*/

{
    PIO_STACK_LOCATION  irpSp, nextSp;
    PDEVICE_EXTENSION   extension;
    NTSTATUS            status;
    PULONG              p;
    KIRQL               oldIrql, cancelIrql;

    Irp->IoStatus.Information = 0;
    irpSp = IoGetCurrentIrpStackLocation(Irp);
    extension = DeviceObject->DeviceExtension;

    switch (irpSp->Parameters.DeviceIoControl.IoControlCode) {

        case IOCTL_SERIAL_GET_COMMSTATUS:
            if (irpSp->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(SERIAL_STATUS)) {

                status = STATUS_BUFFER_TOO_SMALL;
            } else {
                RtlZeroMemory(Irp->AssociatedIrp.SystemBuffer,
                              sizeof(SERIAL_STATUS));
                Irp->IoStatus.Information = sizeof(SERIAL_STATUS);
                status = STATUS_SUCCESS;
            }
            break;

        case IOCTL_SERIAL_GET_WAIT_MASK:
            if (irpSp->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(ULONG)) {

                status = STATUS_BUFFER_TOO_SMALL;
            } else {
                p = Irp->AssociatedIrp.SystemBuffer;
                KeAcquireSpinLock(&extension->ControlLock, &oldIrql);
                *p = extension->WaitMask;
                KeReleaseSpinLock(&extension->ControlLock, oldIrql);
                status = STATUS_SUCCESS;
                Irp->IoStatus.Information = sizeof(ULONG);
            }
            break;

        case IOCTL_SERIAL_GET_MODEMSTATUS:
            if (irpSp->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(ULONG)) {

                status = STATUS_BUFFER_TOO_SMALL;
            } else {
                p = Irp->AssociatedIrp.SystemBuffer;
                KeAcquireSpinLock(&extension->ControlLock, &oldIrql);
                *p = extension->IsDcdUp ? SERIAL_DCD_STATE : 0;
                KeReleaseSpinLock(&extension->ControlLock, oldIrql);
                status = STATUS_SUCCESS;
                Irp->IoStatus.Information = sizeof(ULONG);
            }
            break;

        case IOCTL_SERIAL_PURGE:
            if (irpSp->Parameters.DeviceIoControl.InputBufferLength <
                sizeof(ULONG)) {

                status = STATUS_BUFFER_TOO_SMALL;
            } else {
                p = Irp->AssociatedIrp.SystemBuffer;
                if ((*p) & SERIAL_PURGE_TXABORT) {
                    IoAcquireCancelSpinLock(&cancelIrql);
                    PlCancelQueue(DeviceObject, &extension->WriteQueue,
                                  &cancelIrql);
                    IoReleaseCancelSpinLock(cancelIrql);
                }

                if ((*p) & SERIAL_PURGE_RXABORT) {
                    IoAcquireCancelSpinLock(&cancelIrql);
                    PlCancelQueue(DeviceObject, &extension->ReadQueue,
                                  &cancelIrql);
                    IoReleaseCancelSpinLock(cancelIrql);
                }
                status = STATUS_SUCCESS;
            }
            break;

        case IOCTL_SERIAL_SET_WAIT_MASK:
            if (irpSp->Parameters.DeviceIoControl.InputBufferLength <
                sizeof(ULONG)) {

                status = STATUS_BUFFER_TOO_SMALL;
            } else {

                // First complete any pending wait irp.

                PlCompleteAllWaits(extension, 0);


                // Set the wait mask.

                p = Irp->AssociatedIrp.SystemBuffer;
                KeAcquireSpinLock(&extension->ControlLock, &oldIrql);
                extension->WaitMask = *p;
                KeReleaseSpinLock(&extension->ControlLock, oldIrql);

                status = STATUS_SUCCESS;
            }
            break;

        case IOCTL_SERIAL_WAIT_ON_MASK:
            IoAcquireCancelSpinLock(&cancelIrql);
            if (Irp->Cancel) {
                status = STATUS_CANCELLED;
            } else {
                IoSetCancelRoutine(Irp, PlCancelRoutine);
                InsertTailList(&extension->WaitQueue,
                               &Irp->Tail.Overlay.ListEntry);
                IoMarkIrpPending(Irp);
                status = STATUS_PENDING;
            }
            IoReleaseCancelSpinLock(cancelIrql);
            break;

        case IOCTL_SERIAL_SET_DTR:
        case IOCTL_SERIAL_CLR_DTR:

            // piggy back these to an alloc port call.

            IoMarkIrpPending(Irp);
            status = STATUS_PENDING;
            nextSp = IoGetNextIrpStackLocation(Irp);
            nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
            nextSp->Parameters.DeviceIoControl.IoControlCode =
                    IOCTL_INTERNAL_PARALLEL_PORT_ALLOCATE;

            IoSetCompletionRoutine(Irp, PlDeviceControlCompletionRoutine,
                                   extension, TRUE, FALSE, FALSE);

            IoCallDriver(extension->PortDeviceObject, Irp);
            break;

        default:
            status = STATUS_INVALID_PARAMETER;
            break;

    }

    if (status != STATUS_PENDING) {
        Irp->IoStatus.Status = status;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }

    return status;
}

VOID
PlUnload(
    IN  PDRIVER_OBJECT  DriverObject
    )

/*++

Routine Description:

    This routine loops through the device list and cleans up after
    each of the devices.

Arguments:

    DriverObject    - Supplies the driver object.

Return Value:

    None.

--*/

{
    PDEVICE_OBJECT      currentDevice;
    PDEVICE_EXTENSION   extension;

    PlDump(
        PLUNLOAD,
        ("PARLINK: In PlUnload\n")
        );

    while (currentDevice = DriverObject->DeviceObject) {

        extension = currentDevice->DeviceExtension;

        if (extension->CreatedSymbolicLink) {
            IoDeleteSymbolicLink(&extension->SymbolicLinkName);
            ExFreePool(extension->SymbolicLinkName.Buffer);
        }

        KeWaitForSingleObject(&extension->UnloadOk, Executive,
                              KernelMode, FALSE, NULL);

        IoDeleteDevice(currentDevice);
    }
}