/*++

Copyright (c) 2001  Microsoft Corporation

Module Name:

    scsi.c

Abstract:

    This file contains RAM disk driver code for processing SCSI commands.

Author:

    Chuck Lenzmeier (ChuckL) 2001

Environment:

    Kernel mode only.

Notes:

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop

//
// Local functions.
//

NTSTATUS
Do6ByteCdbCommand (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    );

NTSTATUS
Do10ByteCdbCommand (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    );

NTSTATUS
Do12ByteCdbCommand (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    );

NTSTATUS
BuildInquiryData (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    );

NTSTATUS
BuildModeSenseInfo (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    );

//
// Declare pageable routines.
//

#ifdef ALLOC_PRAGMA

#pragma alloc_text( PAGE, RamdiskScsiExecuteNone )
#pragma alloc_text( PAGE, RamdiskScsiExecuteIo )
#pragma alloc_text( PAGE, Do6ByteCdbCommand )
#pragma alloc_text( PAGE, Do10ByteCdbCommand )
#pragma alloc_text( PAGE, Do12ByteCdbCommand )
#pragma alloc_text( PAGE, BuildInquiryData )
#pragma alloc_text( PAGE, BuildModeSenseInfo )

#endif // ALLOC_PRAGMA

NTSTATUS
RamdiskScsi (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    This routine is called by the I/O system to process a SCSI IRP.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Irp - a pointer to the I/O Request Packet for this request

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    NTSTATUS status;
    PDISK_EXTENSION diskExtension;

    diskExtension = DeviceObject->DeviceExtension;

    //
    // ISSUE: Can't be paged because ClassCheckMediaState calls it from timer
    // routine. (For removable disks.) Therefore we can't acquire the device
    // mutex here.
    //

    //
    // Check to see if the device is being removed.
    //

    if ( diskExtension->DeviceState > RamdiskDeviceStatePendingRemove ) {

        status = STATUS_DEVICE_DOES_NOT_EXIST;
        COMPLETE_REQUEST( status, 0, Irp );
        return status;
    }

    //
    // Acquire the remove lock for the device.
    //

    status = IoAcquireRemoveLock( &diskExtension->RemoveLock, Irp );

    if ( !NT_SUCCESS(status) ) {

        DBGPRINT( DBG_PNP, DBG_ERROR, ("%s", "RamdiskScsi: acquire RemoveLock failed\n") );

        COMPLETE_REQUEST( status, 0, Irp );
        return status;
    }

    //
    // This IRP must be processed in thread context.
    //

    status = SendIrpToThread( DeviceObject, Irp );

    if ( status != STATUS_PENDING ) {

        DBGPRINT( DBG_PNP, DBG_ERROR, ("%s", "RamdiskScsi: SendIrpToThread failed\n") );

        COMPLETE_REQUEST( status, 0, Irp );
        return status;
    }

    //
    // Release the remove lock.
    //

    IoReleaseRemoveLock(&diskExtension->RemoveLock, Irp );

    return STATUS_PENDING;

} // RamdiskScsi

NTSTATUS
RamdiskScsiExecuteNone (
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PSCSI_REQUEST_BLOCK Srb,
    ULONG ControlCode
    )

/*++

Routine Description:

    This routine is called by the I/O system to process a SCSI IRP that
    does not involve I/O.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Irp - a pointer to the I/O Request Packet for this request

    Srb - the SRB associated with the IRP

    ControlCode - the control code from the SRB

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    NTSTATUS status;
    UCHAR function;
    PDISK_EXTENSION diskExtension;
    
    PAGED_CODE();

    diskExtension = DeviceObject->DeviceExtension;

    //
    // Dispatch based on the SRB function.
    //

    function = Srb->Function;

    switch( function ) {
    
    case SRB_FUNCTION_ATTACH_DEVICE:
    case SRB_FUNCTION_CLAIM_DEVICE:

        DBGPRINT( DBG_SRB, DBG_VERBOSE, ("%s", "SRB_FUNCTION_CLAIM_DEVICE\n") );

        //
        // If the device has not already been claimed, mark it so now.
        // Otherwise, indicate to the caller that the device is busy.
        //

        if ( (diskExtension->Status & RAMDISK_STATUS_CLAIMED) == 0 ) {

            diskExtension->DeviceState = RamdiskDeviceStateWorking;
            diskExtension->Status |= RAMDISK_STATUS_CLAIMED;

            Srb->DataBuffer = DeviceObject;

            status = STATUS_SUCCESS;
            Srb->ScsiStatus = SCSISTAT_GOOD;
            Srb->SrbStatus = SRB_STATUS_SUCCESS;

        } else {

            status  = STATUS_DEVICE_BUSY;
            Srb->ScsiStatus = SCSISTAT_BUSY;
            Srb->SrbStatus = SRB_STATUS_BUSY;
        }

        break;

    case SRB_FUNCTION_RELEASE_DEVICE:
    case SRB_FUNCTION_REMOVE_DEVICE:

        DBGPRINT( DBG_SRB, DBG_VERBOSE, ("%s", "SRB_FUNCTION_RELEASE_DEVICE\n") );

        //
        // Indicate that the device is no longer claimed.
        //

        diskExtension->Status &= ~RAMDISK_STATUS_CLAIMED;

        status = STATUS_SUCCESS;
        Srb->ScsiStatus = SCSISTAT_GOOD;
        Srb->SrbStatus = SRB_STATUS_SUCCESS;

        break;

    default:

        //
        // Unrecognized non-I/O function. Try the I/O path.
        //

        status = RamdiskScsiExecuteIo( DeviceObject, Irp, Srb, ControlCode );

        break;
    }

    return status;

} // RamdiskScsiExecuteNone

NTSTATUS
RamdiskScsiExecuteIo (
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PSCSI_REQUEST_BLOCK Srb,
    ULONG ControlCode
    )

/*++

Routine Description:

    This routine is called by the I/O system to process a SCSI IRP that
    involves I/O.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Irp - a pointer to the I/O Request Packet for this request

    Srb - the SRB associated with the IRP

    ControlCode - the control code from the SRB

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    NTSTATUS status;
    UCHAR function;
    PDISK_EXTENSION diskExtension;
    
    PAGED_CODE();

    diskExtension = DeviceObject->DeviceExtension;

    //
    // Dispatch based on the SRB function.
    //

    function = Srb->Function;

    switch( function ) {
    
    case SRB_FUNCTION_EXECUTE_SCSI:

        Srb->SrbStatus = SRB_STATUS_SUCCESS;

        //
        // Dispatch based on the CDB length.
        //
    
        switch( Srb->CdbLength ) {
        
        case 6:

            status = Do6ByteCdbCommand( DeviceObject, Srb );

            break;

        case 10:

            status = Do10ByteCdbCommand( DeviceObject, Irp, Srb );

            break;

        case 12:

            status = Do12ByteCdbCommand( DeviceObject, Srb );

            break;

        default:

            DBGPRINT( DBG_SRB, DBG_ERROR,
                        ("Unknown CDB length 0x%x for function 0x%x, IOCTL 0x%x\n",
                        Srb->CdbLength, function, ControlCode) );
            UNRECOGNIZED_IOCTL_BREAK;

            status = STATUS_INVALID_DEVICE_REQUEST;
            Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;

            break;
        }

        break;

    case SRB_FUNCTION_FLUSH:
    case SRB_FUNCTION_SHUTDOWN:

        //
        // For flush and shutdown on a file-backed RAM disk, we need to flush
        // the mapped data back to the file.
        //

        status = RamdiskFlushBuffersReal( diskExtension );
        Srb->SrbStatus = SRB_STATUS_SUCCESS;

        break;

    case SRB_FUNCTION_IO_CONTROL:

        //
        // We don't handle this function, but we don't want to complain
        // when we get it.
        //

        status = STATUS_INVALID_DEVICE_REQUEST;
        Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;

        break;

    default:

        DBGPRINT( DBG_SRB, DBG_ERROR,
                    ("Unknown SRB Function 0x%x for IOCTL 0x%x\n", function, ControlCode) );
        UNRECOGNIZED_IOCTL_BREAK;
        status = STATUS_INTERNAL_ERROR;
    }

    
    return status;

} // RamdiskScsiExecuteIo

NTSTATUS
Do6ByteCdbCommand (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    )

/*++

Routine Description:

    This routine handles 6-byte CDBs.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Srb - the SRB associated with the I/O request

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    NTSTATUS status;
    PDISK_EXTENSION diskExtension;
    PCDB cdb;

    PAGED_CODE();

    //
    // Get pointers to the device extension and to the CDB.
    //

    diskExtension = DeviceObject->DeviceExtension;
    cdb = (PCDB)Srb->Cdb;

    //
    // Assume success.
    //

    status = STATUS_SUCCESS;
    Srb->SrbStatus = SRB_STATUS_SUCCESS;
    Srb->ScsiStatus = SCSISTAT_GOOD;

    ASSERT( Srb->CdbLength == 6 );
    ASSERT( cdb != NULL );

    DBGPRINT( DBG_SRB, DBG_VERBOSE,
                ("Do6ByteCdbCommand Called OpCode 0x%x\n", cdb->CDB6GENERIC.OperationCode) );

    //
    // Dispatch based on the operation code.
    //

    switch ( cdb->CDB6GENERIC.OperationCode ) {
    
    case SCSIOP_TEST_UNIT_READY:

        //
        // RAM disks are always ready.
        //

        break;

    case SCSIOP_REQUEST_SENSE:

        //
        // We don't handle request sense.
        //

        Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
        status = STATUS_INVALID_DEVICE_REQUEST;
        
        break;

    case SCSIOP_FORMAT_UNIT:

        // ISSUE: Need to do something here, like zero the image?

        break;

    case SCSIOP_INQUIRY:

        //
        // If the buffer is big enough, build the inquiry data.
        //

        if ( Srb->DataTransferLength >= INQUIRYDATABUFFERSIZE ) {

            status = BuildInquiryData( DeviceObject, Srb );

        } else {

            Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
            status = STATUS_INVALID_DEVICE_REQUEST;
        }

        break;

    case SCSIOP_MODE_SENSE:

        //
        // Build the mode sense information.
        //

        status = BuildModeSenseInfo( DeviceObject, Srb );
        
        break;

    case SCSIOP_MEDIUM_REMOVAL:

        //
        // Remember whether media removal is allowed.
        //

        if (cdb->MEDIA_REMOVAL.Prevent == TRUE) {
            diskExtension->Status |= RAMDISK_STATUS_PREVENT_REMOVE;
        } else {
            diskExtension->Status &= ~RAMDISK_STATUS_PREVENT_REMOVE;
        }

        break;

    //case SCSIOP_READ6:
    //case SCSIOP_WRITE6:
    //case SCSIOP_REZERO_UNIT:
    //case SCSIOP_REQUEST_BLOCK_ADDR:
    //case SCSIOP_READ_BLOCK_LIMITS:
    //case SCSIOP_REASSIGN_BLOCKS:
    //case SCSIOP_SEEK6:
    //case SCSIOP_SEEK_BLOCK:
    //case SCSIOP_PARTITION:
    //case SCSIOP_READ_REVERSE:
    //case SCSIOP_WRITE_FILEMARKS:
    //case SCSIOP_SPACE:
    //case SCSIOP_VERIFY6:
    //case SCSIOP_RECOVER_BUF_DATA:
    //case SCSIOP_MODE_SELECT:
    //case SCSIOP_RESERVE_UNIT:
    //case SCSIOP_RELEASE_UNIT:
    //case SCSIOP_COPY:
    //case SCSIOP_ERASE:
    //case SCSIOP_START_STOP_UNIT:
    //case SCSIOP_RECEIVE_DIAGNOSTIC:
    //case SCSIOP_SEND_DIAGNOSTIC:

    default:

        DBGPRINT( DBG_SRB, DBG_ERROR,
                    ("Unknown CDB Function 0x%x\n", cdb->CDB6GENERIC.OperationCode) );
        UNRECOGNIZED_IOCTL_BREAK;

        status = STATUS_INVALID_DEVICE_REQUEST;
        Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
    }

    DBGPRINT( DBG_SRB, DBG_VERBOSE, ("Do6ByteCdbCommand Done status 0x%x\n", status) );

    return status;

} // Do6ByteCdbCommand

NTSTATUS
Do10ByteCdbCommand (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    )

/*++

Routine Description:

    This routine handles 10-byte CDBs.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Irp - a pointer to the I/O Request Packet for this request

    Srb - the SRB associated with the IRP

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    NTSTATUS status;
    PDISK_EXTENSION diskExtension;
    PCDB cdb;
    PREAD_CAPACITY_DATA readCapacityData;
    ULONGLONG diskSize;
    ULONG lastBlock;
    FOUR_BYTE startingBlockNumber;
    TWO_BYTE count;
    ULONG_PTR offset;
    ULONG dataSize;
    PUCHAR diskByteAddress;
    PUCHAR dataBuffer;
    ULONG mappedLength;

    PAGED_CODE();

    //
    // Get pointers to the device extension and to the CDB.
    //

    diskExtension = DeviceObject->DeviceExtension;
    cdb = (PCDB)Srb->Cdb;

    //
    // Assume success.
    //

    status = STATUS_SUCCESS;
    Srb->SrbStatus = SRB_STATUS_SUCCESS;
    Srb->ScsiStatus = SCSISTAT_GOOD;

    ASSERT( Srb->CdbLength == 10 );
    ASSERT( cdb != NULL );
    
    DBGPRINT( DBG_SRB, DBG_VERBOSE,
                ("Do10ByteCdbCommand Called OpCode 0x%x\n", cdb->CDB10.OperationCode) );

    //
    // Dispatch based on the operation code.
    //

    switch ( cdb->CDB10.OperationCode ) {
    
    case SCSIOP_READ_CAPACITY:

        //
        // Return the disk's block size and last block number (big-endian).
        //

        readCapacityData = Srb->DataBuffer;

        diskSize = diskExtension->DiskLength;
        lastBlock = (ULONG)(diskSize / diskExtension->BytesPerSector) - 1;

        readCapacityData->BytesPerBlock = _byteswap_ulong( diskExtension->BytesPerSector );
        readCapacityData->LogicalBlockAddress = _byteswap_ulong( lastBlock );

        break;

    case SCSIOP_READ:
    case SCSIOP_WRITE:

        //
        // Read from or write to the disk.
        //

        //
        // Convert the transfer length, in blocks, from big-endian. Convert
        // that to bytes.
        //

        count.Byte0 = cdb->CDB10.TransferBlocksLsb;
        count.Byte1 = cdb->CDB10.TransferBlocksMsb;

        dataSize = count.AsUShort * diskExtension->BytesPerSector;

        //
        // If the CDB length is greater than the SRB length, use the SRB
        // length.
        //

        if ( dataSize > Srb->DataTransferLength ) {
            dataSize = Srb->DataTransferLength;
        }

        //
        // Convert the starting block number from big-endian. 
        //

        startingBlockNumber.Byte0 = cdb->CDB10.LogicalBlockByte3;
        startingBlockNumber.Byte1 = cdb->CDB10.LogicalBlockByte2;
        startingBlockNumber.Byte2 = cdb->CDB10.LogicalBlockByte1;
        startingBlockNumber.Byte3 = cdb->CDB10.LogicalBlockByte0;

        //
        // We don't handle RelativeAddress requests.
        //

        if ( cdb->CDB10.RelativeAddress ) {

            Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
            status = STATUS_INVALID_DEVICE_REQUEST;

            break;
        }

        //
        // Get the offset within the disk to the start of the operation.
        //

        offset = (startingBlockNumber.AsULong * diskExtension->BytesPerSector);

        //
        // If the transfer length causes the offset to wrap, or if the request
        // goes beyond the end of the disk, reject the request.
        //

        if ( ((offset + dataSize) < offset) ||
             ((offset + dataSize) > diskExtension->DiskLength) ) {

            Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
            status = STATUS_INVALID_DEVICE_REQUEST;

            break;
        }

        //
        // For a zero-length transfer, we don't have to do anything.
        //

        DBGPRINT( DBG_READWRITE, DBG_VERBOSE, 
            ("%s: Starting Block 0x%x, Length 0x%x at Offset 0x%I64x SrbBuffer=0x%p "
            "SrbLength=0x%x, MdlLength=0x%x\n",
            cdb->CDB10.OperationCode == SCSIOP_READ ? "Read" : "Write",
            startingBlockNumber.AsULong, count.AsUShort, offset, 
            Srb->DataBuffer,
            Srb->DataTransferLength,
            Irp->MdlAddress->ByteCount) );

        dataBuffer = Srb->DataBuffer;

        while ( dataSize != 0 ) {

            //
            // Map the target section of the disk into memory. Then copy the
            // data in the appropriate direction.
            //

            diskByteAddress = RamdiskMapPages( diskExtension, offset, dataSize, &mappedLength );

            if ( diskByteAddress != NULL ) {

                if ( cdb->CDB10.OperationCode == SCSIOP_READ ) {
    
                    memcpy( dataBuffer, diskByteAddress, mappedLength );
    
                } else {
    
                    memcpy( diskByteAddress, dataBuffer, mappedLength );
                }

                RamdiskUnmapPages( diskExtension, diskByteAddress, offset, mappedLength );

                dataSize -= mappedLength;
                offset += mappedLength;
                dataBuffer += mappedLength;

            } else {

                dataSize = 0;
                Srb->SrbStatus = SRB_STATUS_ERROR;
                status = STATUS_INSUFFICIENT_RESOURCES;
            }

        }

        break;

    case SCSIOP_VERIFY:

        //
        // Verify always succeeds.
        //

        break;

    case SCSIOP_MODE_SENSE10:

        //
        // Build the mode sense information.
        //

        status = BuildModeSenseInfo( DeviceObject, Srb );

        break;

    //case SCSIOP_SEEK:
    //case SCSIOP_WRITE_VERIFY:
    //case SCSIOP_READ_FORMATTED_CAPACITY:
    //case SCSIOP_SEARCH_DATA_HIGH:
    //case SCSIOP_SEARCH_DATA_EQUAL:
    //case SCSIOP_SEARCH_DATA_LOW:
    //case SCSIOP_SET_LIMITS:
    //case SCSIOP_READ_POSITION:
    //case SCSIOP_SYNCHRONIZE_CACHE:
    //case SCSIOP_COMPARE:
    //case SCSIOP_COPY_COMPARE:
    //case SCSIOP_WRITE_DATA_BUFF:
    //case SCSIOP_READ_DATA_BUFF:
    //case SCSIOP_CHANGE_DEFINITION:
    //case SCSIOP_READ_SUB_CHANNEL:
    //case SCSIOP_READ_TOC:
    //case SCSIOP_READ_HEADER:
    //case SCSIOP_PLAY_AUDIO:
    //case SCSIOP_GET_CONFIGURATION:
    //case SCSIOP_PLAY_AUDIO_MSF:
    //case SCSIOP_PLAY_TRACK_INDEX:
    //case SCSIOP_PLAY_TRACK_RELATIVE:
    //case SCSIOP_GET_EVENT_STATUS:
    //case SCSIOP_PAUSE_RESUME:
    //case SCSIOP_LOG_SELECT:
    //case SCSIOP_LOG_SENSE:
    //case SCSIOP_STOP_PLAY_SCAN:
    //case SCSIOP_READ_DISK_INFORMATION:
    //case SCSIOP_READ_TRACK_INFORMATION:
    //case SCSIOP_RESERVE_TRACK_RZONE:
    //case SCSIOP_SEND_OPC_INFORMATION:
    //case SCSIOP_MODE_SELECT10:
    //case SCSIOP_CLOSE_TRACK_SESSION:
    //case SCSIOP_READ_BUFFER_CAPACITY:
    //case SCSIOP_SEND_CUE_SHEET:
    //case SCSIOP_PERSISTENT_RESERVE_IN:
    //case SCSIOP_PERSISTENT_RESERVE_OUT:

    default:

        DBGPRINT( DBG_SRB, DBG_ERROR,
                    ("Unknown CDB Function 0x%x\n", cdb->CDB10.OperationCode) );
        UNRECOGNIZED_IOCTL_BREAK;

        status = STATUS_INVALID_DEVICE_REQUEST;
        Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
    }

    DBGPRINT( DBG_SRB, DBG_VERBOSE, ("Do10ByteCdbCommand Done status 0x%x\n", status) );

    return status;

} // Do10ByteCdbCommand

NTSTATUS
Do12ByteCdbCommand (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    )

/*++

Routine Description:

    This routine handles 12-byte CDBs.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Srb - the SRB associated with the IRP

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    NTSTATUS status;
    PDISK_EXTENSION diskExtension;
    PCDB cdb;

    PAGED_CODE();

    //
    // Get pointers to the device extension and to the CDB.
    //

    diskExtension = DeviceObject->DeviceExtension;
    cdb = (PCDB)Srb->Cdb;

    //
    // Assume success.
    //

    status = STATUS_SUCCESS;
    Srb->SrbStatus = SRB_STATUS_SUCCESS;
    Srb->ScsiStatus = SCSISTAT_GOOD;

    ASSERT( Srb->CdbLength == 12 );
    ASSERT( cdb != NULL );
    
    DBGPRINT( DBG_SRB, DBG_VERBOSE,
                ("Do12ByteCdbCommand Called OpCode 0x%x\n", cdb->CDB12.OperationCode) );

    //
    // Dispatch based on the operation code.
    //

    switch ( cdb->CDB12.OperationCode ) {
    
    //case SCSIOP_REPORT_LUNS:
    //case SCSIOP_BLANK:
    //case SCSIOP_SEND_KEY:
    //case SCSIOP_REPORT_KEY:
    //case SCSIOP_MOVE_MEDIUM:
    //case SCSIOP_LOAD_UNLOAD_SLOT:
    //case SCSIOP_SET_READ_AHEAD:
    //case SCSIOP_READ_DVD_STRUCTURE:
    //case SCSIOP_REQUEST_VOL_ELEMENT:
    //case SCSIOP_SEND_VOLUME_TAG:
    //case SCSIOP_READ_ELEMENT_STATUS:
    //case SCSIOP_READ_CD_MSF:
    //case SCSIOP_SCAN_CD:
    //case SCSIOP_SET_CD_SPEED:
    //case SCSIOP_PLAY_CD:
    //case SCSIOP_MECHANISM_STATUS:
    //case SCSIOP_READ_CD:
    //case SCSIOP_SEND_DVD_STRUCTURE:
    //case SCSIOP_INIT_ELEMENT_RANGE:

    default:

        DBGPRINT( DBG_SRB, DBG_ERROR,
                    ("Unknown CDB Function 0x%x\n", cdb->CDB12.OperationCode) );
        UNRECOGNIZED_IOCTL_BREAK;

        status = STATUS_INVALID_DEVICE_REQUEST;
        Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
    }

    DBGPRINT( DBG_SRB, DBG_VERBOSE, ("Do12ByteCdbCommand Done status 0x%x\n", status) );

    return status;

} // Do12ByteCdbCommand

NTSTATUS
BuildInquiryData (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    )

/*++

Routine Description:

    This routine builds inquiry data.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Srb - the SRB associated with the I/O request

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    PDISK_EXTENSION diskExtension;
    PINQUIRYDATA inquiryData;
    STRING vendor;
    STRING product;
    STRING revLevel;

    PAGED_CODE();

    //
    // Get pointers to the device extension and to the inquiry data buffer.
    //

    diskExtension = DeviceObject->DeviceExtension;
    inquiryData = (PINQUIRYDATA)Srb->DataBuffer;

    //
    // Build the inquiry data.
    //

    RtlInitString( &vendor, "Microsoft" );
    RtlInitString( &product, "Ramdisk" );
    RtlInitString( &revLevel, "1.0" );

    RtlZeroMemory( inquiryData, INQUIRYDATABUFFERSIZE );
    inquiryData->DeviceType = DIRECT_ACCESS_DEVICE;
    inquiryData->RemovableMedia = (diskExtension->Options.Fixed ? FALSE : TRUE);
    inquiryData->ANSIVersion = 2;
    inquiryData->ResponseDataFormat = 2;
    inquiryData->AdditionalLength = INQUIRYDATABUFFERSIZE - 4;

    RtlCopyMemory(
        inquiryData->VendorId,
        vendor.Buffer,
        min( vendor.Length, sizeof(inquiryData->VendorId) )
        );
    RtlCopyMemory(
        inquiryData->ProductId,
        product.Buffer,
        min( product.Length, sizeof(inquiryData->ProductId) )
        );
    RtlCopyMemory(
        inquiryData->ProductRevisionLevel,
        revLevel.Buffer,
        min( revLevel.Length, sizeof(inquiryData->ProductRevisionLevel) )
        );

    return STATUS_SUCCESS;

} // BuildInquiryData

NTSTATUS
BuildModeSenseInfo (
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PSCSI_REQUEST_BLOCK Srb
    )

/*++

Routine Description:

    This routine builds mode sense information.

Arguments:

    DeviceObject - a pointer to the object that represents the device on which
        I/O is to be performed

    Srb - the SRB associated with the I/O request

Return Value:

    NTSTATUS - the status of the operation

--*/

