/*++

Copyright (C) Microsoft Corporation, 1993 - 1999

Module Name:

    port.c

Abstract:

    This module contains the code to acquire and release the port
    from the port driver parport.sys.

Author:

    Anthony V. Ercolano 1-Aug-1992
    Norbert P. Kusters 22-Oct-1993

Environment:

    Kernel mode

Revision History :

--*/

#include "pch.h"

NTSTATUS
ParGetPortInfoFromPortDevice(
    IN OUT  PDEVICE_EXTENSION   Extension
    );

VOID
ParReleasePortInfoToPortDevice(
    IN  PDEVICE_EXTENSION   Extension
    );
    
VOID
ParFreePort(
    IN  PDEVICE_EXTENSION   Extension
    );

NTSTATUS
ParAllocPortCompletionRoutine(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp,
    IN  PVOID           Context
    );

BOOLEAN
ParAllocPort(
    IN  PDEVICE_EXTENSION   Extension
    );


NTSTATUS
ParGetPortInfoFromPortDevice(
    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;
    PARALLEL_PNP_INFORMATION    PnpInfo;
    IO_STATUS_BLOCK             IoStatus;
    NTSTATUS                    Status;

    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    //
    // Get Parallel Port Info
    //

    ASSERT(Extension->PortDeviceObject != NULL);

    Irp = IoBuildDeviceIoControlRequest(IOCTL_INTERNAL_GET_PARALLEL_PORT_INFO,
                                        Extension->PortDeviceObject,
                                        NULL, 
                                        0, 
                                        &PortInfo,
                                        sizeof(PARALLEL_PORT_INFORMATION),
                                        TRUE, 
                                        &Event, 
                                        &IoStatus);

    ASSERT(Irp->StackCount > 0);

    if (!Irp) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    Status = ParCallDriver(Extension->PortDeviceObject, Irp);

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

    Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);

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

    Status = IoStatus.Status;

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

    Extension->OriginalController   = PortInfo.OriginalController;
    Extension->Controller           = PortInfo.Controller;
    Extension->SpanOfController     = PortInfo.SpanOfController;
    Extension->TryAllocatePort      = PortInfo.TryAllocatePort;
    Extension->FreePort             = PortInfo.FreePort;
    Extension->QueryNumWaiters      = PortInfo.QueryNumWaiters;
    Extension->PortContext          = PortInfo.Context;
    
    if (Extension->SpanOfController < PARALLEL_REGISTER_SPAN) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Get Parallel Pnp Info
    //
    Irp = IoBuildDeviceIoControlRequest(IOCTL_INTERNAL_GET_PARALLEL_PNP_INFO,
                                        Extension->PortDeviceObject,
                                        NULL, 
                                        0, 
                                        &PnpInfo,
                                        sizeof(PARALLEL_PNP_INFORMATION),
                                        TRUE, 
                                        &Event, 
                                        &IoStatus);

    if (!Irp) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    Status = ParCallDriver(Extension->PortDeviceObject, Irp);

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

    Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);

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

    Status = IoStatus.Status;

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

    Extension->EcrController        = PnpInfo.EcpController;
    Extension->HardwareCapabilities = PnpInfo.HardwareCapabilities;
    Extension->TrySetChipMode       = PnpInfo.TrySetChipMode;
    Extension->ClearChipMode        = PnpInfo.ClearChipMode;
    Extension->TrySelectDevice      = PnpInfo.TrySelectDevice;
    Extension->DeselectDevice       = PnpInfo.DeselectDevice;
    Extension->FifoDepth            = PnpInfo.FifoDepth;
    Extension->FifoWidth            = PnpInfo.FifoWidth;
    

    //
    // get symbolic link name to use for this end of chain device
    //   object from ParPort
    //
    // if anything goes wrong, simply leave Extension->SymbolicLinkName alone 
    //   as it was cleared to all zeros via an RtlZeroMemory in 
    //   ParPnpCreateDevice(...) in parpnp.c
    //

    if( ( 0 == Extension->SymbolicLinkName.Length ) && PnpInfo.PortName ) {
      //
      // If we have no SymbolicLinkName and we have a port name, use the port
      //   name to initialize our symbolic link name in our device extension
      //
      UNICODE_STRING pathPrefix;
      UNICODE_STRING portName;
      ULONG          length;
      PWSTR          buffer;

      RtlInitUnicodeString(&pathPrefix, (PWSTR)L"\\DosDevices\\");
      RtlInitUnicodeString(&portName,   PnpInfo.PortName);

      length = pathPrefix.Length + portName.Length + sizeof(UNICODE_NULL);
      buffer = ExAllocatePool(PagedPool, length);

      if(buffer) {
        Extension->SymbolicLinkName.Buffer        = buffer;
        Extension->SymbolicLinkName.Length        = 0;
        Extension->SymbolicLinkName.MaximumLength = (USHORT)length;
        RtlAppendUnicodeStringToString(&Extension->SymbolicLinkName, &pathPrefix);
        RtlAppendUnicodeStringToString(&Extension->SymbolicLinkName, &portName);
      }
    }

    ParDumpV( ("ParGetPortInfoFromPortDevice(...):\n") );
    ParDumpV( (" - ClassName= %wZ , SymbolicLinkName= %wZ , PortDeviceObject= %08x\n", 
               &Extension->ClassName, &Extension->SymbolicLinkName, Extension->PortDeviceObject) );

    return Status;
}


