
/*++

Copyright (c) Microsoft Corporation.  All rights reserved.

    THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
    PURPOSE.

Module Name:

    Pitcher.c

Abstract:

    This module shows how to a write a generic filter driver.

Environment:

    Kernel mode

Revision History:

    Eliyas Yakub Oct 29 1998

    Fixed bugs - March 15, 2001

    Added Ioctl interface - Aug 16, 2001
    
    Updated to use IoCreateDeviceSecure function - Sep 17, 2002

    Updated to use RemLocks - Oct 29, 2002
    
--*/

#define MODULENAME "Pitcher.c"

#include "..\drivers.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, FilterAddDevice)
#pragma alloc_text (PAGE, FilterDispatchPnp)
#pragma alloc_text (PAGE, FilterUnload)
#endif

//
// Define IOCTL_INTERFACE in your sources file if  you want your
// app to have private interaction with the filter driver. Read KB Q262305
// for more information.
//

#ifdef IOCTL_INTERFACE

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, FilterCreateControlObject)
#pragma alloc_text (PAGE, FilterDeleteControlObject)
#pragma alloc_text (PAGE, FilterDispatchIo)
#endif
FAST_MUTEX ControlMutex;
ULONG InstanceCount = 0;
PDEVICE_OBJECT ControlDeviceObject;

#endif

NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT  DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
/*++

Routine Description:

    Installable driver initialization entry point.
    This entry point is called directly by the I/O system.

Arguments:

    DriverObject - pointer to the driver object

    RegistryPath - pointer to a unicode string representing the path,
                   to driver-specific key in the registry.

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise.

--*/
{
    NTSTATUS            status = STATUS_SUCCESS;
    ULONG               ulIndex;
    PDRIVER_DISPATCH  * dispatch;

    UNREFERENCED_PARAMETER (RegistryPath);

    DebugPrint (("loading\n"));


    //
    // Create dispatch points
    //
    for (ulIndex = 0, dispatch = DriverObject->MajorFunction;
         ulIndex <= IRP_MJ_MAXIMUM_FUNCTION;
         ulIndex++, dispatch++) {

        *dispatch = FilterPass;
    }
    DriverObject->MajorFunction[IRP_MJ_PNP]            = FilterDispatchPnp;
    DriverObject->MajorFunction[IRP_MJ_POWER]          = FilterDispatchPower;
    DriverObject->DriverExtension->AddDevice           = FilterAddDevice;
    DriverObject->DriverUnload                         = FilterUnload;

#ifdef IOCTL_INTERFACE
    //
    // Set the following dispatch points as we will be doing
    // something useful to these requests instead of just
    // passing them down. 
    // 
    
    DriverObject->MajorFunction[IRP_MJ_CREATE]     = 
    DriverObject->MajorFunction[IRP_MJ_CLOSE]      = 
    DriverObject->MajorFunction[IRP_MJ_CLEANUP]    = 
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
                                                        FilterDispatchIo;
    //
    // Mutex is to synchronize multiple threads creating & deleting 
    // control deviceobjects. 
    //
    ExInitializeFastMutex(&ControlMutex);
    
#endif

    return status;
}


NTSTATUS
FilterAddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT PhysicalDeviceObject
    )
/*++

Routine Description:

    The Plug & Play subsystem is handing us a brand new PDO, for which we
    (by means of INF registration) have been asked to provide a driver.

    We need to determine if we need to be in the driver stack for the device.
    Create a function device object to attach to the stack
    Initialize that device object
    Return status success.

    Remember: We can NOT actually send ANY non pnp IRPS to the given driver
    stack, UNTIL we have received an IRP_MN_START_DEVICE.

Arguments:

    DeviceObject - pointer to a device object.

    PhysicalDeviceObject -  pointer to a device object created by the
                            underlying bus driver.

Return Value:

    NT status code.

--*/
{
    NTSTATUS                status = STATUS_SUCCESS;
    PDEVICE_OBJECT          deviceObject = NULL;
    PDEVICE_EXTENSION       deviceExtension;
    ULONG                   deviceType = FILE_DEVICE_UNKNOWN;

    PAGED_CODE ();


    //
    // IoIsWdmVersionAvailable(1, 0x20) returns TRUE on os after Windows 2000.
    //
    if (!IoIsWdmVersionAvailable(1, 0x20)) {
        //
        // Win2K system bugchecks if the filter attached to a storage device
        // doesn't specify the same DeviceType as the device it's attaching
        // to. This bugcheck happens in the filesystem when you disable
        // the devicestack whose top level deviceobject doesn't have a VPB.
        // To workaround we will get the toplevel object's DeviceType and
        // specify that in IoCreateDevice.
        //
        deviceObject = IoGetAttachedDeviceReference(PhysicalDeviceObject);
        deviceType = deviceObject->DeviceType;
        ObDereferenceObject(deviceObject);
    }

    //
    // Create a filter device object.
    //

    status = IoCreateDevice (DriverObject,
                             sizeof (DEVICE_EXTENSION),
                             NULL,  // No Name
                             deviceType,
                             FILE_DEVICE_SECURE_OPEN,
                             FALSE,
                             &deviceObject);


    if (!NT_SUCCESS (status)) {
        //
        // Returning failure here prevents the entire stack from functioning,
        // but most likely the rest of the stack will not be able to create
        // device objects either, so it is still OK.
        //
        return status;
    }

    DebugPrint (("AddDevice PDO (0x%x) FDO (0x%x)\n",
                    PhysicalDeviceObject, deviceObject));

    deviceExtension = (PDEVICE_EXTENSION) deviceObject->DeviceExtension;

    deviceExtension->NextLowerDriver = IoAttachDeviceToDeviceStack (
                                       deviceObject,
                                       PhysicalDeviceObject);
    //
    // Failure for attachment is an indication of a broken plug & play system.
    //

    if(NULL == deviceExtension->NextLowerDriver) {

        IoDeleteDevice(deviceObject);
        return STATUS_UNSUCCESSFUL;
    }

    deviceObject->Flags |= deviceExtension->NextLowerDriver->Flags &
                            (DO_BUFFERED_IO | DO_DIRECT_IO |
                            DO_POWER_PAGABLE );


    deviceObject->DeviceType = deviceExtension->NextLowerDriver->DeviceType;

    deviceObject->Characteristics =
                          deviceExtension->NextLowerDriver->Characteristics;

    deviceExtension->Self = deviceObject;

    //
    // Let us use remove lock to keep count of IRPs so that we don't 
    // deteach and delete our deviceobject until all pending I/Os in our
    // devstack are completed. Remlock is required to protect us from
    // various race conditions where our driver can get unloaded while we
    // are still running dispatch or completion code.
    //
    
    IoInitializeRemoveLock (&deviceExtension->RemoveLock , 
                            POOL_TAG,
                            1, // MaxLockedMinutes 
                            100); // HighWatermark, this parameter is 
                                // used only on checked build. Specifies 
                                // the maximum number of outstanding 
                                // acquisitions allowed on the lock
                                

    //
    // Set the initial state of the Filter DO
    //

    INITIALIZE_PNP_STATE(deviceExtension);

    DebugPrint(("AddDevice: %x to %x->%x \n", deviceObject,
                       deviceExtension->NextLowerDriver,
                       PhysicalDeviceObject));

    deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    return STATUS_SUCCESS;

}


