Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1836 lines
50 KiB

/*++
Copyright (c) 1993 Microsoft Corporation
Module Name:
changer.c
Abstract:
This module contains the code for various CD changers - Sanyo 3-CD, Atapi, Pioneer 6,12,18.
Author:
Environment:
Kernel mode
Revision History :
--*/
#include "changer.h"
#define INQUIRY_DATA_SIZE 2048
NTSTATUS
ChangerCompletionRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
);
BOOLEAN
ChangerDiscSelect(
IN PSHARED_DEVICE_EXTENSION CentralDevice,
IN BOOLEAN ForceChange
);
VOID
SanyoSwitchToNewDisk(
IN PCH_DEVICE_EXTENSION DeviceExtension
);
VOID
AtapiSwitchToNewDisk(
IN PCH_DEVICE_EXTENSION DeviceExtension
);
NTSTATUS
ChangeDiskCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
);
NTSTATUS
ChangerInterpretSenseInfo(
IN PDEVICE_OBJECT DeviceObject,
IN PSCSI_REQUEST_BLOCK Srb,
IN OUT PBOOLEAN Retry
);
NTSTATUS
PassThrough(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PCH_DEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
DebugPrint((3,
"Changer.PassThrough: Routine entry\n"));
Irp->CurrentLocation++;
Irp->Tail.Overlay.CurrentStackLocation++;
return IoCallDriver(deviceExtension->ClassDevice, Irp);
}
VOID
ChangerTickHandler(
IN PDEVICE_OBJECT DeviceObject,
IN PVOID Context
)
/*++
Routine Description:
Timer routine that tracks how long the active platter has been selected.
Arguments:
DeviceObject - Supplies a pointer to the device object that represents
platter 0 of the device.
Context - Not used.
Return Value:
None
--*/
{
PCH_DEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PCH_DEVICE_EXTENSION tmp;
PSHARED_DEVICE_EXTENSION sharedExtension = deviceExtension->SharedDeviceExtension;
KIRQL irql;
ULONG i;
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
sharedExtension->TimerValue++;
sharedExtension->RequestTimeOutValue++;
//
// Act as watchdog and run queues if timer value hits the threshold.
//
if (sharedExtension->RequestTimeOutValue >= CHANGER_TIMEOUT) {
sharedExtension->RequestTimeOutValue = 0;
for (i = 0, tmp = sharedExtension->DeviceList; i < sharedExtension->DiscsPresent; i++) {
if (!IsListEmpty(&tmp->WorkQueue)) {
//
// Indicate that no more requests should be sent.
//
sharedExtension->DeviceFlags = CHANGER_FREEZE_QUEUES;
sharedExtension->NextDevice = tmp;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Call the appropriate switch routine.
//
DebugPrint((1,
"ChangerTickHandler: Starting timeout request\n"));
DebugPrint((3,
"ChangerTickHandler: Switch called from %d\n",__LINE__));
sharedExtension->SwitchToNewDisk(tmp);
return;
}
tmp = tmp->Next;
}
}
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
}
NTSTATUS
ChangerCompletionRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
/*++
Routine Description:
This is the completion routine for any requests sent by GeneralDispatch.
Status is returned and if any requests are queued, the next request will be allowed to start.
Arguments:
Return Value:
None
--*/
{
PCH_DEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PCH_DEVICE_EXTENSION tmp;
PSHARED_DEVICE_EXTENSION sharedExtension = deviceExtension->SharedDeviceExtension;
PLIST_ENTRY request;
PIO_STACK_LOCATION irpStack,nextStack;
PIRP newIrp;
KIRQL irql;
ULONG i;
//
// Complete this request.
//
if (Irp->PendingReturned) {
IoMarkIrpPending(Irp);
}
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
if (deviceExtension->MediaChangeNotificationRequired) {
Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED;
deviceExtension->MediaChangeNotificationRequired = FALSE;
}
deviceExtension->OutstandingRequests--;
ASSERT(deviceExtension->OutstandingRequests == 0);
#if DBG
deviceExtension->ActiveRequest = NULL;
#endif
//
// Determine if a new request needs to be sent.
//
if ((deviceExtension->OutstandingRequests) || (sharedExtension->DeviceFlags)) {
//
// Just return.
//
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
goto CompletionExit;
} else if (sharedExtension->TimerValue >= CHANGER_MAX_WAIT) {
//
// Walk the device list looking for platters with queued requests. If one exists
// switch to it.
//
for (tmp = deviceExtension->Next,i = 0; i < sharedExtension->DiscsPresent - 1; i++) {
if (!IsListEmpty(&tmp->WorkQueue)) {
//
// Indicate that no more requests should be sent.
//
sharedExtension->DeviceFlags |= CHANGER_FREEZE_QUEUES;
sharedExtension->NextDevice = tmp;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Call the appropriate switch routine.
//
DebugPrint((3,
"ChangerCompletion: Switch called from %d\n",__LINE__));
sharedExtension->SwitchToNewDisk(tmp);
goto CompletionExit;
}
tmp = tmp->Next;
}
//
// As we found no new requests, drop through and start any on the
// current device.
//
}
if (!IsListEmpty(&deviceExtension->WorkQueue)) {
//
// Reset the timer as nothing is happening on the other platters.
//
sharedExtension->TimerValue = 0;
sharedExtension->RequestTimeOutValue = 0;
//
// Send the next request.
//
request = RemoveHeadList(&deviceExtension->WorkQueue);
newIrp = CONTAINING_RECORD(request, IRP, Tail.Overlay.ListEntry);
deviceExtension->OutstandingRequests++;
#if DBG
deviceExtension->ActiveRequest = newIrp;
#endif
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
irpStack = IoGetCurrentIrpStackLocation(newIrp);
nextStack = IoGetNextIrpStackLocation(newIrp);
*nextStack = *irpStack;
IoSetCompletionRoutine(newIrp, ChangerCompletionRoutine, deviceExtension, TRUE, TRUE, TRUE);
IoCallDriver(deviceExtension->ClassDevice, newIrp);
} else {
//
// Since the current device has no requests, walk the device list looking
// for platters with queued requests. If one exists switch to it.
//
for (tmp = deviceExtension->Next,i = 0; i < sharedExtension->DiscsPresent - 1; i++) {
if (!IsListEmpty(&tmp->WorkQueue)) {
//
// Indicate that no more requests should be sent.
//
sharedExtension->DeviceFlags |= CHANGER_FREEZE_QUEUES;
sharedExtension->NextDevice = tmp;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Call the appropriate switch routine.
//
DebugPrint((3,
"ChangerCompletion: Switch called from %d\n",__LINE__));
sharedExtension->SwitchToNewDisk(tmp);
goto CompletionExit;
}
tmp = tmp->Next;
}
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
}
//
// Whack the media change count to zero. This ensures that we are in sync with
// cdfs.
//
CompletionExit:
irpStack = IoGetCurrentIrpStackLocation(Irp);
if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
if((irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_CDROM_CHECK_VERIFY) &&
(irpStack->Parameters.DeviceIoControl.OutputBufferLength)) {
DebugPrint((1,
"ChangerCompletionRoutine: Setting MediaCount to 0. Status %x\n",
Irp->IoStatus.Status));
*((PULONG)Irp->AssociatedIrp.SystemBuffer) = 0;
Irp->IoStatus.Information = sizeof(ULONG);
}
}
if (!NT_SUCCESS(Irp->IoStatus.Status)) {
if (IoIsErrorUserInduced(Irp->IoStatus.Status)) {
DebugPrint((1,
"ChangerCompletionRoutine: status %x\n",
Irp->IoStatus.Status));
for (tmp = deviceExtension->Next,i = 0; i < sharedExtension->DiscsPresent - 1; i++) {
tmp->MediaChangeNotificationRequired = TRUE;
tmp = tmp->Next;
}
//
// Mark up the associated real device object.
//
deviceExtension->ClassDevice->Flags |= DO_VERIFY_VOLUME;
IoSetHardErrorOrVerifyDevice(Irp, deviceExtension->ClassDevice);
}
Irp->IoStatus.Information = 0;
}
return Irp->IoStatus.Status;
}
NTSTATUS
GeneralDispatch(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
Main dispatch routine for this driver. Queues the new request and starts the next request
to the device.
Arguments:
DeviceObject
Irp
Return Value:
Returns the status
--*/
{
PCH_DEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PCH_DEVICE_EXTENSION tmp;
PSHARED_DEVICE_EXTENSION sharedExtension = deviceExtension->SharedDeviceExtension;
PIO_STACK_LOCATION irpStack,nextStack;
ULONG i;
KIRQL irql;
//
// Check flags. If any are set, just queue and return.
//
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
if (sharedExtension->DeviceFlags) {
InsertTailList(&deviceExtension->WorkQueue, &Irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
IoMarkIrpPending(Irp);
return STATUS_PENDING;
}
//
// Determine is any request is outstanding. If so, queue and return.
//
for (tmp = sharedExtension->DeviceList,i = 0; i < sharedExtension->DiscsPresent; i++) {
if (tmp->OutstandingRequests) {
//
// Queue this and return.
//
InsertTailList(&deviceExtension->WorkQueue, &Irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
IoMarkIrpPending(Irp);
return STATUS_PENDING;
}
tmp = tmp->Next;
}
if (deviceExtension != sharedExtension->CurrentDevice) {
//
// Queue this request.
//
InsertTailList(&deviceExtension->WorkQueue, &Irp->Tail.Overlay.ListEntry);
IoMarkIrpPending(Irp);
//
// Must issue the switch.
//
sharedExtension->DeviceFlags |= CHANGER_FREEZE_QUEUES;
sharedExtension->NextDevice = deviceExtension;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Call the appropriate switch routine.
//
DebugPrint((3,
"GeneralDispatch: Switch called from %d\n",__LINE__));
sharedExtension->SwitchToNewDisk(deviceExtension);
return STATUS_PENDING;
}
//
// Send the request.
//
sharedExtension->RequestTimeOutValue = 0;
deviceExtension->OutstandingRequests++;
#if DBG
deviceExtension->ActiveRequest = Irp;
#endif
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
irpStack = IoGetCurrentIrpStackLocation(Irp);
nextStack = IoGetNextIrpStackLocation(Irp);
*nextStack = *irpStack;
IoSetCompletionRoutine(Irp, ChangerCompletionRoutine, deviceExtension, TRUE, TRUE, TRUE);
return IoCallDriver(deviceExtension->ClassDevice, Irp);
}
VOID
SanyoSwitchToNewDisk(
IN PCH_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine is called to switch platters on the Torisan 3-CD changer.
this driver. For this device, just send down a TUR. This relies on the
correct atapi driver.
Arguments:
DeviceExtension - DeviceExtension for the platter to switch to.
Return Value:
NONE
--*/
{
PSHARED_DEVICE_EXTENSION sharedExtension = DeviceExtension->SharedDeviceExtension;
PIRP switchIrp;
PSCSI_REQUEST_BLOCK srb;
PIO_STACK_LOCATION irpStack,nextStack;
PCHAR buffer;
KIRQL irql;
switchIrp = IoAllocateIrp((CCHAR)(DeviceExtension->DeviceObject->StackSize+1),
FALSE);
if (switchIrp) {
srb = ExAllocatePool(NonPagedPool, sizeof(SCSI_REQUEST_BLOCK));
buffer = ExAllocatePool(NonPagedPoolCacheAligned, SENSE_BUFFER_SIZE);
if (srb && buffer) {
PCDB cdb;
PDEVICE_EXTENSION classDeviceExtension = DeviceExtension->ClassDevice->DeviceExtension;
//
// All resources have been allocated set up the IRP.
//
IoSetNextIrpStackLocation(switchIrp);
irpStack = IoGetCurrentIrpStackLocation(switchIrp);
irpStack->DeviceObject = DeviceExtension->DeviceObject;
irpStack = IoGetNextIrpStackLocation(switchIrp);
irpStack->Parameters.Scsi.Srb = srb;
irpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_NONE;
//
// Initialize the SRB
//
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
srb->CdbLength = 12;
srb->TimeOutValue = 20;
srb->QueueTag = SP_UNTAGGED;
srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
srb->Length = SCSI_REQUEST_BLOCK_SIZE;
srb->PathId = classDeviceExtension->PathId;
srb->TargetId = classDeviceExtension->TargetId;
srb->Lun = classDeviceExtension->Lun;
srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
srb->OriginalRequest = switchIrp;
//
// Initialize and set up the sense information buffer
//
RtlZeroMemory(buffer, SENSE_BUFFER_SIZE);
srb->SenseInfoBuffer = buffer;
srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
//
// Initialize the CDB
//
cdb = (PCDB)&srb->Cdb[0];
cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;
//
// The sanyo switch platter command is an overloaded TUR. Set
// the 'lun' in the cdb.
//
srb->Cdb[7] = srb->Lun;
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
sharedExtension->DeviceFlags |= CHANGER_SWITCH_IN_PROGRESS;
sharedExtension->RequestTimeOutValue = 0;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Pass the original request as context to the completion routine.
// The completion will send the real request.
//
IoSetCompletionRoutine(switchIrp,
ChangeDiskCompletion,
srb,
TRUE,
TRUE,
TRUE);
IoCallDriver(DeviceExtension->ClassDevice, switchIrp);
return;
} else {
if (srb) {
ExFreePool(srb);
}
if (buffer) {
ExFreePool(buffer);
}
IoFreeIrp(switchIrp);
}
}
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
sharedExtension->DeviceFlags &= ~(CHANGER_SWITCH_IN_PROGRESS | CHANGER_FREEZE_QUEUES);
sharedExtension->NextDevice = NULL;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
}
VOID
AtapiSwitchToNewDisk(
IN PCH_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine is called to switch platters on Atapi 2.5 changers.
This relies on the correct atapi driver.
Arguments:
DeviceExtension - DeviceExtension for the platter to switch to.
Return Value:
NONE
--*/
{
PSHARED_DEVICE_EXTENSION sharedExtension = DeviceExtension->SharedDeviceExtension;
PIRP switchIrp;
PSCSI_REQUEST_BLOCK srb;
PIO_STACK_LOCATION irpStack,nextStack;
PCHAR buffer;
KIRQL irql;
switchIrp = IoAllocateIrp((CCHAR)(DeviceExtension->DeviceObject->StackSize+1),
FALSE);
if (switchIrp) {
srb = ExAllocatePool(NonPagedPool, sizeof(SCSI_REQUEST_BLOCK));
buffer = ExAllocatePool(NonPagedPoolCacheAligned, SENSE_BUFFER_SIZE);
if (srb && buffer) {
PCDB cdb;
PDEVICE_EXTENSION classDeviceExtension = DeviceExtension->ClassDevice->DeviceExtension;
//
// All resources have been allocated set up the IRP.
//
IoSetNextIrpStackLocation(switchIrp);
irpStack = IoGetCurrentIrpStackLocation(switchIrp);
irpStack->DeviceObject = DeviceExtension->DeviceObject;
irpStack = IoGetNextIrpStackLocation(switchIrp);
irpStack->Parameters.Scsi.Srb = srb;
irpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_NONE;
//
// Initialize the SRB
//
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
srb->CdbLength = 12;
srb->TimeOutValue = 20;
srb->QueueTag = SP_UNTAGGED;
srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
srb->Length = SCSI_REQUEST_BLOCK_SIZE;
srb->PathId = classDeviceExtension->PathId;
srb->TargetId = classDeviceExtension->TargetId;
srb->Lun = classDeviceExtension->Lun;
srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
srb->OriginalRequest = switchIrp;
//
// Initialize and set up the sense information buffer
//
RtlZeroMemory(buffer, SENSE_BUFFER_SIZE);
srb->SenseInfoBuffer = buffer;
srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
//
// Initialize the CDB
//
cdb = (PCDB)&srb->Cdb[0];
cdb->LOAD_UNLOAD.OperationCode = SCSIOP_LOAD_UNLOAD_SLOT;
cdb->LOAD_UNLOAD.Start = 1;
cdb->LOAD_UNLOAD.LoadEject = 1;
cdb->LOAD_UNLOAD.Slot = (UCHAR)classDeviceExtension->Lun;
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
sharedExtension->DeviceFlags |= CHANGER_SWITCH_IN_PROGRESS;
sharedExtension->RequestTimeOutValue = 0;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Pass the original request as context to the completion routine.
// The completion will send the real request.
//
IoSetCompletionRoutine(switchIrp,
ChangeDiskCompletion,
srb,
TRUE,
TRUE,
TRUE);
IoCallDriver(DeviceExtension->ClassDevice, switchIrp);
return;
} else {
if (srb) {
ExFreePool(srb);
}
if (buffer) {
ExFreePool(buffer);
}
IoFreeIrp(switchIrp);
}
}
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
sharedExtension->DeviceFlags &= ~(CHANGER_SWITCH_IN_PROGRESS | CHANGER_FREEZE_QUEUES);
sharedExtension->NextDevice = NULL;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
}
NTSTATUS
ChangeDiskCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
/*++
Routine Description:
This is the completion routine for the irp that changes platters. If successfull, it resets
the timer and starts the next request on the new platter.
Arguments:
Return Value:
--*/
{
PCH_DEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PSHARED_DEVICE_EXTENSION sharedExtension = deviceExtension->SharedDeviceExtension;
PSCSI_REQUEST_BLOCK srb = Context;
PIO_STACK_LOCATION irpStack,nextStack;
KIRQL irql;
NTSTATUS status;
BOOLEAN retry;
BOOLEAN setMediaChange = TRUE;
//
// Check SRB status for success of completing request.
//
if (SRB_STATUS(srb->SrbStatus) != SRB_STATUS_SUCCESS) {
irpStack = IoGetCurrentIrpStackLocation(Irp);
nextStack = IoGetNextIrpStackLocation(Irp);
//
// Release the queue if it is frozen.
//
if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
ScsiClassReleaseQueue(deviceExtension->ClassDevice);
}
//
// Call to get the mapping for nt status.
//
status = ChangerInterpretSenseInfo(DeviceObject,srb, &retry);
} else {
status = STATUS_SUCCESS;
}
if (deviceExtension->MediaChangeNotificationRequired) {
deviceExtension->MediaChangeNotificationRequired = FALSE;
setMediaChange = FALSE;
retry = FALSE;
status = STATUS_VERIFY_REQUIRED;
}
//
// Free the structures allocated to do the switch/check verify.
//
if (srb->SenseInfoBuffer) {
ExFreePool(srb->SenseInfoBuffer);
}
ExFreePool(srb);
IoFreeIrp(Irp);
if (NT_SUCCESS(status)) {
PLIST_ENTRY request;
PIRP realIrp;
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
if (!IsListEmpty(&deviceExtension->WorkQueue)) {
//
// Extract the next request.
//
request = RemoveHeadList(&deviceExtension->WorkQueue);
realIrp = CONTAINING_RECORD(request, IRP, Tail.Overlay.ListEntry);
deviceExtension->OutstandingRequests++;
#if DBG
deviceExtension->ActiveRequest = realIrp;
#endif
//
// Update flags and timer.
//
sharedExtension->DeviceFlags &= ~(CHANGER_SWITCH_IN_PROGRESS | CHANGER_FREEZE_QUEUES);
sharedExtension->TimerValue = 0;
sharedExtension->RequestTimeOutValue = 0;
sharedExtension->CurrentDevice = deviceExtension;
sharedExtension->NextDevice = NULL;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
irpStack = IoGetCurrentIrpStackLocation(realIrp);
nextStack = IoGetNextIrpStackLocation(realIrp);
*nextStack = *irpStack;
IoSetCompletionRoutine(realIrp, ChangerCompletionRoutine, deviceExtension, TRUE, TRUE, TRUE);
//
// Send the request.
//
IoCallDriver(deviceExtension->ClassDevice, realIrp);
} else {
ASSERT(0);
}
return STATUS_MORE_PROCESSING_REQUIRED;
} else {
//
// If the status is not ready, wait and resend the switch.
//
if (retry) {
//
// Wait for a bit - 1/2 sec., to allow the switch to complete.
//
KeStallExecutionProcessor(5 * 100 * 1000);
//
// Try the switch again.
//
sharedExtension->SwitchToNewDisk(deviceExtension);
return STATUS_MORE_PROCESSING_REQUIRED;
} else {
PLIST_ENTRY request;
PIRP realIrp;
ULONG i;
PCH_DEVICE_EXTENSION tmp;
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
if ((status == STATUS_VERIFY_REQUIRED) && (setMediaChange)) {
for (tmp = deviceExtension->Next,i = 0; i < sharedExtension->DiscsPresent - 1; i++) {
tmp->MediaChangeNotificationRequired = TRUE;
tmp = tmp->Next;
}
}
if (!IsListEmpty(&deviceExtension->WorkQueue)) {
//
// Give up on this one. Yank the next request and complete it with this error.
//
request = RemoveHeadList(&deviceExtension->WorkQueue);
realIrp = CONTAINING_RECORD(request, IRP, Tail.Overlay.ListEntry);
sharedExtension->TimerValue = 0;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
if (status == STATUS_VERIFY_REQUIRED) {
irpStack = IoGetCurrentIrpStackLocation(realIrp);
if (irpStack->Flags & SL_OVERRIDE_VERIFY_VOLUME) {
//
// Retry the switch.
//
DebugPrint((1,
"ChangeDiskCompletion: Retrying switch - OVERRIDE flag set\n"));
//
// Stuff the request back into the queue.
//
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
InsertHeadList(&deviceExtension->WorkQueue,&realIrp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Retry the switch.
//
sharedExtension->SwitchToNewDisk(deviceExtension);
return STATUS_MORE_PROCESSING_REQUIRED;
}
}
//
// complete the request.
//
realIrp->IoStatus.Information = 0;
realIrp->IoStatus.Status = status;
if (IoIsErrorUserInduced(status)) {
IoSetHardErrorOrVerifyDevice(realIrp, deviceExtension->ClassDevice);
}
IoCompleteRequest(realIrp, IO_DISK_INCREMENT);
KeAcquireSpinLock(&sharedExtension->SpinLock, &irql);
}
//
// Find the next device, and start the switch to get it going. Wrap around
// to the current device, if necessary.
//
for (tmp = deviceExtension->Next,i = 0; i < sharedExtension->DiscsPresent; i++) {
if (!IsListEmpty(&tmp->WorkQueue)) {
//
// Indicate that no more requests should be sent.
//
sharedExtension->DeviceFlags |= CHANGER_FREEZE_QUEUES;
sharedExtension->NextDevice = tmp;
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
//
// Call the appropriate switch routine.
//
DebugPrint((3,
"ChangeDiskCompletion: Switch called from %d, status %x\n",
__LINE__,
status));
sharedExtension->SwitchToNewDisk(tmp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
tmp = tmp->Next;
}
sharedExtension->DeviceFlags &= ~(CHANGER_SWITCH_IN_PROGRESS | CHANGER_FREEZE_QUEUES);
sharedExtension->TimerValue = 0;
sharedExtension->NextDevice = NULL;
DebugPrint((2,
"ChangeDiskCompletion: Returning without starting new request.\n"));
DebugPrint((2,
"Status of switch %x\n",
status));
KeReleaseSpinLock(&sharedExtension->SpinLock, irql);
return STATUS_MORE_PROCESSING_REQUIRED;
}
}
}
NTSTATUS
ChangerInterpretSenseInfo(
IN PDEVICE_OBJECT DeviceObject,
IN PSCSI_REQUEST_BLOCK Srb,
IN OUT PBOOLEAN Retry
)
/*++
Routine Description:
This routine interprets the data returned from the SCSI
request sense. It determines the status to return in the
IRP.
Arguments:
Srb - Supplies the scsi request block which failed.
Return Value:
The mapped NTSTATUS
--*/
{
PCH_DEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
PCH_DEVICE_EXTENSION tmpExtension;
PSHARED_DEVICE_EXTENSION sharedExtension = deviceExtension->SharedDeviceExtension;
PSENSE_DATA senseBuffer = Srb->SenseInfoBuffer;
NTSTATUS status;
ULONG i;
*Retry = FALSE;
//
// Check that request sense buffer is valid.
//
if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) {
DebugPrint((1,
"ChangerInterpretSenseInfo: Error code is %x\n",
senseBuffer->ErrorCode));
DebugPrint((1,
"ChangerInterpretSenseInfo: Sense key is %x\n",
senseBuffer->SenseKey));
DebugPrint((1,
"ChangerInterpretSenseInfo: Additional sense code is %x\n",
senseBuffer->AdditionalSenseCode));
DebugPrint((1,
"ChangerInterpretSenseInfo: Additional sense code qualifier is %x\n",
senseBuffer->AdditionalSenseCodeQualifier));
switch (senseBuffer->SenseKey & 0xf) {
case SCSI_SENSE_NOT_READY:
DebugPrint((1,
"ChangerInterpretSenseInfo: Device not ready\n"));
status = STATUS_DEVICE_NOT_READY;
switch (senseBuffer->AdditionalSenseCode) {
case SCSI_ADSENSE_LUN_NOT_READY:
DebugPrint((1,
"ChangerInterpretSenseInfo: Lun not ready\n"));
switch (senseBuffer->AdditionalSenseCodeQualifier) {
case SCSI_SENSEQ_BECOMING_READY:
DebugPrint((1, "ChangerInterpretSenseInfo:"
" In process of becoming ready\n"));
*Retry = TRUE;
break;
case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED:
DebugPrint((1, "ChangerInterpretSenseInfo:"
" Manual intervention required\n"));
status = STATUS_NO_MEDIA_IN_DEVICE;
break;
case SCSI_SENSEQ_INIT_COMMAND_REQUIRED:
default:
DebugPrint((1, "ChangerInterpretSenseInfo:"
" Initializing command required\n"));
*Retry = TRUE;
break;
} // end switch (senseBuffer->AdditionalSenseCodeQualifier)
break;
case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE:
DebugPrint((1,
"ChangerInterpretSenseInfo:"
" No Media in device.\n"));
status = STATUS_NO_MEDIA_IN_DEVICE;
break;
}
break;
case SCSI_SENSE_MEDIUM_ERROR:
DebugPrint((1,"ChangerInterpretSenseInfo: Bad media\n"));
status = STATUS_DEVICE_DATA_ERROR;
break;
case SCSI_SENSE_HARDWARE_ERROR:
DebugPrint((1,"ChangerInterpretSenseInfo: Hardware error\n"));
status = STATUS_IO_DEVICE_ERROR;
*Retry = TRUE;
break;
case SCSI_SENSE_ILLEGAL_REQUEST:
DebugPrint((1, "ChangerInterpretSenseInfo: Illegal SCSI request\n"));
status = STATUS_INVALID_DEVICE_REQUEST;
switch (senseBuffer->AdditionalSenseCode) {
case SCSI_ADSENSE_ILLEGAL_COMMAND:
DebugPrint((1, "ChangerInterpretSenseInfo: Illegal command\n"));
break;
case SCSI_ADSENSE_ILLEGAL_BLOCK:
DebugPrint((1, "ChangerInterpretSenseInfo: Illegal block address\n"));
status = STATUS_NONEXISTENT_SECTOR;
break;
case SCSI_ADSENSE_INVALID_LUN:
DebugPrint((1,"ChangerInterpretSenseInfo: Invalid LUN\n"));
status = STATUS_NO_SUCH_DEVICE;
break;
case SCSI_ADSENSE_MUSIC_AREA:
DebugPrint((1,"ChangerInterpretSenseInfo: Music area\n"));
break;
case SCSI_ADSENSE_DATA_AREA:
DebugPrint((1,"ChangerInterpretSenseInfo: Data area\n"));
break;
case SCSI_ADSENSE_VOLUME_OVERFLOW:
DebugPrint((1, "ChangerInterpretSenseInfo: Volume overflow\n"));
break;
case SCSI_ADSENSE_INVALID_CDB:
DebugPrint((1, "ChangerInterpretSenseInfo: Invalid CDB\n"));
break;
} // end switch (senseBuffer->AdditionalSenseCode)
break;
case SCSI_SENSE_UNIT_ATTENTION:
switch (senseBuffer->AdditionalSenseCode) {
case SCSI_ADSENSE_MEDIUM_CHANGED:
DebugPrint((1, "ChangerInterpretSenseInfo: Media changed\n"));
break;
case SCSI_ADSENSE_BUS_RESET:
DebugPrint((1,"ChangerInterpretSenseInfo: Bus reset\n"));
break;
default:
DebugPrint((1,"ChangerInterpretSenseInfo: Unit attention\n"));
break;
} // end switch (senseBuffer->AdditionalSenseCode)
//
// Mark up the associated real device object.
//
deviceExtension->ClassDevice->Flags |= DO_VERIFY_VOLUME;
status = STATUS_VERIFY_REQUIRED;
*Retry = FALSE;
break;
case SCSI_SENSE_ABORTED_COMMAND:
DebugPrint((1,"ChangerInterpretSenseInfo: Command aborted\n"));
status = STATUS_IO_DEVICE_ERROR;
break;
case SCSI_SENSE_RECOVERED_ERROR:
DebugPrint((1,"ChangerInterpretSenseInfo: Recovered error\n"));
status = STATUS_SUCCESS;
break;
case SCSI_SENSE_NO_SENSE:
status = STATUS_SUCCESS;
break;
default:
DebugPrint((1, "ChangerInterpretSenseInfo: Unrecognized sense code\n"));
status = STATUS_IO_DEVICE_ERROR;
*Retry = TRUE;
break;
} // end switch (senseBuffer->SenseKey & 0xf)
} else {
//
// Request sense buffer not valid. No sense information
// to pinpoint the error. Return general request fail.
//
DebugPrint((1,"ChangerInterpretSenseInfo: Request sense info not valid. SrbStatus %2x. Scsi status %x\n",
SRB_STATUS(Srb->SrbStatus),
Srb->ScsiStatus));
switch (SRB_STATUS(Srb->SrbStatus)) {
case SRB_STATUS_INVALID_LUN:
case SRB_STATUS_INVALID_TARGET_ID:
case SRB_STATUS_NO_DEVICE:
case SRB_STATUS_NO_HBA:
case SRB_STATUS_INVALID_PATH_ID:
status = STATUS_NO_SUCH_DEVICE;
break;
case SRB_STATUS_COMMAND_TIMEOUT:
case SRB_STATUS_ABORTED:
case SRB_STATUS_TIMEOUT:
status = STATUS_IO_TIMEOUT;
*Retry = TRUE;
break;
case SRB_STATUS_SELECTION_TIMEOUT:
status = STATUS_DEVICE_NOT_CONNECTED;
break;
case SRB_STATUS_DATA_OVERRUN:
status = STATUS_DATA_OVERRUN;
break;
case SRB_STATUS_PHASE_SEQUENCE_FAILURE:
status = STATUS_IO_DEVICE_ERROR;
break;
case SRB_STATUS_REQUEST_FLUSHED:
if (deviceExtension->ClassDevice->Flags & DO_VERIFY_VOLUME ) {
status = STATUS_VERIFY_REQUIRED;
} else {
status = STATUS_IO_DEVICE_ERROR;
*Retry = TRUE;
}
break;
case SRB_STATUS_INVALID_REQUEST:
//
// An invalid request was attempted.
//
status = STATUS_INVALID_DEVICE_REQUEST;
break;
case SRB_STATUS_BUS_RESET:
status = STATUS_IO_DEVICE_ERROR;
*Retry = TRUE;
break;
case SRB_STATUS_ERROR:
status = STATUS_IO_DEVICE_ERROR;
if (Srb->ScsiStatus == SCSISTAT_BUSY) {
status = STATUS_DEVICE_NOT_READY;
*Retry = TRUE;
}
break;
default:
status = STATUS_IO_DEVICE_ERROR;
*Retry = TRUE;
break;
}
}
return status;
}
BOOLEAN
IsThisASanyo(
IN PDEVICE_OBJECT DeviceObject,
IN PSCSI_ADAPTER_BUS_INFO AdapterInfo
)
/*++
Routine Description:
This routine is called by DriverEntry to determine whether a Sanyo 3-CD
changer device is present.
Arguments:
DeviceObject - Supplies the device object for the 'real' device.
PathId -
Return Value:
TRUE - if a Sanyo changer device is found.
--*/
{
KEVENT event;
PIRP irp;
IO_STATUS_BLOCK ioStatus;
PCHAR inquiryBuffer;
NTSTATUS status;
ULONG scsiBus;
PINQUIRYDATA inquiryData;
PSCSI_INQUIRY_DATA lunInfo;
PSCSI_ADDRESS scsiAddress;
scsiAddress = ExAllocatePool(NonPagedPool, sizeof(SCSI_ADDRESS));
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest(IOCTL_SCSI_GET_ADDRESS,
DeviceObject,
NULL,
0,
scsiAddress,
sizeof(SCSI_ADDRESS),
FALSE,
&event,
&ioStatus);
if (!irp) {
return FALSE;
}
status = IoCallDriver(DeviceObject, irp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = ioStatus.Status;
}
if (!NT_SUCCESS(status)) {
return FALSE;
}
inquiryBuffer = (PCHAR)AdapterInfo;
for (scsiBus=0; scsiBus < (ULONG)AdapterInfo->NumberOfBuses; scsiBus++) {
//
// Get the SCSI bus scan data for this bus.
//
lunInfo = (PVOID) (inquiryBuffer + AdapterInfo->BusData[scsiBus].InquiryDataOffset);
for (;;) {
if ((lunInfo->PathId == scsiAddress->PathId) &&
(lunInfo->TargetId == scsiAddress->TargetId) &&
(lunInfo->Lun == scsiAddress->Lun)) {
inquiryData = (PVOID) lunInfo->InquiryData;
if ((RtlCompareMemory(inquiryData->VendorId, "TORiSAN CD-ROM CDR-C", 20) == 20) ||
(RtlCompareMemory(inquiryData->VendorId, "TORiSAN CD-ROM CDR_C", 20) == 20)) {
ExFreePool(inquiryBuffer);
return TRUE;
}
}
if (!lunInfo->NextInquiryDataOffset) {
break;
}
lunInfo = (PVOID) (inquiryBuffer + lunInfo->NextInquiryDataOffset);
}
}
ExFreePool(inquiryBuffer);
return FALSE;
}
BOOLEAN
IsThisAnAtapiChanger(
IN PDEVICE_OBJECT DeviceObject,
OUT PULONG DiscsPresent
)
/*++
Routine Description:
This routine is called by DriverEntry to determine whether an Atapi
changer device is present.
Arguments:
DeviceObject - Supplies the device object for the 'real' device.
DiscsPresent - Supplies a pointer to the number of Discs supported by the changer.
Return Value:
TRUE - if an Atapi changer device is found.
--*/
{
NTSTATUS status;
PDEVICE_EXTENSION deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
PMECHANICAL_STATUS_INFORMATION_HEADER mechanicalStatusBuffer;
SCSI_REQUEST_BLOCK srb;
PCDB cdb = (PCDB)srb.Cdb;
BOOLEAN retVal = FALSE;
*DiscsPresent = 0;
if (deviceExtension->DeviceFlags & DEV_NO_12BYTE_CDB) {
return FALSE;
}
//
// Build and issue the mechanical status command.
//
mechanicalStatusBuffer = ExAllocatePool(NonPagedPoolCacheAligned,
sizeof(MECHANICAL_STATUS_INFORMATION_HEADER));
if (!mechanicalStatusBuffer) {
retVal = FALSE;
} else {
//
// Build and send the Mechanism status CDB.
//
RtlZeroMemory(&srb, sizeof(srb));
srb.CdbLength = 12;
srb.TimeOutValue = 20;
cdb->MECH_STATUS.OperationCode = SCSIOP_MECHANISM_STATUS;
cdb->MECH_STATUS.AllocationLength[1] = sizeof(MECHANICAL_STATUS_INFORMATION_HEADER);
status = ScsiClassSendSrbSynchronous(DeviceObject,
&srb,
mechanicalStatusBuffer,
sizeof(MECHANICAL_STATUS_INFORMATION_HEADER),
FALSE);
if (status == STATUS_SUCCESS) {
//
// Indicate number of slots available
//
*DiscsPresent = mechanicalStatusBuffer->NumberAvailableSlots;
if (*DiscsPresent > 1) {
retVal = TRUE;
} else {
//
// If only one disc or it returned zero, no need for this driver.
//
retVal = FALSE;
}
} else {
//
// Device doesn't support this command.
//
retVal = FALSE;
}
ExFreePool(mechanicalStatusBuffer);
}
return retVal;
}
BOOLEAN
SupportedDevice(
IN PDEVICE_OBJECT PortDeviceObject,
IN PDEVICE_OBJECT ClassDeviceObject,
IN PSHARED_DEVICE_EXTENSION SharedExtension,
IN PSCSI_ADAPTER_BUS_INFO AdapterInfo
)
{
ULONG discsPresent;
if (IsThisASanyo(ClassDeviceObject, AdapterInfo)) {
SharedExtension->DiscsPresent = 3;
SharedExtension->SwitchToNewDisk = SanyoSwitchToNewDisk;
return TRUE;
}
if (IsThisAnAtapiChanger(ClassDeviceObject, &discsPresent)) {
SharedExtension->DiscsPresent = discsPresent;
SharedExtension->SwitchToNewDisk = AtapiSwitchToNewDisk;
return TRUE;
}
return FALSE;
}
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
This routine is called at system initialization time to initialize
this driver.
Arguments:
DriverObject - Supplies the driver object.
RegistryPath - Supplies the registry path for this driver.
Return Value:
STATUS_SUCCESS - We could initialize at least one device.
STATUS_NO_SUCH_DEVICE - We could not initialize even one device.
--*/
{
ULONG deviceNumber = 0;
ULONG createdDevices = 0;
PDEVICE_OBJECT classDeviceObject;
PDEVICE_OBJECT portDeviceObject;
PDEVICE_OBJECT deviceObject;
PDEVICE_EXTENSION classDeviceExtension;
PSHARED_DEVICE_EXTENSION sharedExtension;
PCH_DEVICE_EXTENSION deviceExtension;
PCH_DEVICE_EXTENSION tmp;
STRING deviceNameString;
UNICODE_STRING unicodeDeviceName;
PFILE_OBJECT fileObject;
PCHAR buffer;
CCHAR deviceNameBuffer[256];
NTSTATUS status = STATUS_NO_SUCH_DEVICE, retStatus = STATUS_NO_SUCH_DEVICE;
BOOLEAN added = FALSE;
//
// Allocate the shared extension. This will be used by all dev. exts that
// are tied to the device.
//
sharedExtension = ExAllocatePool(NonPagedPool, sizeof(SHARED_DEVICE_EXTENSION));
if (!sharedExtension) {
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(sharedExtension, sizeof(SHARED_DEVICE_EXTENSION));
KeInitializeSpinLock(&sharedExtension->SpinLock);
//
// Setup entry points.
//
DriverObject->MajorFunction[IRP_MJ_CREATE] = PassThrough;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = PassThrough;
DriverObject->MajorFunction[IRP_MJ_READ] = GeneralDispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = GeneralDispatch;
DriverObject->MajorFunction[IRP_MJ_SCSI] = GeneralDispatch;
//
// For each cdrom, determine whether we are interested in it.
// If so, create a filter dev. obj., and attach to the real one.
//
for (; deviceNumber < IoGetConfigurationInformation()->CdRomCount; deviceNumber++) {
sprintf(deviceNameBuffer, "\\Device\\CdRom%d", deviceNumber);
RtlInitString(&deviceNameString, deviceNameBuffer);
status = RtlAnsiStringToUnicodeString(&unicodeDeviceName,
&deviceNameString,
TRUE);
if (!NT_SUCCESS(status)){
goto CleanUpAndExit;
}
status = IoGetDeviceObjectPointer(&unicodeDeviceName,
FILE_READ_ATTRIBUTES,
&fileObject,
&classDeviceObject);
//
// The network detect code is garbage. There is a chance this
// driver is being loaded just because the net code cannot figure
// out what drivers to load to do detect. Sometimes this load
// occurs when there is no cdrom - an error will be returned above
// and sometimes it is loaded when there is a cdrom and the cdrom
// is already mounted. Check for these conditions now.
//
if ((NT_SUCCESS(status)) &&
// status was success so there is a classDeviceObject
(classDeviceObject->DeviceType == FILE_DEVICE_CD_ROM)) {
classDeviceExtension = classDeviceObject->DeviceExtension;
portDeviceObject = classDeviceExtension->PortDeviceObject;
//
// Get inquiry data for scsiportN, that corresponds to this cdrom
//
status = ScsiClassGetInquiryData(portDeviceObject, (PSCSI_ADAPTER_BUS_INFO *) &buffer);
if (!NT_SUCCESS(status)) {
goto CleanUpAndExit;
}
//
// Determine is a new shared extension is needed - case of multiple changers.
//
if (createdDevices && (createdDevices == sharedExtension->DiscsPresent)) {
IoInitializeTimer(sharedExtension->DeviceList->DeviceObject, ChangerTickHandler, NULL);
IoStartTimer(sharedExtension->DeviceList->DeviceObject);
//
// Allocate the shared extension. This will be used by all dev. exts that
// are tied to the device.
//
sharedExtension = ExAllocatePool(NonPagedPool, sizeof(SHARED_DEVICE_EXTENSION));
if (!sharedExtension) {
return STATUS_INSUFFICIENT_RESOURCES;
}
added = FALSE;
RtlZeroMemory(sharedExtension, sizeof(SHARED_DEVICE_EXTENSION));
KeInitializeSpinLock(&sharedExtension->SpinLock);
}
if (SupportedDevice(portDeviceObject,classDeviceObject,sharedExtension, (PSCSI_ADAPTER_BUS_INFO)buffer)) {
retStatus = STATUS_SUCCESS;
added = TRUE;
//
// Build filter device object.
//
status = IoCreateDevice(DriverObject,
sizeof(CH_DEVICE_EXTENSION),
NULL,
FILE_DEVICE_CD_ROM,
FILE_REMOVABLE_MEDIA | FILE_READ_ONLY_DEVICE,
FALSE,
&deviceObject);
if (!NT_SUCCESS(status)) {
goto CleanUpAndExit;
}
createdDevices++;
deviceObject->Flags |= DO_DIRECT_IO;
//
// Set up required stack size in device object.
//
deviceObject->StackSize = (CCHAR)classDeviceObject->StackSize + 1;
deviceExtension = deviceObject->DeviceExtension;
//
// Attach to the real device.
//
status = IoAttachDeviceByPointer(deviceObject,
classDeviceObject);
if (!NT_SUCCESS(status)) {
goto CleanUpAndExit;
}
//
// Setup deviceExtension
//
deviceExtension->DeviceObject = deviceObject;
deviceExtension->ClassDevice = classDeviceObject;
deviceExtension->SharedDeviceExtension = sharedExtension;
if (sharedExtension->DeviceList) {
tmp->Next = deviceExtension;
tmp = deviceExtension;
tmp->Next = sharedExtension->DeviceList;
} else {
tmp = deviceExtension;
sharedExtension->DeviceList = deviceExtension;
deviceExtension->Next = deviceExtension;
//
// Assume platter 0 is selected.
//
sharedExtension->CurrentDevice = deviceExtension;
}
InitializeListHead(&deviceExtension->WorkQueue);
}
} else {
goto CleanUpAndExit;
}
}
if (added) {
//
// Setup the last found changer's timer.
//
IoInitializeTimer(sharedExtension->DeviceList->DeviceObject, ChangerTickHandler, NULL);
IoStartTimer(sharedExtension->DeviceList->DeviceObject);
retStatus = STATUS_SUCCESS;
} else {
CleanUpAndExit:
if (!NT_SUCCESS(status)) {
if (sharedExtension) {
ExFreePool(sharedExtension);
}
}
}
return retStatus;
}