VOID
ParReleasePortInfoToPortDevice(
    IN  PDEVICE_EXTENSION   Extension
    )

/*++

Routine Description:

    This routine will release the port information back to the port driver.

Arguments:

    Extension   - Supplies the device extension.

Return Value:

    None.

--*/
{
    //
    // ParPort treats this as a NO-OP in Win2K, so don't bother sending the IOCTL.
    //
    // In follow-on to Win2K parport may use this to page the entire driver as
    //   it was originally intended, so we'll turn this back on then.
    //

    return;
#if 0
    KEVENT          Event;
    PIRP            Irp;
    IO_STATUS_BLOCK IoStatus;
    NTSTATUS        Status;

    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    Irp = IoBuildDeviceIoControlRequest(IOCTL_INTERNAL_RELEASE_PARALLEL_PORT_INFO,
                                        Extension->PortDeviceObject,
                                        NULL, 
                                        0, 
                                        NULL, 
                                        0,
                                        TRUE, 
                                        &Event, 
                                        &IoStatus);

    if (!Irp) {
        return;
    }

    Status = ParCallDriver(Extension->PortDeviceObject, Irp);

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

    KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
#endif
}

VOID
ParFreePort(
    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.

--*/

{
    // Don't allow multiple releases

    if (Extension->bAllocated) {
        ParDump2(PARALLOCFREEPORT, ("port::ParFreePort: %x - calling ParPort's FreePort function\n", Extension->Controller) );
        Extension->FreePort(Extension->PortContext);
    } else {
        ParDump2(PARALLOCFREEPORT, ("port::ParFreePort: %x - we don't have the Port!!! (!Ext->bAllocated)\n", Extension->Controller) );
    }
        
    Extension->bAllocated = FALSE;
}


NTSTATUS
ParAllocPortCompletionRoutine(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp,
    IN  PVOID           Event
    )

/*++

Routine Description:

    This routine is the completion routine for a port allocate request.

Arguments:

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

Return Value:

    STATUS_MORE_PROCESSING_REQUIRED - The Irp still requires processing.

--*/

{
    UNREFERENCED_PARAMETER( Irp );
    UNREFERENCED_PARAMETER( DeviceObject );

    KeSetEvent((PKEVENT) Event, 0, FALSE);
    
    return STATUS_MORE_PROCESSING_REQUIRED;
}

BOOLEAN
ParAllocPort(
    IN  PDEVICE_EXTENSION   Extension
    )

/*++

Routine Description:

    This routine takes the given Irp and sends it down as a port allocate
    request.  When this request completes, the Irp will be queued for
    processing.

Arguments:

    Extension   - Supplies the device extension.

Return Value:

    FALSE   - The port was not successfully allocated.
    TRUE    - The port was successfully allocated.

--*/

{
    PIO_STACK_LOCATION  NextSp;
    KEVENT              Event;
    PIRP                Irp;
    BOOLEAN             bAllocated;
    NTSTATUS            Status;
    LARGE_INTEGER       Timeout;
/*
    ParDump(PARDUMP_VERBOSE_MAX,
            ("PARALLEL: "
             "ParAllocPort(...): %wZ\n",
             &Extension->SymbolicLinkName) );

    // Don't allow multiple allocations
    if (Extension->bAllocated) {
        ParDump(PARDUMP_VERBOSE_MAX,
                ("PARALLEL: "
                 "ParAllocPort(...): %wZ\n",
                 &Extension->SymbolicLinkName) );
        return TRUE;
    }
*/        
    ParDump2(PARALLOCFREEPORT,
            ("ParAllocPort: Enter %x\n", Extension->Controller) );

    // Don't allow multiple allocations
    if (Extension->bAllocated) {
        ParDump2(PARALLOCFREEPORT,
                ("ParAllocPort: %x Already allocated\n", Extension->Controller) );
        return TRUE;
    }

    Irp = Extension->CurrentOpIrp;
    
    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    NextSp = IoGetNextIrpStackLocation(Irp);
    NextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    NextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_PARALLEL_PORT_ALLOCATE;

    IoSetCompletionRoutine(Irp, 
                           ParAllocPortCompletionRoutine, 
                           &Event,
                           TRUE, 
                           TRUE, 
                           TRUE);

    ParCallDriver(Extension->PortDeviceObject, Irp);

    Timeout.QuadPart = -((LONGLONG) Extension->TimerStart*10*1000*1000);

    Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &Timeout);

    if (Status == STATUS_TIMEOUT) {
    
        IoCancelIrp(Irp);
        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
    }

    bAllocated = (BOOLEAN)NT_SUCCESS(Irp->IoStatus.Status);
    
    Extension->bAllocated = bAllocated;
    
    if (!bAllocated) {
        Irp->IoStatus.Status = STATUS_DEVICE_BUSY;
        ParDump2(PARALLOCFREEPORT,
                ("ParAllocPort: %x FAILED - DEVICE_BUSY\n", Extension->Controller) );
/*
        ParDump(PARDUMP_VERBOSE_MAX,
                ("PARALLEL: "
                 "ParAllocPort(...): %wZ FAILED - DEVICE_BUSY\n",
                 &Extension->SymbolicLinkName) );
*/
    }

    return bAllocated;
}