NTSTATUS
FilterPass (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    The default dispatch routine.  If this driver does not recognize the
    IRP, then it should send it down, unmodified.
    If the device holds iris, this IRP must be queued in the device extension
    No completion routine is required.

    For demonstrative purposes only, we will pass all the (non-PnP) Irps down
    on the stack (as we are a filter driver). A real driver might choose to
    service some of these Irps.

    As we have NO idea which function we are happily passing on, we can make
    NO assumptions about whether or not it will be called at raised IRQL.
    For this reason, this function must be in put into non-paged pool
    (aka the default location).

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

      NT status code

--*/
{
    PDEVICE_EXTENSION       deviceExtension;
    PIO_STACK_LOCATION	    pStack;
    NTSTATUS                status;
    
//#if DBG
//    pStack = IoGetCurrentIrpStackLocation(Irp);
//    DebugPrint(("IRP_MJ_0x%x, IRP_MN_0x%x\n", pStack->MajorFunction, pStack->MinorFunction));
//#endif
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
    if (!NT_SUCCESS (status)) {
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
    }
    pStack = IoGetCurrentIrpStackLocation(Irp);
    DebugPrint(("IRP_MJ_0x%x, IRP_MN_0x%x", pStack->MajorFunction, pStack->MinorFunction));
    if(pStack->MajorFunction == IRP_MJ_SCSI)
       DbgPrint("SCSI Opcode = %s", ScsiopToString(pStack->Parameters.Scsi.Srb->Function));
    DbgPrint("\n");
    IoSkipCurrentIrpStackLocation (Irp);
    status = IoCallDriver (deviceExtension->NextLowerDriver, Irp);
    IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp); 
    return status;
}






NTSTATUS
FilterDispatchPnp (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    The plug and play dispatch routines.

    Most of these the driver will completely ignore.
    In all cases it must pass on the IRP to the lower driver.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

      NT status code

--*/
{
    PDEVICE_EXTENSION           deviceExtension;
    PIO_STACK_LOCATION         irpStack;
    NTSTATUS                            status;
    KEVENT                               event;

    PAGED_CODE();

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    irpStack = IoGetCurrentIrpStackLocation(Irp);

    DebugPrint(("%s\n", PnPMinorFunctionString(irpStack->MinorFunction)));

    status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
    if (!NT_SUCCESS (status)) {
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
    }
    

    switch (irpStack->MinorFunction) {
    case IRP_MN_START_DEVICE:
        //
        // The device is starting.
        // We cannot touch the device (send it any non pnp irps) until a
        // start device has been passed down to the lower drivers.
        //
        KeInitializeEvent(&event, NotificationEvent, FALSE);
        IoCopyCurrentIrpStackLocationToNext(Irp);
        IoSetCompletionRoutine(Irp,
                               (PIO_COMPLETION_ROUTINE) FilterStartCompletionRoutine,
                               &event,
                               TRUE,
                               TRUE,
                               TRUE);

        status = IoCallDriver(deviceExtension->NextLowerDriver, Irp);
        
        //
        // Wait for lower drivers to be done with the Irp. Important thing to
        // note here is when you allocate memory for an event in the stack  
        // you must do a KernelMode wait instead of UserMode to prevent 
        // the stack from getting paged out.
        //

        if (status == STATUS_PENDING) {
           KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);          
           status = Irp->IoStatus.Status;
        }
        if (NT_SUCCESS (status))
        {

            //
            // As we are successfully now back, we will
            // first set our state to Started.
            //

            SET_NEW_PNP_STATE(deviceExtension, Started);

            //
            // On the way up inherit FILE_REMOVABLE_MEDIA during Start.
            // This characteristic is available only after the driver stack is started!.
            //
            if (deviceExtension->NextLowerDriver->Characteristics & FILE_REMOVABLE_MEDIA)
            {
                DeviceObject->Characteristics |= FILE_REMOVABLE_MEDIA;
            }

#ifdef IOCTL_INTERFACE
            //
            // If the PreviousPnPState is stopped then we are being stopped temporarily
            // and restarted for resource rebalance. 
            //
            if(Stopped != deviceExtension->PreviousPnPState) {
                //
                // Device is started for the first time.
                //
                FilterCreateControlObject(DeviceObject);
            }
#endif   
        }
        
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp); 
        return status;

    case IRP_MN_REMOVE_DEVICE:

        //
        // Wait for all outstanding requests to complete
        //

        IoReleaseRemoveLockAndWait(&deviceExtension->RemoveLock, Irp);
        IoSkipCurrentIrpStackLocation(Irp);
        status = IoCallDriver(deviceExtension->NextLowerDriver, Irp);
        SET_NEW_PNP_STATE(deviceExtension, Deleted);
#ifdef IOCTL_INTERFACE
        FilterDeleteControlObject();
#endif 
        IoDetachDevice(deviceExtension->NextLowerDriver);
        IoDeleteDevice(DeviceObject);
        return status;

    case IRP_MN_QUERY_STOP_DEVICE:
        SET_NEW_PNP_STATE(deviceExtension, StopPending);
        status = STATUS_SUCCESS;
        break;

    case IRP_MN_CANCEL_STOP_DEVICE:

        //
        // Check to see whether you have received cancel-stop
        // without first receiving a query-stop. This could happen if someone
        // above us fails a query-stop and passes down the subsequent
        // cancel-stop.
        //

        if(StopPending == deviceExtension->DevicePnPState)
        {
            //
            // We did receive a query-stop, so restore.
            //
            RESTORE_PREVIOUS_PNP_STATE(deviceExtension);
        }
        status = STATUS_SUCCESS; // We must not fail this IRP.
        break;

    case IRP_MN_STOP_DEVICE:
        SET_NEW_PNP_STATE(deviceExtension, Stopped);
        status = STATUS_SUCCESS;
        break;

    case IRP_MN_QUERY_REMOVE_DEVICE:

        SET_NEW_PNP_STATE(deviceExtension, RemovePending);
        status = STATUS_SUCCESS;
        break;

    case IRP_MN_SURPRISE_REMOVAL:

        SET_NEW_PNP_STATE(deviceExtension, SurpriseRemovePending);
        status = STATUS_SUCCESS;
        break;

    case IRP_MN_CANCEL_REMOVE_DEVICE:

        //
        // Check to see whether you have received cancel-remove
        // without first receiving a query-remove. This could happen if
        // someone above us fails a query-remove and passes down the
        // subsequent cancel-remove.
        //

        if(RemovePending == deviceExtension->DevicePnPState)
        {
            //
            // We did receive a query-remove, so restore.
            //
            RESTORE_PREVIOUS_PNP_STATE(deviceExtension);
        }

        status = STATUS_SUCCESS; // We must not fail this IRP.
        break;

    case IRP_MN_DEVICE_USAGE_NOTIFICATION:

        //
        // On the way down, pagable might become set. Mimic the driver
        // above us. If no one is above us, just set pagable.
        //
        if ((DeviceObject->AttachedDevice == NULL) || (DeviceObject->AttachedDevice->Flags & DO_POWER_PAGABLE))
        {

            DeviceObject->Flags |= DO_POWER_PAGABLE;
        }
        IoCopyCurrentIrpStackLocationToNext(Irp);
        IoSetCompletionRoutine(
            Irp,
            FilterDeviceUsageNotificationCompletionRoutine,
            NULL,
            TRUE,
            TRUE,
            TRUE
            );
        return IoCallDriver(deviceExtension->NextLowerDriver, Irp);

    default:
        //
        // If you don't handle any IRP you must leave the
        // status as is.
        //
        status = Irp->IoStatus.Status;
        break;
    }

    //
    // Pass the IRP down and forget it.
    //
    Irp->IoStatus.Status = status;
    IoSkipCurrentIrpStackLocation (Irp);
    status = IoCallDriver (deviceExtension->NextLowerDriver, Irp);
    IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp); 
    return status;
}


