#include "pch.h"

NTSTATUS PptPdoStartDevice( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryRemove( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoRemoveDevice( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoCancelRemove( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoStopDevice( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryStop( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoCancelStop( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryDeviceRelations( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryCapabilities( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryDeviceText( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryId( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryPnpDeviceState( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoQueryBusInformation( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoSurpriseRemoval( PDEVICE_OBJECT DevObj, PIRP Irp );
NTSTATUS PptPdoDefaultPnpHandler( PDEVICE_OBJECT DevObj, PIRP Irp );

PDRIVER_DISPATCH 
PptPdoPnpDispatchTable[] =
{ 
    PptPdoStartDevice,          // IRP_MN_START_DEVICE                 0x00
    PptPdoQueryRemove,          // IRP_MN_QUERY_REMOVE_DEVICE          0x01
    PptPdoRemoveDevice,         // IRP_MN_REMOVE_DEVICE                0x02
    PptPdoCancelRemove,         // IRP_MN_CANCEL_REMOVE_DEVICE         0x03
    PptPdoStopDevice,           // IRP_MN_STOP_DEVICE                  0x04
    PptPdoQueryStop,            // IRP_MN_QUERY_STOP_DEVICE            0x05
    PptPdoCancelStop,           // IRP_MN_CANCEL_STOP_DEVICE           0x06
    PptPdoQueryDeviceRelations, // IRP_MN_QUERY_DEVICE_RELATIONS       0x07
    PptPdoDefaultPnpHandler,    // IRP_MN_QUERY_INTERFACE              0x08
    PptPdoQueryCapabilities,    // IRP_MN_QUERY_CAPABILITIES           0x09
    PptPdoDefaultPnpHandler,    // IRP_MN_QUERY_RESOURCES              0x0A
    PptPdoDefaultPnpHandler,    // IRP_MN_QUERY_RESOURCE_REQUIREMENTS  0x0B
    PptPdoQueryDeviceText,      // IRP_MN_QUERY_DEVICE_TEXT            0x0C
    PptPdoDefaultPnpHandler,    // IRP_MN_FILTER_RESOURCE_REQUIREMENTS 0x0D
    PptPdoDefaultPnpHandler,    // no such PnP request                 0x0E
    PptPdoDefaultPnpHandler,    // IRP_MN_READ_CONFIG                  0x0F
    PptPdoDefaultPnpHandler,    // IRP_MN_WRITE_CONFIG                 0x10
    PptPdoDefaultPnpHandler,    // IRP_MN_EJECT                        0x11
    PptPdoDefaultPnpHandler,    // IRP_MN_SET_LOCK                     0x12
    PptPdoQueryId,              // IRP_MN_QUERY_ID                     0x13
    PptPdoQueryPnpDeviceState,  // IRP_MN_QUERY_PNP_DEVICE_STATE       0x14
    PptPdoQueryBusInformation,  // IRP_MN_QUERY_BUS_INFORMATION        0x15
    PptPdoDefaultPnpHandler,    // IRP_MN_DEVICE_USAGE_NOTIFICATION    0x16
    PptPdoSurpriseRemoval,      // IRP_MN_SURPRISE_REMOVAL             0x17
    PptPdoDefaultPnpHandler     // IRP_MN_QUERY_LEGACY_BUS_INFORMATION 0x18
};


NTSTATUS
PptPdoStartDevice(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    ) 
{
    PPDO_EXTENSION  pdx = Pdo->DeviceExtension;

    pdx->DeviceStateFlags = PPT_DEVICE_STARTED;
    KeSetEvent(&pdx->PauseEvent, 0, FALSE); // unpause any worker thread

    PptRegGetDeviceParameterDword( Pdo, L"Event22Delay", &pdx->Event22Delay );

    //
    // Register device interface for Legacy LPTx interface PDOs and set the interface active
    //  - succeed start even if the device interface code fails
    //
    if( PdoTypeRawPort == pdx->PdoType ) {

        // This is a legacy interface "raw port" PDO, don't set interface for other types of PDOs 

        NTSTATUS  status;
        BOOLEAN   setActive = FALSE;

        if( NULL == pdx->DeviceInterface.Buffer ) {
            // Register device interface
            status = IoRegisterDeviceInterface( Pdo, &GUID_PARCLASS_DEVICE, NULL, &pdx->DeviceInterface );
            if( STATUS_SUCCESS == status ) {
                setActive = TRUE;
            }
        }

        if( (TRUE == setActive) && (FALSE == pdx->DeviceInterfaceState) ) {
            // set interface active
            status = IoSetDeviceInterfaceState( &pdx->DeviceInterface, TRUE );
            if( STATUS_SUCCESS == status ) {
                pdx->DeviceInterfaceState = TRUE;
            }
        }
    }

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoQueryRemove(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION  pdx = Pdo->DeviceExtension;

    // DDpnp2( ("PptPdoQueryRemove\n") );

    // PnP won't remove us if there are open handles to us - so WE don't need to check for open handles

    pdx->DeviceStateFlags |= (PPT_DEVICE_REMOVE_PENDING | PAR_DEVICE_PAUSED);
    KeClearEvent(&pdx->PauseEvent); // pause any worker thread

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoRemoveDevice(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION  pdx     = Pdo->DeviceExtension;
    NTSTATUS        status;

    pdx->DeviceStateFlags = PAR_DEVICE_PAUSED;
    KeClearEvent(&pdx->PauseEvent); // pause any worker thread

    // Set Device Interface inactive for PdoTypeRawPort - other PDO types don't have device interfaces
    if( PdoTypeRawPort == pdx->PdoType ) {
        if( (pdx->DeviceInterface.Buffer != NULL) && (TRUE == pdx->DeviceInterfaceState) ) {
            IoSetDeviceInterfaceState( &pdx->DeviceInterface, FALSE );
            pdx->DeviceInterfaceState = FALSE;
        }
    }

    // If we were not reported in the last FDO BusRelations enumeration then it is safe to delete self
    if( pdx->DeleteOnRemoveOk ) {
        DD((PCE)pdx,DDT,"PptPdoRemoveDevice - DeleteOnRemoveOk == TRUE - cleaning up self\n");
        P4DestroyPdo( Pdo );
        status = P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
        return status;
    } else {
        return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
    }
}


NTSTATUS
PptPdoCancelRemove(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION  pdx = Pdo->DeviceExtension;

    pdx->DeviceStateFlags &= ~(PPT_DEVICE_REMOVE_PENDING | PAR_DEVICE_PAUSED);
    KeSetEvent(&pdx->PauseEvent, 0, FALSE); // unpause any worker thread

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoStopDevice(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION  pdx = Pdo->DeviceExtension;

    // DDpnp2( ("PptPdoStopDevice\n") );

    pdx->DeviceStateFlags |=  PAR_DEVICE_PAUSED;
    pdx->DeviceStateFlags &= ~PPT_DEVICE_STARTED;
    KeClearEvent(&pdx->PauseEvent); // pause any worker thread

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoQueryStop(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION  pdx = Pdo->DeviceExtension;

    // DDpnp2( ("PptPdoQueryStop\n") );

    pdx->DeviceStateFlags  |= (PPT_DEVICE_STOP_PENDING | PAR_DEVICE_PAUSED);
    KeClearEvent(&pdx->PauseEvent); // pause any worker thread

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoCancelStop(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION  pdx = Pdo->DeviceExtension;

    pdx->DeviceStateFlags &= ~PPT_DEVICE_STOP_PENDING;
    KeSetEvent(&pdx->PauseEvent, 0, FALSE); // unpause any worker thread

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoQueryDeviceRelations(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION        pdx         = Pdo->DeviceExtension;
    PIO_STACK_LOCATION    irpSp       = IoGetCurrentIrpStackLocation( Irp );
    DEVICE_RELATION_TYPE  requestType = irpSp->Parameters.QueryDeviceRelations.Type;
    NTSTATUS              status      = Irp->IoStatus.Status;
    ULONG_PTR             info        = Irp->IoStatus.Information;

    if( TargetDeviceRelation == requestType ) {
        PDEVICE_RELATIONS devRel = ExAllocatePool( PagedPool, sizeof(DEVICE_RELATIONS) );
        if( devRel ) {
            devRel->Count = 1;
            ObReferenceObject( Pdo );
            devRel->Objects[0] = Pdo;
            status = STATUS_SUCCESS;
            info   = (ULONG_PTR)devRel;
        } else {
            status = STATUS_NO_MEMORY;
        }
    } else {
        DD((PCE)pdx,DDT,"PptPdoQueryDeviceRelations - unhandled request Type = %d\n",requestType);
    }
    return P4CompleteRequest( Irp, status, info );
}


NTSTATUS
PptPdoQueryCapabilities(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION      pdx = Pdo->DeviceExtension;
    PIO_STACK_LOCATION  irpSp = IoGetCurrentIrpStackLocation( Irp );

    irpSp->Parameters.DeviceCapabilities.Capabilities->RawDeviceOK       = TRUE;
    if( PdoTypeRawPort == pdx->PdoType ) {
        // This is the legacy LPTx interface device - no driver should
        //  ever be installed for this so don't bother the user with a popup.
        irpSp->Parameters.DeviceCapabilities.Capabilities->SilentInstall = TRUE;
    }

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoQueryDeviceText(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION      pdx        = Pdo->DeviceExtension;
    PIO_STACK_LOCATION  irpSp      = IoGetCurrentIrpStackLocation( Irp );
    PWSTR               buffer     = NULL;
    ULONG               bufLen;
    ULONG_PTR           info;
    NTSTATUS            status;

    if( DeviceTextDescription == irpSp->Parameters.QueryDeviceText.DeviceTextType ) {

        //
        // DeviceTextDescription is: catenation of MFG+<SPACE>+MDL
        //
        if( pdx->Mfg && pdx->Mdl ) {
            //
            // Construct UNICODE string to return from the ANSI strings
            //   that we have in our extension
            //
            // need space for <SPACE> and terminating NULL
            //
            bufLen = strlen( (const PCHAR)pdx->Mfg ) + strlen( (const PCHAR)pdx->Mdl ) + 2 * sizeof(CHAR);
            bufLen *= ( sizeof(WCHAR)/sizeof(CHAR) );
            buffer = ExAllocatePool( PagedPool | POOL_COLD_ALLOCATION, bufLen );
            if( buffer ) {
                RtlZeroMemory( buffer, bufLen );
                _snwprintf( buffer, bufLen/2, L"%S %S", pdx->Mfg, pdx->Mdl );
                DD((PCE)pdx,DDT,"PptPdoQueryDeviceText - DeviceTextDescription - <%S>\n",buffer);
                status = STATUS_SUCCESS;
            } else {
                status = STATUS_NO_MEMORY;
            }
        } else {
            DD((PCE)pdx,DDE,"PptPdoQueryDeviceText - MFG and/or MDL NULL - FAIL DeviceTextDescription\n");
            status = STATUS_UNSUCCESSFUL;
        }
    } else if( DeviceTextLocationInformation == irpSp->Parameters.QueryDeviceText.DeviceTextType ) {

        //
        // DeviceTextLocationInformation is LPTx or LPTx.y (note that
        //   this is also the symlink name minus the L"\\DosDevices\\"
        //   prefix)
        //

        if( pdx->Location ) {
            bufLen = strlen( (const PCHAR)pdx->Location ) + sizeof(CHAR);
            bufLen *= ( sizeof(WCHAR)/sizeof(CHAR) );
            buffer = ExAllocatePool( PagedPool | POOL_COLD_ALLOCATION, bufLen );
            if( buffer ) {
                RtlZeroMemory( buffer, bufLen );
                _snwprintf( buffer, bufLen/2, L"%S", pdx->Location );
                DD((PCE)pdx,DDT,"PptPdoQueryDeviceText - DeviceTextLocationInformation - <%S>\n",buffer);
                status = STATUS_SUCCESS;
            } else {
                status = STATUS_NO_MEMORY;
            }
        } else {
            DD((PCE)pdx,DDE,"PptPdoQueryDeviceText - Location NULL - FAIL DeviceTextLocationInformation\n");
            status = STATUS_UNSUCCESSFUL;
        }
    } else {

        // Unknown DeviceTextType - don't change anything in IRP
        buffer = NULL;
        status = Irp->IoStatus.Status;
    }

    if( (STATUS_SUCCESS == status) && buffer ) {
        info = (ULONG_PTR)buffer;
    } else {
        if( buffer ) {
            ExFreePool( buffer );
        }
        info = Irp->IoStatus.Information;
    }

    return P4CompleteRequest( Irp, status, info );
}


NTSTATUS
PptPdoQueryId( PDEVICE_OBJECT Pdo, PIRP Irp )
{
    PPDO_EXTENSION      pdx        = Pdo->DeviceExtension;
    PIO_STACK_LOCATION  irpSp      = IoGetCurrentIrpStackLocation( Irp );
    PWSTR               buffer     = NULL;
    ULONG               bufLen;
    NTSTATUS            status;
    ULONG_PTR           info;

    switch( irpSp->Parameters.QueryId.IdType ) {
        
    case BusQueryDeviceID :
        //
        // DeviceID generation: catenate MFG and MDL fields from the
        //   IEEE 1284 device ID string (no space between fields), append
        //   MFG+MDL catenation to LPTENUM\ prefix
        //
        if( pdx->Mfg && pdx->Mdl ) {
            //
            // Construct UNICODE string to return from the ANSI strings
            //   that we have in our extension
            //
            CHAR prefix[] = "LPTENUM\\";
            // sizeof(prefix) provides space for NULL terminator
            bufLen = sizeof(prefix) + strlen( (const PCHAR)pdx->Mfg ) + strlen( (const PCHAR)pdx->Mdl );
            bufLen *= ( sizeof(WCHAR)/sizeof(CHAR) );
            buffer = ExAllocatePool( PagedPool | POOL_COLD_ALLOCATION, bufLen );
            if( buffer ) {
                RtlZeroMemory( buffer, bufLen );
                _snwprintf( buffer, bufLen/2, L"%S%S%S", prefix, pdx->Mfg, pdx->Mdl );
                P4SanitizeId( buffer ); // replace any illegal characters with underscore
                DD((PCE)pdx,DDT,"PptPdoQueryId - BusQueryDeviceID - <%S>\n",buffer);
                status = STATUS_SUCCESS;
            } else {
                status = STATUS_NO_MEMORY;
            }

        } else {

            DD((PCE)pdx,DDE,"PptPdoQueryId - MFG and/or MDL NULL - FAIL BusQueryDeviceID\n");
            status = STATUS_UNSUCCESSFUL;

        }
        break;
        
    case BusQueryInstanceID :
        //
        // InstanceID is LPTx or LPTx.y Location of the device (note
        //   that this is also the symlink name minus the
        //   \DosDevices\ prefix)
        //
        if( pdx->Location ) {
            //
            // Construct UNICODE string to return from the ANSI string
            //   that we have in our extension
            //
            bufLen = strlen( (const PCHAR)pdx->Location ) + sizeof(CHAR);
            bufLen *= ( sizeof(WCHAR)/sizeof(CHAR) );
            buffer = ExAllocatePool( PagedPool | POOL_COLD_ALLOCATION, bufLen );
            if( buffer ) {
                RtlZeroMemory( buffer, bufLen );
                _snwprintf( buffer, bufLen/2, L"%S", pdx->Location );
                P4SanitizeId( buffer ); // replace any illegal characters with underscore
                DD((PCE)pdx,DDT,"PptPdoQueryId - BusQueryInstanceID - <%S>\n",buffer);
                status = STATUS_SUCCESS;
            } else {
                status = STATUS_NO_MEMORY;
            }
        } else {

            DD((PCE)pdx,DDE,"PptPdoQueryId - Location NULL - FAIL BusQueryInstanceID\n");
            status = STATUS_UNSUCCESSFUL;

        }
        break;
        
    case BusQueryHardwareIDs :
        //
        // HardwareID generation:
        //
        // Generate MfgMdlCrc string as follows:
        //   1) catenate MFG and MDL fields
        //   2) generate checksum on MFG+MDL catenation
        //   3) truncate MFG+MDL catenation
        //   4) append checksum
        //
        // Return as HardwareID MULTI_SZ: LPTENUM\%MfgMdlCrc% followed by bare %MfgMdlCrc%
        //
        //   example: LPTENUM\Acme_CorpFooBarPrint3FA5\0Acme_CorpFooBarPrint3FA5\0\0
        //
        if( pdx->Mfg && pdx->Mdl ) {
            ULONG  lengthOfMfgMdlBuffer = strlen( (const PCHAR)pdx->Mfg ) + strlen( (const PCHAR)pdx->Mdl ) + sizeof(CHAR);
            PCHAR  mfgMdlBuffer         = ExAllocatePool( PagedPool | POOL_COLD_ALLOCATION, lengthOfMfgMdlBuffer );

            if( mfgMdlBuffer ) {
                const CHAR  prefix[]              = "LPTENUM\\";
                const ULONG mfgMdlTruncationLimit = 20;
                const ULONG checksumLength        = 4;
                USHORT      checksum;

                // 1) catenate MFG and MDL fields and 2) generate checksum on catenation
                RtlZeroMemory( mfgMdlBuffer, lengthOfMfgMdlBuffer );
                _snprintf( mfgMdlBuffer, lengthOfMfgMdlBuffer, "%s%s", pdx->Mfg, pdx->Mdl );
                GetCheckSum( mfgMdlBuffer, (USHORT)strlen(mfgMdlBuffer), &checksum );

                //
                // alloc buffer large enough for result returned to PnP,
                // include space for 4 checksum chars (twice) + 1 NULL between strings + 2 termination chars (MULTI_SZ)
                //
                bufLen = strlen( prefix ) + 2 * mfgMdlTruncationLimit + 2 * checksumLength + 3 * sizeof(CHAR); 
                bufLen *= (sizeof(WCHAR)/sizeof(CHAR)); // convert to size needed for WCHARs
                buffer = ExAllocatePool( PagedPool | POOL_COLD_ALLOCATION, bufLen );
                if( buffer ) {
                    ULONG wcharsWritten;
                    RtlZeroMemory( buffer, bufLen );

                    // Construct the HardwareID MULTI_SZ:
                    //
                    //  Write the first Hardware ID: LPTENUM\xxx
                    wcharsWritten = _snwprintf( buffer, bufLen/2, L"%S%.20S%04X", prefix, mfgMdlBuffer, checksum );

                    //  Skip forward a UNICODE_NULL past the end of the first Hardware ID and write the second
                    //    Hardware ID: bare xxx
                    _snwprintf( buffer+wcharsWritten+1, bufLen/2-wcharsWritten-1, L"%.20S%04X", mfgMdlBuffer, checksum );

                    ExFreePool( mfgMdlBuffer );

                    DD((PCE)pdx,DDT,"PptPdoQueryId - BusQueryHardwareIDs 1st ID - <%S>\n",buffer);
                    DD((PCE)pdx,DDT,"PptPdoQueryId - BusQueryHardwareIDs 2nd ID - <%S>\n",buffer+wcslen(buffer)+1);                    
                    // replace any illegal characters with underscore, preserve UNICODE_NULLs
                    P4SanitizeMultiSzId( buffer, bufLen/2 );

                    status = STATUS_SUCCESS;

                    // printing looks for PortName in the devnode - Pdo's Location is the PortName
                    P4WritePortNameToDevNode( Pdo, pdx->Location );

                } else {
                    ExFreePool( mfgMdlBuffer );
                    DD((PCE)pdx,DDT,"PptPdoQueryId - no pool for buffer - FAIL BusQueryHardwareIDs\n");
                    status = STATUS_INSUFFICIENT_RESOURCES;
                }

            } else {
                DD((PCE)pdx,DDT,"PptPdoQueryId - no pool for mfgMdlBuffer - FAIL BusQueryHardwareIDs\n");
                status = STATUS_INSUFFICIENT_RESOURCES;
            }
        } else {
            DD((PCE)pdx,DDT,"PptPdoQueryId - MFG and/or MDL NULL - FAIL BusQueryHardwareIDs\n");
            status = STATUS_UNSUCCESSFUL;
        }

        //
        // Save the MFG and MDL fields from the IEEE 1284 Device ID string under the 
        //   "<DevNode>\Device Parameters" key so that user mode code (e.g., printing)
        //   can retrieve the fields.
        //
        PptWriteMfgMdlToDevNode( Pdo, pdx->Mfg, pdx->Mdl );

        break;
        
    case BusQueryCompatibleIDs :

        //
        // Printing group specified that we not report compatible IDs - 2000-04-24
        //
#define PPT_REPORT_COMPATIBLE_IDS 0
#if (0 == PPT_REPORT_COMPATIBLE_IDS)

        DD((PCE)pdx,DDT,"PptPdoQueryId - BusQueryCompatibleIDs - query not supported\n");
        status = Irp->IoStatus.Status;

#else
        //
        // Return the compatible ID string reported by device, if any
        //

        if( pdx->Cid ) {
            //
            // Construct UNICODE string to return from the ANSI string
            //   that we have in our extension
            //
            bufLen = strlen( pdx->Cid ) + 2 * sizeof(CHAR);
            bufLen *= ( sizeof(WCHAR)/sizeof(CHAR) );
            buffer = ExAllocatePool( PagedPool | POOL_COLD_ALLOCATION, bufLen );
            if( buffer ) {
                RtlZeroMemory( buffer, bufLen );
                _snwprintf( buffer, bufLen/2, L"%S", pdx->Cid );
                DD((PCE)pdx,DDT,"PptPdoQueryId - BusQueryCompatibleIDs - <%S>\n",buffer);

                //
                // convert the 1284 ID representation of a Compatible ID seperator (',') into
                //   a MULTI_SZ - (i.e., scan the WSTR and replace any L',' with L'\0')
                //
                {
                    PWCHAR p = buffer;
                    while( *p ) {
                        if( L',' == *p ) {
                            *p = L'\0';
                        }
                        ++p;
                    }
                }

                // replace any illegal characters with underscore, preserve UNICODE_NULLs
                P4SanitizeMultiSzId( buffer, bufLen/2 );

                status = STATUS_SUCCESS;

            } else {
                DD((PCE)pdx,DDT,"PptPdoQueryId - no pool - FAIL BusQueryCompatibleIDs\n");
                status = STATUS_INSUFFICIENT_RESOURCES;
            }
        } else {
            DD((PCE)pdx,DDT,"PptPdoQueryId - CID NULL - BusQueryCompatibleIDs\n");
            status = Irp->IoStatus.Status;
        }
#endif //  #if (0 == PPT_REPORT_COMPATIBLE_IDS)

        break;
        
    default :
        //
        // Invalid irpSp->Parameters.QueryId.IdType
        //
        DD((PCE)pdx,DDT,"PptPdoQueryId - unrecognized irpSp->Parameters.QueryId.IdType\n");
        status = Irp->IoStatus.Status;
    }


    if( (STATUS_SUCCESS == status) && buffer ) {
        info = (ULONG_PTR)buffer;
    } else {
        if( buffer ) {
            ExFreePool( buffer );
        }
        info = Irp->IoStatus.Information;
    }

    return P4CompleteRequest( Irp, status, info );
}


NTSTATUS
PptPdoQueryPnpDeviceState( PDEVICE_OBJECT Pdo, PIRP Irp )
{
    PPDO_EXTENSION      pdx    = Pdo->DeviceExtension;
    NTSTATUS            status = Irp->IoStatus.Status;
    ULONG_PTR           info   = Irp->IoStatus.Information;


    if( PdoTypeRawPort == pdx->PdoType ) {
        info |= PNP_DEVICE_DONT_DISPLAY_IN_UI;
        status = STATUS_SUCCESS;
    }
    return P4CompleteRequest( Irp, status, info );
}

NTSTATUS
PptPdoQueryBusInformation( PDEVICE_OBJECT Pdo, PIRP Irp )
{
    PPDO_EXTENSION  pdx = Pdo->DeviceExtension;
    NTSTATUS        status;
    ULONG_PTR       info;

    if( pdx->PdoType != PdoTypeRawPort ) {

        //
        // we are a "real" device enumerated by parport - report BusInformation
        //

        PPNP_BUS_INFORMATION  pBusInfo = ExAllocatePool( PagedPool, sizeof(PNP_BUS_INFORMATION) );

        if( pBusInfo ) {

            pBusInfo->BusTypeGuid   = GUID_BUS_TYPE_LPTENUM;
            pBusInfo->LegacyBusType = PNPBus;
            pBusInfo->BusNumber     = 0;

            status                  = STATUS_SUCCESS;
            info                    = (ULONG_PTR)pBusInfo;

        } else {

            // no pool
            status = STATUS_NO_MEMORY;
            info   = Irp->IoStatus.Information;

        }

    } else {

        //
        // we are a pseudo device (Legacy Interface Raw Port PDO LPTx) - don't report BusInformation
        //
        status = Irp->IoStatus.Status;
        info   = Irp->IoStatus.Information;

    }

    return P4CompleteRequest( Irp, status, info );
}


NTSTATUS
PptPdoSurpriseRemoval( 
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    PPDO_EXTENSION      pdx = Pdo->DeviceExtension;

    // Set Device Interface inactive for PdoTypeRawPort - other PDO types don't have device interfaces
    if( PdoTypeRawPort == pdx->PdoType ) {
        if( (pdx->DeviceInterface.Buffer != NULL) && (TRUE == pdx->DeviceInterfaceState) ) {
            IoSetDeviceInterfaceState( &pdx->DeviceInterface, FALSE );
            pdx->DeviceInterfaceState = FALSE;
        }
    }

    pdx->DeviceStateFlags |= PPT_DEVICE_SURPRISE_REMOVED;
    KeClearEvent(&pdx->PauseEvent); // pause any worker thread

    return P4CompleteRequest( Irp, STATUS_SUCCESS, Irp->IoStatus.Information );
}


NTSTATUS
PptPdoDefaultPnpHandler(
    IN PDEVICE_OBJECT  Pdo,
    IN PIRP            Irp
    )
{
    UNREFERENCED_PARAMETER( Pdo );

    return P4CompleteRequest( Irp, Irp->IoStatus.Status, Irp->IoStatus.Information );
}


NTSTATUS 
PptPdoPnp(
    IN PDEVICE_OBJECT Pdo,
    IN PIRP           Irp
    ) 
{ 
    PPDO_EXTENSION               pdx   = Pdo->DeviceExtension;
    PIO_STACK_LOCATION           irpSp = IoGetCurrentIrpStackLocation( Irp );

    // diagnostic
    PptPdoDumpPnpIrpInfo( Pdo, Irp);

    if( pdx->DeviceStateFlags & PPT_DEVICE_DELETE_PENDING ) {
        DD((PCE)pdx,DDT,"PptPdoPnp - PPT_DEVICE_DELETE_PENDING - bailing out\n");
        return P4CompleteRequest( Irp, STATUS_DELETE_PENDING, Irp->IoStatus.Information );
    }

    if( irpSp->MinorFunction < arraysize(PptPdoPnpDispatchTable) ) {
        return PptPdoPnpDispatchTable[ irpSp->MinorFunction ]( Pdo, Irp );
    } else {
        DD((PCE)pdx,DDT,"PptPdoPnp - Default Handler - IRP_MN = %x\n",irpSp->MinorFunction);
        return PptPdoDefaultPnpHandler( Pdo, Irp );
    }
}