/*++ Copyright (C) Microsoft Corporation, 1996 - 1999 Module Name: mcd.c Abstract: Environment: Kernel mode Revision History : --*/ #include "stdarg.h" #include "ntddk.h" #include "mcd.h" #include "initguid.h" #include "ntddstor.h" typedef struct _MCD_CLASS_DATA { LONG DeviceOpen; UNICODE_STRING MediumChangerInterfaceString; BOOLEAN DosNameCreated; } MCD_CLASS_DATA, *PMCD_CLASS_DATA; NTSTATUS ChangerClassCreateClose ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); NTSTATUS ChangerClassDeviceControl ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); VOID ChangerClassError( PDEVICE_OBJECT DeviceObject, PSCSI_REQUEST_BLOCK Srb, NTSTATUS *Status, BOOLEAN *Retry ); NTSTATUS ChangerAddDevice( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject ); NTSTATUS ChangerStartDevice( IN PDEVICE_OBJECT Fdo ); NTSTATUS ChangerStopDevice( IN PDEVICE_OBJECT DeviceObject, IN UCHAR Type ); NTSTATUS ChangerInitDevice( IN PDEVICE_OBJECT Fdo ); NTSTATUS ChangerRemoveDevice( IN PDEVICE_OBJECT DeviceObject, IN UCHAR Type ); NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ); VOID ChangerUnload( IN PDRIVER_OBJECT DriverObject ); NTSTATUS CreateChangerDeviceObject( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject ); NTSTATUS ClasspSendSynchronousCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ); #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, DriverEntry) #pragma alloc_text(PAGE, ChangerUnload) #pragma alloc_text(PAGE, CreateChangerDeviceObject) #pragma alloc_text(PAGE, ChangerClassCreateClose) #pragma alloc_text(PAGE, ChangerClassDeviceControl) #pragma alloc_text(PAGE, ChangerAddDevice) #pragma alloc_text(PAGE, ChangerStartDevice) #pragma alloc_text(PAGE, ChangerInitDevice) #pragma alloc_text(PAGE, ChangerRemoveDevice) #pragma alloc_text(PAGE, ChangerStopDevice) #pragma alloc_text(PAGE, ChangerReadWriteVerification) #endif NTSTATUS ChangerClassCreateClose ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine handles CREATE/CLOSE requests. As these are exclusive devices, don't allow multiple opens. Arguments: DeviceObject Irp Return Value: NT Status --*/ { PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; PMCD_CLASS_DATA mcdClassData; ULONG miniclassExtSize; NTSTATUS status = STATUS_SUCCESS; PAGED_CODE(); mcdClassData = (PMCD_CLASS_DATA)(fdoExtension->CommonExtension.DriverData); miniclassExtSize = ChangerAdditionalExtensionSize(); // // The class library's private data is after the miniclass's. // (ULONG_PTR)mcdClassData += miniclassExtSize; if (irpStack->MajorFunction == IRP_MJ_CLOSE) { DebugPrint((3, "ChangerClassCreateClose - IRP_MJ_CLOSE\n")); // // Indicate that the device is available for others. // mcdClassData->DeviceOpen = 0; status = STATUS_SUCCESS; } else if (irpStack->MajorFunction == IRP_MJ_CREATE) { DebugPrint((3, "ChangerClassCreateClose - IRP_MJ_CREATE\n")); // // If already opened, return busy. // if (mcdClassData->DeviceOpen) { DebugPrint((1, "ChangerClassCreateClose - returning DEVICE_BUSY. DeviceOpen - %x\n", mcdClassData->DeviceOpen)); status = STATUS_DEVICE_BUSY; } else { // // Indicate that the device is busy. // InterlockedIncrement(&mcdClassData->DeviceOpen); status = STATUS_SUCCESS; } } Irp->IoStatus.Status = status; ClassReleaseRemoveLock(DeviceObject, Irp); ClassCompleteRequest(DeviceObject,Irp, IO_NO_INCREMENT); return status; } // end ChangerCreate() NTSTATUS ChangerClassDeviceControl ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; NTSTATUS status; PAGED_CODE(); switch (irpStack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_CHANGER_GET_PARAMETERS: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_GET_PARAMETERS\n")); // // Validate buffer length. // if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(GET_CHANGER_PARAMETERS)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerGetParameters(DeviceObject, Irp); } break; case IOCTL_CHANGER_GET_STATUS: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_GET_STATUS\n")); status = ChangerGetStatus(DeviceObject, Irp); break; case IOCTL_CHANGER_GET_PRODUCT_DATA: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_GET_PRODUCT_DATA\n")); if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(CHANGER_PRODUCT_DATA)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerGetProductData(DeviceObject, Irp); } break; case IOCTL_CHANGER_SET_ACCESS: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_SET_ACCESS\n")); if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CHANGER_SET_ACCESS)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerSetAccess(DeviceObject, Irp); } break; case IOCTL_CHANGER_GET_ELEMENT_STATUS: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_GET_ELEMENT_STATUS\n")); if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CHANGER_READ_ELEMENT_STATUS)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { PCHANGER_READ_ELEMENT_STATUS readElementStatus = Irp->AssociatedIrp.SystemBuffer; ULONG length = readElementStatus->ElementList.NumberOfElements * sizeof(CHANGER_ELEMENT_STATUS); // // Further validate parameters. // if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < length) { status = STATUS_BUFFER_TOO_SMALL; } else if (length == 0) { status = STATUS_INVALID_PARAMETER; } else { status = ChangerGetElementStatus(DeviceObject, Irp); } } break; case IOCTL_CHANGER_INITIALIZE_ELEMENT_STATUS: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_INITIALIZE_ELEMENT_STATUS\n")); if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CHANGER_INITIALIZE_ELEMENT_STATUS)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerInitializeElementStatus(DeviceObject, Irp); } break; case IOCTL_CHANGER_SET_POSITION: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_SET_POSITION\n")); if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CHANGER_SET_POSITION)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerSetPosition(DeviceObject, Irp); } break; case IOCTL_CHANGER_EXCHANGE_MEDIUM: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_EXCHANGE_MEDIUM\n")); status = ChangerExchangeMedium(DeviceObject, Irp); break; case IOCTL_CHANGER_MOVE_MEDIUM: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_MOVE_MEDIUM\n")); if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CHANGER_MOVE_MEDIUM)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerMoveMedium(DeviceObject, Irp); } break; case IOCTL_CHANGER_REINITIALIZE_TRANSPORT: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_REINITIALIZE_TRANSPORT\n")); if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CHANGER_ELEMENT)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerReinitializeUnit(DeviceObject, Irp); } break; case IOCTL_CHANGER_QUERY_VOLUME_TAGS: DebugPrint((3, "Mcd.ChangerDeviceControl: IOCTL_CHANGER_QUERY_VOLUME_TAGS\n")); if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CHANGER_SEND_VOLUME_TAG_INFORMATION)) { status = STATUS_INFO_LENGTH_MISMATCH; } else if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(READ_ELEMENT_ADDRESS_INFO)) { status = STATUS_INFO_LENGTH_MISMATCH; } else { status = ChangerQueryVolumeTags(DeviceObject, Irp); } break; default: DebugPrint((1, "Mcd.ChangerDeviceControl: Unhandled IOCTL\n")); // // Pass the request to the common device control routine. // return ClassDeviceControl(DeviceObject, Irp); break; } Irp->IoStatus.Status = status; if (!NT_SUCCESS(status) && IoIsErrorUserInduced(status)) { DebugPrint((1, "Mcd.ChangerDeviceControl: IOCTL %x, status %x\n", irpStack->Parameters.DeviceIoControl.IoControlCode, status)); IoSetHardErrorOrVerifyDevice(Irp, DeviceObject); } ClassReleaseRemoveLock(DeviceObject, Irp); ClassCompleteRequest(DeviceObject,Irp, IO_NO_INCREMENT); return status; } VOID ChangerClassError( PDEVICE_OBJECT DeviceObject, PSCSI_REQUEST_BLOCK Srb, NTSTATUS *Status, BOOLEAN *Retry ) /*++ Routine Description: Arguments: DeviceObject Irp Return Value: Final Nt status indicating the results of the operation. Notes: --*/ { PSENSE_DATA senseBuffer = Srb->SenseInfoBuffer; PIRP irp = Srb->OriginalRequest; PAGED_CODE(); if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) { switch (senseBuffer->SenseKey & 0xf) { case SCSI_SENSE_ILLEGAL_REQUEST: switch (senseBuffer->AdditionalSenseCode) { case SCSI_ADSENSE_ILLEGAL_BLOCK: if (senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_ILLEGAL_ELEMENT_ADDR ) { DebugPrint((1, "MediumChanger: An operation was attempted on an invalid element\n")); // // Attemped operation to an invalid element. // *Retry = FALSE; *Status = STATUS_ILLEGAL_ELEMENT_ADDRESS; } break; case SCSI_ADSENSE_POSITION_ERROR: if (senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_SOURCE_EMPTY) { DebugPrint((1, "MediumChanger: The specified source element has no media\n")); // // The indicated source address has no media. // *Retry = FALSE; *Status = STATUS_SOURCE_ELEMENT_EMPTY; } else if (senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_DESTINATION_FULL) { DebugPrint((1, "MediumChanger: The specified destination element already has media.\n")); // // The indicated destination already contains media. // *Retry = FALSE; *Status = STATUS_DESTINATION_ELEMENT_FULL; } break; default: break; } default: break; } // end switch } // // Allow device-specific module to update this. // ChangerError(DeviceObject, Srb, Status, Retry); return; } NTSTATUS ChangerAddDevice( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject ) /*++ Routine Description: This routine creates and initializes a new FDO for the corresponding PDO. It may perform property queries on the FDO but cannot do any media access operations. Arguments: DriverObject - MC class driver object. Pdo - the physical device object we are being added to Return Value: status --*/ { PULONG devicesFound = NULL; NTSTATUS status; PAGED_CODE(); // // Get the address of the count of the number of tape devices already initialized. // devicesFound = &IoGetConfigurationInformation()->MediumChangerCount; status = CreateChangerDeviceObject(DriverObject, PhysicalDeviceObject); if(NT_SUCCESS(status)) { (*devicesFound)++; } return status; } NTSTATUS ChangerStartDevice( IN PDEVICE_OBJECT Fdo ) /*++ Routine Description: This routine is called after InitDevice, and creates the symbolic link, and sets up information in the registry. The routine could be called multiple times, in the event of a StopDevice. Arguments: Fdo - a pointer to the functional device object for this device Return Value: status --*/ { PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; PINQUIRYDATA inquiryData = NULL; ULONG pageLength; ULONG inquiryLength; SCSI_REQUEST_BLOCK srb; PCDB cdb; NTSTATUS status; PMCD_CLASS_DATA mcdClassData = (PMCD_CLASS_DATA)fdoExtension->CommonExtension.DriverData; ULONG miniClassExtSize = ChangerAdditionalExtensionSize(); PAGED_CODE(); // // Build and send request to get inquiry data. // inquiryData = ExAllocatePool(NonPagedPoolCacheAligned, MAXIMUM_CHANGER_INQUIRY_DATA); if (!inquiryData) { // // The buffer cannot be allocated. // return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(&srb, SCSI_REQUEST_BLOCK_SIZE); // // Set timeout value. // srb.TimeOutValue = 2; srb.CdbLength = 6; cdb = (PCDB)srb.Cdb; // // Set CDB operation code. // cdb->CDB6INQUIRY.OperationCode = SCSIOP_INQUIRY; // // Set allocation length to inquiry data buffer size. // cdb->CDB6INQUIRY.AllocationLength = MAXIMUM_CHANGER_INQUIRY_DATA; status = ClassSendSrbSynchronous(Fdo, &srb, inquiryData, MAXIMUM_CHANGER_INQUIRY_DATA, FALSE); if (SRB_STATUS(srb.SrbStatus) == SRB_STATUS_SUCCESS || SRB_STATUS(srb.SrbStatus) == SRB_STATUS_DATA_OVERRUN) { srb.SrbStatus = SRB_STATUS_SUCCESS; } if (srb.SrbStatus == SRB_STATUS_SUCCESS) { inquiryLength = inquiryData->AdditionalLength + FIELD_OFFSET(INQUIRYDATA, Reserved); if (inquiryLength > srb.DataTransferLength) { inquiryLength = srb.DataTransferLength; } } else { // // The class function will only write inquiryLength of inquiryData // to the reg. key. // inquiryLength = 0; } // // Add changer device info to registry // ClassUpdateInformationInRegistry(Fdo, "Changer", fdoExtension->DeviceNumber, inquiryData, inquiryLength); ExFreePool(inquiryData); return STATUS_SUCCESS; } NTSTATUS ChangerStopDevice( IN PDEVICE_OBJECT DeviceObject, IN UCHAR Type ) { return STATUS_SUCCESS; } #define CHANGER_SRB_LIST_SIZE 2 NTSTATUS ChangerInitDevice( IN PDEVICE_OBJECT Fdo ) /*++ Routine Description: This routine will complete the changer initialization. This includes allocating sense info buffers and srb s-lists. Additionally, the miniclass driver's init entry points are called. This routine will not clean up allocate resources if it fails - that is left for device stop/removal Arguments: Fdo - a pointer to the functional device object for this device Return Value: NTSTATUS --*/ { PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; PVOID senseData = NULL; NTSTATUS status; PVOID minitapeExtension; STORAGE_PROPERTY_ID propertyId; UNICODE_STRING interfaceName; PMCD_CLASS_DATA mcdClassData; PAGED_CODE(); // // Allocate request sense buffer. // senseData = ExAllocatePool(NonPagedPoolCacheAligned, SENSE_BUFFER_SIZE); if (senseData == NULL) { // // The buffer cannot be allocated. // status = STATUS_INSUFFICIENT_RESOURCES; goto ChangerInitDeviceExit; } // // Build the lookaside list for srb's for the device. Should only // need a couple. // ClassInitializeSrbLookasideList(&(fdoExtension->CommonExtension), CHANGER_SRB_LIST_SIZE); // // Set the sense data pointer in the device extension. // fdoExtension->SenseData = senseData; fdoExtension->TimeOutValue = 600; // // Call port driver to get adapter capabilities. // propertyId = StorageAdapterProperty; status = ClassGetDescriptor(fdoExtension->CommonExtension.LowerDeviceObject, &propertyId, &(fdoExtension->AdapterDescriptor)); if(!NT_SUCCESS(status)) { DebugPrint((1, "ChangerStartDevice: Unable to get adapter descriptor. Status %x\n", status)); goto ChangerInitDeviceExit; } // // Invoke the device-specific initialization function. // status = ChangerInitialize(Fdo); // // Register interfaces for this device. // RtlInitUnicodeString(&interfaceName, NULL); status = IoRegisterDeviceInterface(fdoExtension->LowerPdo, (LPGUID) &MediumChangerClassGuid, NULL, &interfaceName); if(NT_SUCCESS(status)) { ULONG miniclassExtSize; mcdClassData = (PMCD_CLASS_DATA)(fdoExtension->CommonExtension.DriverData); miniclassExtSize = ChangerAdditionalExtensionSize(); // // The class library's private data is after the miniclass's. // (ULONG_PTR)mcdClassData += miniclassExtSize; mcdClassData->MediumChangerInterfaceString = interfaceName; status = IoSetDeviceInterfaceState( &interfaceName, TRUE); if(!NT_SUCCESS(status)) { DebugPrint((1, "ChangerInitDevice: Unable to register Changer%x interface name - %x.\n", fdoExtension->DeviceNumber, status)); status = STATUS_SUCCESS; } } return status; // // Fall through and return whatever status the miniclass driver returned. // ChangerInitDeviceExit: if (senseData) { ExFreePool(senseData); } return status; } // End ChangerStartDevice NTSTATUS ChangerRemoveDevice( IN PDEVICE_OBJECT DeviceObject, IN UCHAR Type ) /*++ Routine Description: This routine is responsible for releasing any resources in use by the tape driver. Arguments: DeviceObject - the device object being removed Return Value: none - this routine may not fail --*/ { PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; PMCD_CLASS_DATA mcdClassData = (PMCD_CLASS_DATA)fdoExtension->CommonExtension.DriverData; ULONG miniClassExtSize = ChangerAdditionalExtensionSize(); WCHAR dosNameBuffer[64]; UNICODE_STRING dosUnicodeString; NTSTATUS status; PAGED_CODE(); if((Type == IRP_MN_QUERY_REMOVE_DEVICE) || (Type == IRP_MN_CANCEL_REMOVE_DEVICE)) { return STATUS_SUCCESS; } // // Free all allocated memory. // if (fdoExtension->DeviceDescriptor) { ExFreePool(fdoExtension->DeviceDescriptor); fdoExtension->DeviceDescriptor = NULL; } if (fdoExtension->AdapterDescriptor) { ExFreePool(fdoExtension->AdapterDescriptor); fdoExtension->AdapterDescriptor = NULL; } if (fdoExtension->SenseData) { ExFreePool(fdoExtension->SenseData); fdoExtension->SenseData = NULL; } // // Remove the lookaside list. // ClassDeleteSrbLookasideList(&fdoExtension->CommonExtension); (ULONG_PTR)mcdClassData += miniClassExtSize; if(mcdClassData->MediumChangerInterfaceString.Buffer != NULL) { IoSetDeviceInterfaceState(&(mcdClassData->MediumChangerInterfaceString), FALSE); RtlFreeUnicodeString(&(mcdClassData->MediumChangerInterfaceString)); // // Clear it. // RtlInitUnicodeString(&(mcdClassData->MediumChangerInterfaceString), NULL); } // // Delete the symbolic link "changerN". // if(mcdClassData->DosNameCreated) { swprintf(dosNameBuffer, L"\\DosDevices\\Changer%d", fdoExtension->DeviceNumber); RtlInitUnicodeString(&dosUnicodeString, dosNameBuffer); IoDeleteSymbolicLink(&dosUnicodeString); mcdClassData->DosNameCreated = FALSE; } // // Remove registry bits. // if(Type == IRP_MN_REMOVE_DEVICE) { IoGetConfigurationInformation()->MediumChangerCount--; } return STATUS_SUCCESS; } NTSTATUS ChangerReadWriteVerification( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is a stub that returns invalid device request. Arguments: DeviceObject - Supplies the device object. Irp - Supplies the I/O request packet. Return Value: STATUS_INVALID_DEVICE_REQUEST --*/ { return STATUS_INVALID_DEVICE_REQUEST; } 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. --*/ { CLASS_INIT_DATA InitializationData; // // Zero InitData // RtlZeroMemory (&InitializationData, sizeof(CLASS_INIT_DATA)); // // Set sizes // InitializationData.InitializationDataSize = sizeof(CLASS_INIT_DATA); InitializationData.FdoData.DeviceExtensionSize = sizeof(FUNCTIONAL_DEVICE_EXTENSION) + ChangerAdditionalExtensionSize() + sizeof(MCD_CLASS_DATA); InitializationData.FdoData.DeviceType = FILE_DEVICE_CHANGER; InitializationData.FdoData.DeviceCharacteristics = 0; // // Set entry points // InitializationData.FdoData.ClassStartDevice = ChangerStartDevice; InitializationData.FdoData.ClassInitDevice = ChangerInitDevice; InitializationData.FdoData.ClassStopDevice = ChangerStopDevice; InitializationData.FdoData.ClassRemoveDevice = ChangerRemoveDevice; InitializationData.ClassAddDevice = ChangerAddDevice; InitializationData.FdoData.ClassReadWriteVerification = NULL; InitializationData.FdoData.ClassDeviceControl = ChangerClassDeviceControl; InitializationData.FdoData.ClassError = ChangerClassError; InitializationData.FdoData.ClassShutdownFlush = NULL; InitializationData.FdoData.ClassCreateClose = ChangerClassCreateClose; // // Stub routine to make the class driver happy. // InitializationData.FdoData.ClassReadWriteVerification = ChangerReadWriteVerification; InitializationData.ClassUnload = ChangerUnload; // // Call the class init routine // return ClassInitialize( DriverObject, RegistryPath, &InitializationData); } VOID ChangerUnload( IN PDRIVER_OBJECT DriverObject ) { PAGED_CODE(); UNREFERENCED_PARAMETER(DriverObject); return; } NTSTATUS CreateChangerDeviceObject( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject ) /*++ Routine Description: This routine creates an object for the device and then searches the device for partitions and creates an object for each partition. Arguments: DriverObject - Pointer to driver object created by system. PhysicalDeviceObject - DeviceObject of the attached to device. Return Value: NTSTATUS --*/ { PDEVICE_OBJECT lowerDevice; PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL; CCHAR deviceNameBuffer[64]; NTSTATUS status; PDEVICE_OBJECT deviceObject; ULONG requiredStackSize; PVOID senseData; WCHAR dosNameBuffer[64]; WCHAR wideNameBuffer[64]; UNICODE_STRING dosUnicodeString; UNICODE_STRING deviceUnicodeString; PMCD_CLASS_DATA mcdClassData; ULONG mcdCount; PAGED_CODE(); DebugPrint((3,"CreateChangerDeviceObject: Enter routine\n")); lowerDevice = IoGetAttachedDeviceReference(PhysicalDeviceObject); // // Claim the device. Note that any errors after this // will goto the generic handler, where the device will // be released. // status = ClassClaimDevice(lowerDevice, FALSE); if(!NT_SUCCESS(status)) { // // Someone already had this device. // ObDereferenceObject(lowerDevice); return status; } // // Create device object for this device. // mcdCount = 0; do { sprintf(deviceNameBuffer, "\\Device\\Changer%d", mcdCount); status = ClassCreateDeviceObject(DriverObject, deviceNameBuffer, PhysicalDeviceObject, TRUE, &deviceObject); mcdCount++; } while (status == STATUS_OBJECT_NAME_COLLISION); if (!NT_SUCCESS(status)) { DebugPrint((1,"CreateChangerDeviceObjects: Can not create device %s\n", deviceNameBuffer)); goto CreateChangerDeviceObjectExit; } // // Indicate that IRPs should include MDLs. // deviceObject->Flags |= DO_DIRECT_IO; fdoExtension = deviceObject->DeviceExtension; // // Back pointer to device object. // fdoExtension->CommonExtension.DeviceObject = deviceObject; // // This is the physical device. // fdoExtension->CommonExtension.PartitionZeroExtension = fdoExtension; // // Initialize lock count to zero. The lock count is used to // disable the ejection mechanism when media is mounted. // fdoExtension->LockCount = 0; // // Save system tape number // fdoExtension->DeviceNumber = mcdCount - 1; // // Set the alignment requirements for the device based on the // host adapter requirements // if (lowerDevice->AlignmentRequirement > deviceObject->AlignmentRequirement) { deviceObject->AlignmentRequirement = lowerDevice->AlignmentRequirement; } // // Save the device descriptors // fdoExtension->AdapterDescriptor = NULL; fdoExtension->DeviceDescriptor = NULL; // // Clear the SrbFlags and disable synchronous transfers // fdoExtension->SrbFlags = SRB_FLAGS_DISABLE_SYNCH_TRANSFER; // // Attach to the PDO // fdoExtension->LowerPdo = PhysicalDeviceObject; fdoExtension->CommonExtension.LowerDeviceObject = IoAttachDeviceToDeviceStack(deviceObject, PhysicalDeviceObject); if(fdoExtension->CommonExtension.LowerDeviceObject == NULL) { // // The attach failed. Cleanup and return. // status = STATUS_UNSUCCESSFUL; goto CreateChangerDeviceObjectExit; } // // Create the dos port driver name. // swprintf(dosNameBuffer, L"\\DosDevices\\Changer%d", fdoExtension->DeviceNumber); RtlInitUnicodeString(&dosUnicodeString, dosNameBuffer); // // Recreate the deviceName // swprintf(wideNameBuffer, L"\\Device\\Changer%d", fdoExtension->DeviceNumber); RtlInitUnicodeString(&deviceUnicodeString, wideNameBuffer); mcdClassData = (PMCD_CLASS_DATA)(fdoExtension->CommonExtension.DriverData); (ULONG_PTR)mcdClassData += ChangerAdditionalExtensionSize(); if (NT_SUCCESS(IoAssignArcName(&dosUnicodeString, &deviceUnicodeString))) { mcdClassData->DosNameCreated = TRUE; } else { mcdClassData->DosNameCreated = FALSE; } // // The device is initialized properly - mark it as such. // deviceObject->Flags &= ~DO_DEVICE_INITIALIZING; ObDereferenceObject(lowerDevice); return(STATUS_SUCCESS); CreateChangerDeviceObjectExit: // // Release the device since an error occured. // // ClassClaimDevice(PortDeviceObject, // LunInfo, // TRUE, // NULL); ObDereferenceObject(lowerDevice); if (deviceObject != NULL) { IoDeleteDevice(deviceObject); } return status; } // end CreateChangerDeviceObject() NTSTATUS ChangerClassSendSrbSynchronous( IN PDEVICE_OBJECT Fdo, IN PSCSI_REQUEST_BLOCK Srb, IN PVOID BufferAddress, IN ULONG BufferLength, IN BOOLEAN WriteToDevice ) { PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; IO_STATUS_BLOCK ioStatus; ULONG controlType; PIRP irp; PIO_STACK_LOCATION irpStack; KEVENT event; PUCHAR senseInfoBuffer; ULONG retryCount = MAXIMUM_RETRIES; NTSTATUS status; BOOLEAN retry; // // NOTE: While this code may look as though it could be pagable, // making it pagable creates the possibility of a page // boundary between IoCallDriver() and ClassReleaseQueue(), // which could leave the queue frozen as we try to page in // this code, which is required to unfreeze the queue. // The result would be a nice case of deadlock. // ASSERT(fdoExtension->CommonExtension.IsFdo); // // Write length to SRB. // Srb->Length = SCSI_REQUEST_BLOCK_SIZE; // // Set SCSI bus address. // Srb->Function = SRB_FUNCTION_EXECUTE_SCSI; // // NOTICE: The SCSI-II specification indicates that this field should be // zero; however, some target controllers ignore the logical unit number // in the INDENTIFY message and only look at the logical unit number field // in the CDB. // // Srb->Cdb[1] |= deviceExtension->Lun << 5; // // Enable auto request sense. // Srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE; // // Sense buffer is in aligned nonpaged pool. // senseInfoBuffer = ExAllocatePoolWithTag(NonPagedPoolCacheAligned, SENSE_BUFFER_SIZE, '7CcS'); if (senseInfoBuffer == NULL) { DebugPrint((1, "ClassSendSrbSynchronous: Can't allocate request sense " "buffer\n")); return(STATUS_INSUFFICIENT_RESOURCES); } Srb->SenseInfoBuffer = senseInfoBuffer; Srb->DataBuffer = BufferAddress; if(BufferAddress != NULL) { if(WriteToDevice) { Srb->SrbFlags = SRB_FLAGS_DATA_OUT; } else { Srb->SrbFlags = SRB_FLAGS_DATA_IN; } } else { Srb->SrbFlags = SRB_FLAGS_NO_DATA_TRANSFER; } // // Start retries here. // retry: // // Set the event object to the unsignaled state. // It will be used to signal request completion. // KeInitializeEvent(&event, NotificationEvent, FALSE); // // Build device I/O control request with METHOD_NEITHER data transfer. // We'll queue a completion routine to cleanup the MDL's and such ourself. // irp = IoAllocateIrp( (CCHAR) (fdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1), FALSE); if(irp == NULL) { ExFreePool(senseInfoBuffer); DebugPrint((1, "ClassSendSrbSynchronous: Can't allocate Irp\n")); return(STATUS_INSUFFICIENT_RESOURCES); } // // Get next stack location. // irpStack = IoGetNextIrpStackLocation(irp); // // Set up SRB for execute scsi request. Save SRB address in next stack // for the port driver. // irpStack->MajorFunction = IRP_MJ_SCSI; irpStack->Parameters.Scsi.Srb = Srb; IoSetCompletionRoutine(irp, ClasspSendSynchronousCompletion, Srb, TRUE, TRUE, TRUE); irp->UserIosb = &ioStatus; irp->UserEvent = &event; if(BufferAddress) { // // Build an MDL for the data buffer and stick it into the irp. The // completion routine will unlock the pages and free the MDL. // irp->MdlAddress = IoAllocateMdl( BufferAddress, BufferLength, FALSE, FALSE, irp ); if (irp->MdlAddress == NULL) { ExFreePool(senseInfoBuffer); Srb->SenseInfoBuffer = NULL; IoFreeIrp( irp ); DebugPrint((1, "ClassSendSrbSynchronous: Can't allocate MDL\n")); return STATUS_INSUFFICIENT_RESOURCES; } try { MmProbeAndLockPages( irp->MdlAddress, KernelMode, (WriteToDevice ? IoReadAccess : IoWriteAccess)); } except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); ExFreePool(senseInfoBuffer); Srb->SenseInfoBuffer = NULL; IoFreeMdl(irp->MdlAddress); IoFreeIrp(irp); DebugPrint((1, "ClassSendSrbSynchronous: Exception %lx " "locking buffer\n", status)); return status; } } // // Disable synchronous transfer for these requests. // SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER); // // Set the transfer length. // Srb->DataTransferLength = BufferLength; // // Zero out status. // Srb->ScsiStatus = Srb->SrbStatus = 0; Srb->NextSrb = 0; // // Set up IRP Address. // Srb->OriginalRequest = irp; // // Call the port driver with the request and wait for it to complete. // status = IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, NULL); status = ioStatus.Status; } // // Check that request completed without error. // if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) { ULONG retryInterval; // // Release the queue if it is frozen. // if (Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) { ClassReleaseQueue(Fdo); } // // Update status and determine if request should be retried. // retry = ClassInterpretSenseInfo(Fdo, Srb, IRP_MJ_SCSI, 0, MAXIMUM_RETRIES - retryCount, &status, &retryInterval); if (retry) { if ((status == STATUS_DEVICE_NOT_READY && ((PSENSE_DATA) senseInfoBuffer)->AdditionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) || (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)) { LARGE_INTEGER delay; // // Delay for at least 2 seconds. // if(retryInterval < 2) { retryInterval = 2; } delay.QuadPart = (LONGLONG)( - 10 * 1000 * (LONGLONG)1000 * retryInterval); // // Stall for a while to let the controller spinup. // KeDelayExecutionThread(KernelMode, FALSE, &delay); } // // If retries are not exhausted then retry this operation. // if (retryCount--) { goto retry; } } } else { status = STATUS_SUCCESS; } Srb->SenseInfoBuffer = NULL; ExFreePool(senseInfoBuffer); return status; } NTSTATUS ClasspSendSynchronousCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: This completion routine will set the user event in the irp after freeing the irp and the associated MDL (if any). Arguments: DeviceObject - the device object which requested the completion routine Irp - the irp being completed Context - unused Return Value: STATUS_MORE_PROCESSING_REQUIRED --*/ { DebugPrint((3, "ClasspSendSynchronousCompletion: %p %p %p\n", DeviceObject, Irp, Context)); // // First set the status and information fields in the io status block // provided by the caller. // *(Irp->UserIosb) = Irp->IoStatus; // // Unlock the pages for the data buffer. // if(Irp->MdlAddress) { MmUnlockPages(Irp->MdlAddress); IoFreeMdl(Irp->MdlAddress); } // // Signal the caller's event. // KeSetEvent(Irp->UserEvent, IO_NO_INCREMENT, FALSE); // // Free the MDL and the IRP. // IoFreeIrp(Irp); return STATUS_MORE_PROCESSING_REQUIRED; } PVOID ChangerClassAllocatePool( IN POOL_TYPE PoolType, IN ULONG NumberOfBytes ) { return ExAllocatePoolWithTag(PoolType, NumberOfBytes, 'CMcS'); } VOID ChangerClassFreePool( IN PVOID PoolToFree ) { ExFreePool(PoolToFree); } #if DBG ULONG MCDebug = 0; UCHAR DebugBuffer[128]; #endif #if DBG VOID MCDebugPrint( ULONG DebugPrintLevel, PCCHAR DebugMessage, ... ) /*++ Routine Description: Debug print for all medium changer drivers Arguments: Debug print level between 0 and 3, with 3 being the most verbose. Return Value: None --*/ { va_list ap; va_start(ap, DebugMessage); if (DebugPrintLevel <= MCDebug) { vsprintf(DebugBuffer, DebugMessage, ap); DbgPrint(DebugBuffer); } va_end(ap); } // end MCDebugPrint() #else // // DebugPrint stub // VOID MCDebugPrint( ULONG DebugPrintLevel, PCCHAR DebugMessage, ... ) { } #endif