NTSTATUS
FilterStartCompletionRoutine(IN PDEVICE_OBJECT   DeviceObject,
    			     IN PIRP             PassthruIrp,
    			     IN PVOID            Context )


/*++
Routine Description:
    A completion routine for use when calling the lower device objects to
    which our filter deviceobject is attached.

Arguments:

    DeviceObject - Pointer to deviceobject
    Irp          - Pointer to a PnP Irp.
    Context      - NULL
Return Value:

    NT Status is returned.

--*/

{
    PKEVENT             event = (PKEVENT)Context;

    UNREFERENCED_PARAMETER (DeviceObject);

    // 08/01/2005 DPA

    // The following optimization replaced with an unconditional call to KeSetEvent().
    // The optimization does not work in XP. The IRP is not marked PendingReturned
    // despite the fact the lower driver returned STATUS_PENDING to the IoCallDriver().
    // If we don't set the event our upper filter driver hangs on the Start Device IRP.

    // If the lower driver didn't return STATUS_PENDING, we don't need to 
    // set the event because we won't be waiting on it. 
    // This optimization avoids grabbing the dispatcher lock, and improves perf.
    //
    //if (PassthruIrp->PendingReturned == TRUE) {
    //    KeSetEvent (event, IO_NO_INCREMENT, FALSE);
    //}

    KeSetEvent (event, IO_NO_INCREMENT, FALSE);

    //
    // The dispatch routine will have to call IoCompleteRequest
    //

    return STATUS_MORE_PROCESSING_REQUIRED;

}

NTSTATUS
FilterDeviceUsageNotificationCompletionRoutine(
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp,
    IN PVOID            Context
    )
/*++
Routine Description:
    A completion routine for use when calling the lower device objects to
    which our filter deviceobject is attached.

Arguments:

    DeviceObject - Pointer to deviceobject
    Irp          - Pointer to a PnP Irp.
    Context      - NULL
Return Value:

    NT Status is returned.

--*/

{
    PDEVICE_EXTENSION       deviceExtension;

    UNREFERENCED_PARAMETER(Context);

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;


    if (Irp->PendingReturned) {

        IoMarkIrpPending(Irp);
    }

    //
    // On the way up, pagable might become clear. Mimic the driver below us.
    //
    if (!(deviceExtension->NextLowerDriver->Flags & DO_POWER_PAGABLE)) {

        DeviceObject->Flags &= ~DO_POWER_PAGABLE;
    }

    IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp); 

    return STATUS_CONTINUE_COMPLETION;

}

NTSTATUS
FilterDispatchPower(
    IN PDEVICE_OBJECT    DeviceObject,
    IN PIRP              Irp
    )
/*++

Routine Description:

    This routine is the dispatch routine for power irps.

Arguments:

    DeviceObject - Pointer to the device object.

    Irp - Pointer to the request packet.

Return Value:

    NT Status code
--*/
{
    PDEVICE_EXTENSION   deviceExtension;
    NTSTATUS    status;
    
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
    if (!NT_SUCCESS (status)) { // may be device is being removed.
        Irp->IoStatus.Status = status;
        PoStartNextPowerIrp(Irp);
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
    }

    PoStartNextPowerIrp(Irp);
    IoSkipCurrentIrpStackLocation(Irp);
    status = PoCallDriver(deviceExtension->NextLowerDriver, Irp);
    IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp); 
    return status;
}



VOID
FilterUnload(
    IN PDRIVER_OBJECT DriverObject
    )
/*++

Routine Description:

    Free all the allocated resources in DriverEntry, etc.

Arguments:

    DriverObject - pointer to a driver object.

Return Value:

    VOID.

--*/
{
    PAGED_CODE ();

    //
    // The device object(s) should be NULL now
    // (since we unload, all the devices objects associated with this
    // driver must be deleted.
    //
    ASSERT(DriverObject->DeviceObject == NULL);

    //
    // We should not be unloaded until all the devices we control
    // have been removed from our queue.
    //
    DebugPrint (("unload\n"));

    return;
}