{
    PDISK_EXTENSION diskExtension;
    MODE_PARAMETER_HEADER  modeHeader = {0};
    MODE_PARAMETER_HEADER10 modeHeader10 = {0};
    PVOID header = NULL;
    PVOID data = NULL;
    unsigned char headerSize;
    unsigned dataSize = 0;
    PCDB cdb;
    unsigned cdbLength;
    unsigned dataBufferLength;
    unsigned char valueType;    
    unsigned dataLength;

    PAGED_CODE();

    //
    // Get pointers to the device extension and to the inquiry data buffer.
    //

    diskExtension = DeviceObject->DeviceExtension;
    cdb = (PCDB)Srb->Cdb;
    cdbLength = Srb->CdbLength;

    //
    // Dispatch based on the CDB length.
    //

    switch ( cdbLength ) {
    
    case 6:

        dataBufferLength = cdb->MODE_SENSE.AllocationLength;
        valueType = cdb->MODE_SENSE.Pc;
        headerSize = sizeof(MODE_PARAMETER_HEADER);

        if ( valueType != 0 ) {

            //
            // We only support current value retrieval.
            //

            Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
            return STATUS_INVALID_DEVICE_REQUEST;
        }

        if ( dataBufferLength > headerSize ) {

            header = &modeHeader;
            data = (char*)header + headerSize;
            dataLength = headerSize - FIELD_OFFSET( MODE_PARAMETER_HEADER, MediumType );
            modeHeader.ModeDataLength = (UCHAR)dataLength;
            modeHeader.MediumType = 0x00;
            modeHeader.DeviceSpecificParameter = 0x00;
            modeHeader.BlockDescriptorLength = 0x00;
        }

        break;

    case 10:

        dataBufferLength = *(USHORT *)cdb->MODE_SENSE10.AllocationLength;
        valueType = cdb->MODE_SENSE10.Pc;
        headerSize = sizeof(MODE_PARAMETER_HEADER10);

        if ( dataBufferLength > headerSize ) {

            header = &modeHeader10;
            data = (char*)header + headerSize;
            dataLength = headerSize - FIELD_OFFSET( MODE_PARAMETER_HEADER10, MediumType );
            RtlCopyMemory(
                modeHeader10.ModeDataLength,
                &dataLength,
                sizeof(modeHeader10.ModeDataLength)
                );
            modeHeader10.MediumType = 0x00;
            modeHeader10.DeviceSpecificParameter = 0x00;
            modeHeader10.BlockDescriptorLength[0] = 0;
            modeHeader10.BlockDescriptorLength[1] = 0;
        }

        break;

    default:

        //
        // Can't get here.
        //

        ASSERT( FALSE );

        Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
        return STATUS_INVALID_DEVICE_REQUEST;
    }

    if ( header != NULL ) {

        RtlCopyMemory( Srb->DataBuffer, header, headerSize );
        dataBufferLength -= headerSize;
    }

    if ( (data != NULL) && (dataBufferLength != 0) ) {

        RtlCopyMemory(
            (PUCHAR)Srb->DataBuffer + headerSize,
            data,
            min( dataBufferLength, dataSize )
            );
    }

    return STATUS_SUCCESS;

} // BuildModeSenseInfo