#ifdef IOCTL_INTERFACE
NTSTATUS
FilterCreateControlObject(IN PDEVICE_OBJECT DeviceObject)
{
    UNICODE_STRING      ntDeviceName;
    UNICODE_STRING      symbolicLinkName;
    PCONTROL_DEVICE_EXTENSION   deviceExtension;
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    UNICODE_STRING  sddlString;    

    PAGED_CODE();    

    //
    // Using unsafe function so that the IRQL remains at PASSIVE_LEVEL.
    // IoCreateDeviceSecure & IoCreateSymbolicLink must be called at
    // PASSIVE_LEVEL.
    //
    ExAcquireFastMutexUnsafe(&ControlMutex);

    //
    // If this is a first instance of the device, then create a controlobject
    // and register dispatch points to handle ioctls.
    //
    DebugPrint(("FilterCreateControlDevice: Instance count=%d\n", InstanceCount));
    if(1 == ++InstanceCount)
    {

        //
        // Initialize the unicode strings
        //
        RtlInitUnicodeString(&ntDeviceName, NTDEVICE_NAME_STRING);
        RtlInitUnicodeString(&symbolicLinkName, SYMBOLIC_NAME_STRING);

        //
        // Initialize a security descriptor string. Refer to SDDL docs in the SDK
        // for more info.
        //
        RtlInitUnicodeString( &sddlString, L"D:P(A;;GA;;;SY)(A;;GA;;;BA)");

        //
        // Create a named deviceobject so that applications or drivers
        // can directly talk to us without going throuhg the entire stack.
        // This call could fail if there are not enough resources or
        // another deviceobject of same name exists (name collision).
        // Let us use the new IoCreateDeviceSecure and specify a security
        // descriptor (SD) that allows only System and Admin groups to access the 
        // control device. Let us also specify a unique guid to allow administrators 
        // to change the SD if he desires to do so without changing the driver. 
        // The SD will be stored in 
        // HKLM\SYSTEM\CCSet\Control\Class\<GUID>\Properties\Security.
        // An admin can override the SD specified in the below call by modifying
        // the registry.
        //
        
	DebugPrint(("Creating control device\n", ntDeviceName));
	status = IoCreateDeviceSecure(DeviceObject->DriverObject,
 	                              sizeof(CONTROL_DEVICE_EXTENSION),
                                      &ntDeviceName,
                                      FILE_DEVICE_UNKNOWN,
                                      FILE_DEVICE_SECURE_OPEN,
                                      FALSE, 
                                      &sddlString,
                                      (LPCGUID)&GUID_SD_FILTER_CONTROL_OBJECT,
                                      &ControlDeviceObject);
	DebugPrint(("IoCreateDeviceSecure done\n"));
        if (NT_SUCCESS( status ))
        {
	    DebugPrint(("Created device\n", ntDeviceName));
            ControlDeviceObject->Flags |= DO_BUFFERED_IO;
            status = IoCreateSymbolicLink( &symbolicLinkName, &ntDeviceName );
            if ( !NT_SUCCESS( status ))
            {
                IoDeleteDevice(ControlDeviceObject);
                DebugPrint(("IoCreateSymbolicLink failed %x\n", status));
                goto End;
            }
            deviceExtension = ControlDeviceObject->DeviceExtension;
            deviceExtension->ControlData = NULL;
            deviceExtension->Deleted = FALSE;
            ControlDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
        }
        else
        {
            DebugPrint(("IoCreateDevice failed %x\n", status));
        }
    }

End:
    ExReleaseFastMutexUnsafe(&ControlMutex); 
    return status;
    
}

VOID
FilterDeleteControlObject(
)
{
    UNICODE_STRING      symbolicLinkName;
    PCONTROL_DEVICE_EXTENSION   deviceExtension;

    PAGED_CODE();    

    ExAcquireFastMutexUnsafe (&ControlMutex);

    //
    // If this is the last instance of the device then delete the controlobject
    // and symbolic link to enable the pnp manager to unload the driver.
    //
    
    if(!(--InstanceCount) && ControlDeviceObject)
    {
        RtlInitUnicodeString(&symbolicLinkName, SYMBOLIC_NAME_STRING);
        deviceExtension = ControlDeviceObject->DeviceExtension;
        deviceExtension->Deleted = TRUE;
        IoDeleteSymbolicLink(&symbolicLinkName);
        IoDeleteDevice(ControlDeviceObject);
        ControlDeviceObject = NULL;
    }

    ExReleaseFastMutexUnsafe (&ControlMutex); 

}
#endif // IOCTL_INTERFACE

NTSTATUS
FilterDispatchIo(
    IN PDEVICE_OBJECT    DeviceObject,
    IN PIRP              Irp
    )
/*++

Routine Description:

    This routine is the dispatch routine for non-pnp IRPs.

    If the request is for our control device we will process and complete the
    control request in this routine.

    If the request is not a an IOCTL_IRP_PASSTHRU request we will simply pass
    the request down the stack.

    If the request is an IOCTL_IRP_PASSTHRU request we will initiate a new IRP
    using the IRP information contained in the IOCTL_IRP_PASSTHRU input buffer
    and leave the original IRP pending until the new IRP completes:

    1) We allocate and initialize the new IRP.

    2) We flag the new IRP as a passthru request by setting the information field
       in the IOSB for the new IRP to IOCTL_IRP_PASSTHRU. This will allow our lower
       filter driver to catch and complete the IRP if the intervening drivers do not
       complete it.

    4) We attach our completion routine to the new IR and pass the pointer to the original
       IRP as the context pointer for the completion routine.

    5) We call the next lower driver on the stack with the new IRP and return
       STATUS_MORE_PROCESSING_REQUIRED to the I/O Manager.

   
Arguments:

    DeviceObject - Pointer to the device object.

    Irp - Pointer to the request packet.

Return Value:

    NT Status code
--*/
{
    PCONTROL_DEVICE_EXTENSION	pCtlDeviceExtension;
    PDEVICE_EXTENSION			pDeviceExtension;
    PIO_STACK_LOCATION			irpStack, pMySlot, pNextSlot;
    PDEVICE_OBJECT				pNextDevice;
    NTSTATUS					status;
	PUSER_IRP					pUsrIrp;
    PIRP						pKerIrp;  

    PAGED_CODE();

    pMySlot = IoGetCurrentIrpStackLocation (Irp);

#ifdef IOCTL_INTERFACE

	if(DeviceObject == ControlDeviceObject)
    {
       // This is targeted at our control deviceobject so let's handle it here.
       // We don't have to worry about acquiring remlocks for I/Os that come 
       // on our control object because the I/O manager takes reference on our 
       // deviceobject when it initiates a request to our device and that keeps
       // our driver from unloading when we have pending I/Os. But we still
       // have to watch out for a scenario where another driver can send 
       // requests to our deviceobject directly without opening an handle.
       //

       pCtlDeviceExtension = ControlDeviceObject->DeviceExtension;
       if(!pCtlDeviceExtension->Deleted)
       {
           status = STATUS_SUCCESS;
           Irp->IoStatus.Information = 0;
           switch (pMySlot->MajorFunction)
           {
               case IRP_MJ_CREATE:
                   DebugPrint(("Create \n"));
                   break;
                
               case IRP_MJ_CLOSE:
                   DebugPrint(("Close \n"));
                   break;
                
               case IRP_MJ_CLEANUP:
                   DebugPrint(("Cleanup \n"));
                   break;
                
               case  IRP_MJ_DEVICE_CONTROL:
                   DebugPrint(("DeviceIoControl\n"));
                   break;
                default:
                  break;
           }
       }
       else
       {
          ASSERTMSG(FALSE, "Requests being sent to a dead device\n");
          status = STATUS_DEVICE_REMOVED;
       }
       Irp->IoStatus.Status = status;
       IoCompleteRequest (Irp, IO_NO_INCREMENT);
       DebugPrint(("Control device - final status: 0x%4.4x\n", status));
       return status;
     }
#endif //IOCTL_INTERFACE

     // If this is not an IOCTL_IRP_PASSTHRU request just pass it on

     if((pMySlot->MajorFunction != IRP_MJ_DEVICE_CONTROL) ||
        (pMySlot->Parameters.DeviceIoControl.IoControlCode != IOCTL_IRP_PASSTHRU))
     { 
       return FilterPass(DeviceObject, Irp);    
     }

     // We've been asked to initiate an IRP - validate the caller's buffer

     pUsrIrp = (PUSER_IRP) Irp->AssociatedIrp.SystemBuffer;
     if(!pUsrIrp ||
	pMySlot->Parameters.DeviceIoControl.InputBufferLength < sizeof(USER_IRP) ||
	pMySlot->Parameters.DeviceIoControl.OutputBufferLength < sizeof(USER_IRP))
	{
	   DebugPrint(("Invalid user buffer, Address: 0x%x, Input Length: %d, Output Length: %d\n",
			pUsrIrp,
			pMySlot->Parameters.DeviceIoControl.InputBufferLength,
			pMySlot->Parameters.DeviceIoControl.OutputBufferLength));
	   Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
	   IoCompleteRequest(Irp, IO_NO_INCREMENT);
	   return STATUS_INVALID_PARAMETER;
	} 

	// Allocate a new kernel IRP with enough stack slots for this driver and
	// all lower drivers (we need a slot for our completion routine).

	pKerIrp = IoAllocateIrp(DeviceObject->StackSize, FALSE);									// Allocate a new IRP for that device
        IoSetNextIrpStackLocation(pKerIrp);													// Push stack pointer to first slot

        // Setup our completion routine for the kernel IRP. We pass the original
	// IOCTL IRP to the completion routine as the context pointer so it can be
	// completed as well.

	IoSetCompletionRoutine(pKerIrp,														// Target IRP
			       SwbtsIrpPassthruCompletion,									// Entry point of completion routine,
			       Irp,															// Pass original IRP as context to completion routine
			       TRUE, TRUE, TRUE);											// Handle SUCCESS, ERROR, and CANCEL events

	// Copy the user's IRP data from the IOCTL input buffer to the next lower
        // driver's stack location of the newly allocated IRP

	pNextSlot = IoGetNextIrpStackLocation(pKerIrp);										// Get next drivers slot
	pNextSlot->MajorFunction = pUsrIrp->IoStack.MajorFunction;							// Major function code
	pNextSlot->MinorFunction = pUsrIrp->IoStack.MinorFunction;							// Minor function code
	pNextSlot->Parameters = pUsrIrp->IoStack.Parameters;								// Function specific parameters
	pNextSlot->FileObject = pUsrIrp->IoStack.FileObject;

        // Fixup major function specific parameters

	switch(pUsrIrp->IoStack.MajorFunction)
        {
           case IRP_MJ_SCSI:

              // Set SRB pointer in kernel IRP to point to callers SRB now in our 
              // kernel I/O buffer and set SenseInfo and data buffer pointers in
              // callers SRB to point to proper location in our kernel buffer
 
              pNextSlot->Parameters.Scsi.Srb = &(pUsrIrp->Parameters.Scsi.Srb);
              pUsrIrp->Parameters.Scsi.Srb.DataBuffer = pUsrIrp->Parameters.Scsi.Data;
              pUsrIrp->Parameters.Scsi.Srb.SenseInfoBuffer = pUsrIrp->Parameters.Scsi.SenseInfo;
              break;

           default:
              break;
        }

	// Initialize the I/O status block for the IRP. Set a flag in the
	// Information member to flag this IRP as a test suite IRP on the
	// way down so our lower filter can catch and complete it if it gets
	// that far. This assumes lower drivers will not modify the information
	// field unless they are completing the request.

	pKerIrp->IoStatus.Status = 0;
	pKerIrp->IoStatus.Information = IOCTL_IRP_PASSTHRU;

	// Fire in the hole - start the request - God help us...

	pDeviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;				// Device extension for our device
	pNextDevice = pDeviceExtension->NextLowerDriver;									// Saved pointer to next device on stack 
	IoMarkIrpPending(Irp);
        DebugPrint(("Throwing Passthru IRP %s\n", IrpMajorFunctionString(pNextSlot->MajorFunction)));																// Mark the original IRP pending
	status= IoCallDriver(pNextDevice, pKerIrp);											// Start the replacement IRP
	return STATUS_PENDING;																// Return status to I/O Manager
}

NTSTATUS
SwbtsIrpPassthruCompletion(IN PDEVICE_OBJECT   DeviceObject,
    			   IN PIRP             pKerIrp,
    			   IN PVOID            Context )


/*++
Routine Description:

    This routine is called to complete processing of a (kernel) IRP generated by an IOCTL_IRP_PASSTHRU
    request to this driver. The IOCTL request passed a user mode copy of an IRP in the input buffer of
    the request. Our dispatch routine started  a kernel IRP using a copy of the the data in the user's
    buffer and set this routine as the completion routine for it.  Our dispatch routine has passed us
    a pointer to the original IRP in the context argument to this call.  Now we copy the I/O status block
    of the generated IRP back into the I/O status block of the IRP in the user's buffer and complete both
    IRPs.

Arguments:

    DeviceObject - Pointer to deviceobject
    pKerIrp      - Pointer to a driver allocated Irp.
    Context      - Pointer to the IOCTL_IRP_PASSTHRU IRP that generated the IRP that
                   we have been called to complete

Return Value:

    NT Status is returned.

--*/

{
    PIRP                   pIoctlIrp; 
    PUSER_IRP              pUsrIrp;
    UNREFERENCED_PARAMETER (DeviceObject);
    DebugPrint(("Completing Passthru IRP @0x%8.8x\n", pKerIrp));
    DebugPrint(("Context IRP @0x%8.8x\n", Context));
    DebugPrint(("Passthru IRP status: 0x%8.8x, 0x%8.8x\n", pKerIrp->IoStatus.Status, pKerIrp->IoStatus.Information));

    // Copy the I/O Status Block of the Passthru IRP to the I/O Status Block in the output buffer of
    // the IOCTL request.

    pIoctlIrp = (PIRP) Context;
    pUsrIrp = (PUSER_IRP) pIoctlIrp->AssociatedIrp.SystemBuffer;
    RtlCopyBytes(&(pUsrIrp->IoStatus), &(pKerIrp->IoStatus), sizeof(pUsrIrp->IoStatus));


    // Complete the IOCTL IRP

    pIoctlIrp->IoStatus.Status = STATUS_SUCCESS;
    pIoctlIrp->IoStatus.Information = sizeof(USER_IRP);
    IoCompleteRequest(pIoctlIrp, IO_NO_INCREMENT);
    
    // Free the driver allocated IRP and tell the I/O manager no additional
    // processing on his part is needed.

    IoFreeIrp(pKerIrp);
    return STATUS_MORE_PROCESSING_REQUIRED;
}

#if DBG

PCHAR
IrpMajorFunctionString (
    UCHAR MajorFunction
)
{
    static char szString[12];

    switch (MajorFunction)
    {
        case IRP_MJ_CREATE:
            return "IRP_MJ_CREATE";

        case IRP_MJ_CREATE_NAMED_PIPE:
            return "IRP_MJ_CREATE_NAMED_PIPE";

        case IRP_MJ_CLOSE:
            return "IRP_MJ_CLOSE";

        case IRP_MJ_READ:
            return "IRP_MJ_READ";

        case IRP_MJ_WRITE:
            return "IRP_MJ_WRITE";

        case IRP_MJ_QUERY_INFORMATION:
            return "IRP_MJ_QUERY_INFORMATION";

        case IRP_MJ_SET_INFORMATION:
            return "IRP_MJ_SET_INFORMATION";

        case IRP_MJ_QUERY_EA:
            return "IRP_MJ_QUERY_EA";

        case IRP_MJ_SET_EA:
            return "IRP_MJ_SET_EA";

        case IRP_MJ_FLUSH_BUFFERS:
            return "IRP_MJ_FLUSH_BUFFERS";

        case IRP_MJ_QUERY_VOLUME_INFORMATION:
            return "IRP_MJ_QUERY_VOLUME_INFORMATION";

        case IRP_MJ_SET_VOLUME_INFORMATION:
            return "IRP_MJ_SET_VOLUME_INFORAMATION";

	case IRP_MJ_DIRECTORY_CONTROL:
	    return "IRP_MJ_DIRECTORY_CONTROL";

        case IRP_MJ_FILE_SYSTEM_CONTROL:
            return "IRP_MJ_FILE_SYSTEM_CONTROL";

        case IRP_MJ_DEVICE_CONTROL:
            return "IRP_MJ_DEVICE_CONTROL";

        case IRP_MJ_INTERNAL_DEVICE_CONTROL:
            return "IRP_MJ_INTERNAL_DEVICE_CONTROL";

        case IRP_MJ_SHUTDOWN:
            return "IRP_MJ_SHUTDOWN";

        case IRP_MJ_LOCK_CONTROL:
            return "IRP_MJ_LOCK_CONTROL";

        case IRP_MJ_CLEANUP:
            return "IRP_MJ_CLEANUP";

        case IRP_MJ_CREATE_MAILSLOT:
            return "IRP_MJ_CREATE_MAILSLOT";

        case IRP_MJ_QUERY_SECURITY:
            return "IRP_MJ_QUERY_SECURITY";

        case IRP_MJ_SET_SECURITY:
            return "IRP_MJ_SET_SECURITY";

        case IRP_MJ_POWER:
            return "IRP_MJ_POWER";

        case IRP_MJ_SYSTEM_CONTROL:
            return "IRP_MJ_SYSTEM_CONTROL";

        case IRP_MJ_DEVICE_CHANGE:
            return "IRP_MJ_DEVICE_CHANGE";

        case IRP_MJ_QUERY_QUOTA:
            return "IRP_MJ_QUERY_QUOTA";

        case IRP_MJ_PNP:
            return "IRP_MJ_PNP";

        default:
            RtlStringCbPrintfA(szString, sizeof(szString), "IRP_MJ_0x%2.2x", MajorFunction);
            return szString;
    }
}
PCHAR
PnPMinorFunctionString (
    UCHAR MinorFunction
)
{
    switch (MinorFunction)
    {
        case IRP_MN_START_DEVICE:
            return "IRP_MN_START_DEVICE";
        case IRP_MN_QUERY_REMOVE_DEVICE:
            return "IRP_MN_QUERY_REMOVE_DEVICE";
        case IRP_MN_REMOVE_DEVICE:
            return "IRP_MN_REMOVE_DEVICE";
        case IRP_MN_CANCEL_REMOVE_DEVICE:
            return "IRP_MN_CANCEL_REMOVE_DEVICE";
        case IRP_MN_STOP_DEVICE:
            return "IRP_MN_STOP_DEVICE";
        case IRP_MN_QUERY_STOP_DEVICE:
            return "IRP_MN_QUERY_STOP_DEVICE";
        case IRP_MN_CANCEL_STOP_DEVICE:
            return "IRP_MN_CANCEL_STOP_DEVICE";
        case IRP_MN_QUERY_DEVICE_RELATIONS:
            return "IRP_MN_QUERY_DEVICE_RELATIONS";
        case IRP_MN_QUERY_INTERFACE:
            return "IRP_MN_QUERY_INTERFACE";
        case IRP_MN_QUERY_CAPABILITIES:
            return "IRP_MN_QUERY_CAPABILITIES";
        case IRP_MN_QUERY_RESOURCES:
            return "IRP_MN_QUERY_RESOURCES";
        case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
            return "IRP_MN_QUERY_RESOURCE_REQUIREMENTS";
		case IRP_MN_QUERY_LEGACY_BUS_INFORMATION:
			return "IRP_MN_QUERY_LEGACY_BUS_INFORMATION";
        case IRP_MN_QUERY_DEVICE_TEXT:
            return "IRP_MN_QUERY_DEVICE_TEXT";
        case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
            return "IRP_MN_FILTER_RESOURCE_REQUIREMENTS";
        case IRP_MN_READ_CONFIG:
            return "IRP_MN_READ_CONFIG";
        case IRP_MN_WRITE_CONFIG:
            return "IRP_MN_WRITE_CONFIG";
        case IRP_MN_EJECT:
            return "IRP_MN_EJECT";
        case IRP_MN_SET_LOCK:
            return "IRP_MN_SET_LOCK";
        case IRP_MN_QUERY_ID:
            return "IRP_MN_QUERY_ID";
        case IRP_MN_QUERY_PNP_DEVICE_STATE:
            return "IRP_MN_QUERY_PNP_DEVICE_STATE";
        case IRP_MN_QUERY_BUS_INFORMATION:
            return "IRP_MN_QUERY_BUS_INFORMATION";
        case IRP_MN_DEVICE_USAGE_NOTIFICATION:
            return "IRP_MN_DEVICE_USAGE_NOTIFICATION";
        case IRP_MN_SURPRISE_REMOVAL:
            return "IRP_MN_SURPRISE_REMOVAL";

        default:
            return "unknown_pnp_irp";
    }
}

PCHAR ScsiopToString (
    UCHAR Scsiop
)
{
    static char szString[12];

    switch (Scsiop)
    {
       case 0x00:
            return "TEST_UNIT_READY";

//      case SCSIOP_REZERO_UNIT:
//          return "REZERO_UNIT";

        case 0x01:
            return "REWIND";

//        case SCSIOP_REQUEST_BLOCK_ADDR:
//            return "REQUEST_BLOCK_ADDR";

        case 0x02:
            return "VENDOR_SPECIFIC_CDB";

        case 0x03:
            return "REQUEST_SENSE";

        case 0x04:
            return "FORMAT_UNIT";

        case 0x05:
            return "READ_BLOCK_LIMITS";

        case 0x06:
            return "VENDOR_SPECIFIC_CDB";

        case 0x07:
            return "REASSIGN_BLOCKS";

//      case SCSIOP_INIT_ELEMENT_STATUS:
//          return "INIT_ELEMENT_STATUS";

        case 0x08:
            return "READ6";

        case 0x09:
            return "VENDOR_SPECIFIC_CDB";

//      case SCSIOP_RECEIVE:
//          return "RECEIVE";

        case 0x0A:
            return "WRITE6";

//     case SCSIOP_PRINT:
//          return "PRINT";

//      case SCSIOP_SEND:
//          return "SEND";

        case 0x0B:
            return "SEEK6";

        case 0x0C:
        case 0x0D:
        case 0x0E:
            return "VENDOR_SPECIFIC_CDB";

//        case SCSIOP_TRACK_SELECT:
//            return "TRACK_SELECT";

//      case SCSIOP_SLEW_PRINT:
//          return "SLEW_PRINT";

//        case SCSIOP_SEEK_BLOCK:
//            return "SEEK_BLOCK";

//        case SCSIOP_PARTITION:
//            return "PARTITION";

        case 0x0F:
            return "READ_REVERSE6";

        case 0x10:
            return "WRITE_FILEMARKS";

//      case SCSIOP_FLUSH_BUFFER:
//          return "FLUSH_BUFFER";

        case 0x11:
            return "SPACE";

        case 0x12:
            return "INQUIRY";

        case 0x13:
            return "VERIFY6";

        case 0x14:
            return "RECOVER_BUF_DATA";

        case 0x15:
            return "MODE_SELECT";

        case 0x16:
            return "RESERVE_UNIT";

        case 0x17:
            return "RELEASE_UNIT";

        case 0x18:
            return "COPY";

        case 0x19:
            return "ERASE";

        case 0x1A:
            return "MODE_SENSE";

        case 0x1B:
            return "START_STOP_UNIT";

//      case SCSIOP_STOP_PRINT:
//          return "STOP_PRINT";

//      case SCSIOP_LOAD_UNLOAD:
//          return "LOAD_UNLOAD";

        case 0x1C:
            return "RECEIVE_DIAGNOSTIC";

        case 0x1D:
            return "SEND_DIAGNOSTIC";

        case 0x1E:
            return "MEDIUM_REMOVAL";

        case 0x1F:
            return "UNDEFINED CDB (0x1F)";
 
        case 0x20:
        case 0x21:
        case 0x22:
        case 0x23:
            return "VENDOR_SPECIFIC_CDB";

        case 0x24:
            return "SET_WINDOW";

        case 0x25:
            return "READ_CAPACITY";

        case 0x26:
        case 0x27:
	         return "VENDOR_SPECIFIC_CDB";

        case 0x28:
            return "READ10";

        case 0x29:
            return "READ_GENERATION";

        case 0x2A:
            return "WRITE10";

        case 0x2B:
            return "SEEK10";

//      case SCSIOP_LOCATE:
//          return "LOCATE";

//      case SCSIOP_POSITION_TO_ELEMENT:
//          return "POSITION_TO_ELEMENT";

        case 0x2C:
            return "ERASE10";

        case 0x2D:
            return "VENDOR_SPECIFIC_CDB";

        case 0x2E:
            return "WRITE_AND_VERIFY10";

        case 0x2F:
            return "VERIFY";

        case 0x30:
            return "SEARCH_DATA_HIGH";

        case 0x31:
            return "SEARCH_DATA_EQUAL";

        case 0x32:
            return "SEARCH_DATA_LOW";

        case 0x33:
            return "SET_LIMITS";

        case 0x34:
            return "READ_POSITION";

        case 0x35:
            return "SYNCHRONIZE_CACHE";

        case 0x36:
             return "LOCK_UNLOCK_CACHE";

        case 0x37:
             return "READ_DEFECT_DATA";

        case 0x38:
            return "MEDIUM_SCAN";
           

        case 0x39:
            return "COMPARE";

        case 0x3A:
            return "COPY_COMPARE";

        case 0x3B:
            return "WRITE_DATA_BUFF";

        case 0x3C:
            return "READ_DATA_BUFF";

        case 0x3D:
            return "UPDATE_BLOCK";

        case 0x3E:
            return "READ_LONG10";

	     case 0x3F:
            return "WRITE_LONG10";
 
        case 0x40:
            return "CHANGE_DEFINITION";

        case 0x41:
            return "WRITE_SAME10";

        case 0x42:
            return "READ_SUB_CHANNEL";

        case 0x43:
            return "READ_TOC";

        case 0x44:
            return "READ_HEADER";

        case 0x45:
            return "PLAY_AUDIO";

        case 0x46:
            return "GET_CONFIGURATION";

        case 0x47:
            return "PLAY_AUDIO_MSF";

        case 0x48:
            return "PLAY_TRACK_INDEX";

        case 0x49:
            return "PLAY_TRACK_RELATIVE";

        case 0x4A:
            return "GET_EVENT_STATUS";

        case 0x4B:
            return "PAUSE_RESUME";

        case 0x4C:
            return "LOG_SELECT";

        case 0x4D:
            return "LOG_SENSE";

        case 0x4E:
            return "STOP_PLAY_SCAN";

        case 0x4F:
            return "UNDEFINED_CDB (0x4F)";

        case 0x50:
            return "XDWRITE10";

        case 0x51:
            return "XPWRITE10";

//      case SCSIOP_READ_DISK_INFORMATION:
//          return "READ_DISK_INFORMATION";

        case 0x52:
            return "XDREAD10";

//      case SCSIOP_READ_TRACK_INFORMATION:
//          return "READ_TRACK_INFORMATION";

        case 0x53:
            return "XDWRITucRead10";

//      case SCSIOP_RESERVE_TRACK_RZONE:
//          return "RESERVE_TRACK_RZONE";

        case 0x54:
            return "SEND_OPC_INFORMATION";

        case 0x55:
            return "MODE_SELECT10";

        case 0x56:
            return "RESERVE_UNIT10";

        case 0x57:
            return "RELEASE_UNIT10";

        case 0x58:
            return "REPAIR_TRACK";

        case 0x59:
            return "UNDEFINED_CDB (0x59)";

        case 0x5A:
            return "MODE_SENSE10";

        case 0x5B:
            return "CLOSE_TRACK_SESSION";

        case 0x5C:
            return "READ_BUFFER_CAPACITY";

        case 0x5D:
            return "SEND_CUE_SHEET";

        case 0x5E:
            return "PERSISTENT_RESERVE_IN";

        case 0x5F:
            return "PERSISTENT_RESERVE_OUT";

        case 0x60:
            return "UNDEFINED_CDB (0x60)";

        case 0x61:
            return "UNDEFINED_CDB (0x61)";

        case 0x62:
            return "UNDEFINED_CDB (0x62)";

        case 0x63:
            return "UNDEFINED_CDB (0x63)";

        case 0x64:
            return "UNDEFINED_CDB (0x64)";

        case 0x65:
            return "UNDEFINED_CDB (0x65)";

        case 0x66:
            return "UNDEFINED_CDB (0x66)";

        case 0x67:
            return "UNDEFINED_CDB (0x67)";

        case 0x68:
            return "UNDEFINED_CDB (0x68)";

        case 0x69:
            return "UNDEFINED_CDB (0x69)";

        case 0x6A:
            return "UNDEFINED_CDB (0x6A)";

        case 0x6B:
            return "UNDEFINED_CDB (0x6B)";

        case 0x6C:
            return "UNDEFINED_CDB (0x6C)";

        case 0x6D:
            return "UNDEFINED_CDB (0x6D)";

        case 0x6E:
            return "UNDEFINED_CDB (0x6E)";

        case 0x6F:
            return "UNDEFINED_CDB (0x6F)";

        case 0x70:
            return "UNDEFINED_CDB (0x70)";

        case 0x71:
            return "UNDEFINED_CDB (0x71)";

        case 0x72:
            return "UNDEFINED_CDB (0x72)";

        case 0x73:
            return "UNDEFINED_CDB (0x73)";

        case 0x74:
            return "UNDEFINED_CDB (0x70)";

        case 0x75:
            return "UNDEFINED_CDB (0x75)";

        case 0x76:
            return "UNDEFINED_CDB (0x76)";

        case 0x77:
            return "UNDEFINED_CDB (0x77)";

        case 0x78:
            return "UNDEFINED_CDB (0x78)";

        case 0x79:
            return "UNDEFINED_CDB (0x79)";

        case 0x7a:
            return "UNDEFINED_CDB (0x7A)";

        case 0x7b:
            return "UNDEFINED_CDB (0x7B)";

        case 0x7c:
            return "UNDEFINED_CDB (0x7C)";

        case 0x7d:
            return "UNDEFINED_CDB (0x7D)";

        case 0x7e:
            return "UNDEFINED_CDB (0x7E)";

        case 0x7f:
            return "VARIABLE_LENGTH_CDB";

        case 0x80:
            return "XDWRITE_EXTENDED";

        case 0x81:
            return "REBUILD";

        case 0x82:
            return "REGENERATE";

        case 0x83:
            return "EXTENDED_COPY";

        case 0x84:
            return "RECEIVE_COPY_RESULTS";

        case 0x85:
            return "ATA_PASSTHROUGH16";

        case 0x86:
            return "ACCESS_CONTROL_IN";

        case 0x87:
            return "ACCESS_CONTROL_OUT";

        case 0x88:
            return "READ16";

        case 0x89:
            return "UNDEFINED_CDB (0x89)";

        case 0x8A:
            return "WRITE16";

        case 0x8B:
            return "UNDEFINED_CDB (0x8B)";

        case 0x8C:
            return "READ_ATTRIBUTE";

        case 0x8D:
            return "WRITE_ATTRIBUTE";

        case 0x8e:
            return "WRITE_AND_VERIFY16";

        case 0x8F:
            return "VERIFY16";

        case 0x90:
            return "PRE-FETCH16";

        case 0x91:
            return "SYNCHRONIZE_CACHE16";

        case 0x92:
            return "LOCK-UNLOCK CACHE";

        case 0x93:
            return "WRITE_SAME16";

        case 0x94:
            return "UNDEFINED_CDB (0x94)";

        case 0x95:
            return "UNDEFINED_CDB (0x95)";

        case 0x96:
            return "UNDEFINED_CDB (0x96)";

        case 0x97:
            return "UNDEFINED_CDB (0x97)";

        case 0x98:
            return "UNDEFINED_CDB (0x98)";

        case 0x99:
            return "UNDEFINED_CDB (0x99)";

        case 0x9A:
            return "UNDEFINED_CDB (0x9A)";

        case 0x9B:
            return "UNDEFINED_CDB (0x9B)";

        case 0x9C:
            return "UNDEFINED_CDB (0x9C)";

        case 0x9D:
            return "UNDEFINED_CDB (0x9D)";

        case 0x9E:
            return "SERVICE_ACTION_IN";

        case 0x9F:
            return "SERVICE_ACTION_OUT)";

        case 0xA0:
            return "REPORT_LUNS";

        case 0xA1:
            return "ATA_PASSTHROUGH12";

        case 0xA2:
            return "SEND_EVENT";

        case 0xA3:
            return "SEND_KEY";

        case 0xA4:
            return "REPORT_KEY";

        case 0xA5:
            return "MOVE_MEDIUM";

        case 0xA6:
            return "LOAD_UNLOAD_SLOT";

        case 0xA7:
            return "MOVE_NMEDIUM_ATTACHED";

        case 0xA8:
            return "READ12";

        case 0xA9:
            return "SERVICE_ACTION_OUT";

        case 0xAA:
            return "WRITE12";

        case 0xAB:
            return "SERVICE_ACTION_IN";

        case 0xAC:
            return "ERASE12";

        case 0xAD:
            return "READ_DVD_STRUCTURE";

        case 0xAE:
            return "WRITE_AND_VERIFY12";

        case 0xAF:
            return "VERIFY12";

        case 0xB0:
            return "SEARCH_DATA_HIGH12";

        case 0xB1:
            return "SEARCH_DATA_EQUAL12";

        case 0xB2:
            return "SEARCH_DATA_LOW12";

        case 0xB3:
             return "SET_LIMITS12";

        case 0xB4:
             return "READ_ELEMENT_STATUS_ATTACHED";

        case 0xB5:
             return "REQUEST_VOLUME_ELEMENT_ADDRESS";

        case 0xB6:
             return "SET_STREAMING";

        case 0xB7:
            return "READ_DEFECT_DATA12";

        case 0xB8:
            return "READ_ELEMENT_STATUS";

        case 0xB9:
            return "READ_CD_MSF12";

        case 0xBA:
            return "SCAN12";

        case 0xBB:
            return "SET_CDROM_SPEED12";

        case 0xBC:
            return "PLAY_CD12";

        case 0xBD:
            return "MECHANISM_STATUS";

        case 0xBE:
            return "READ_CD12";

        case 0xBF:
            return "SEND_DEVD_STRUCTURE";

        default:
            return "VENDOR_SPECIFIC_CDB";
    }
}
#endif
