windows-nt/Source/XPSP1/NT/drivers/storage/scsiport/enum.c

4557 lines
124 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (C) Microsoft Corporation, 1996 - 1999
Module Name:
enum.c
Abstract:
This module contains device enumeration code for the scsi port driver
Authors:
Peter Wieland
Environment:
Kernel mode only
Notes:
Revision History:
--*/
#include "port.h"
#define __FILE_ID__ 'enum'
ULONG EnumDebug = 2;
#if DBG
static const char *__file__ = __FILE__;
#endif
#define MINIMUM_BUS_SCAN_INTERVAL ((ULONGLONG) (30 * SECONDS))
ULONG BreakOnTarget = (ULONG) -1;
ULONG BreakOnScan = TRUE;
ULONG BreakOnMissingLun = FALSE;
typedef struct {
UCHAR LunListLength[4]; // sizeof LunSize * 8
UCHAR Reserved[4];
UCHAR Luns[16][8];
} SP_DEFAULT_LUN_LIST;
SP_DEFAULT_LUN_LIST ScsiPortDefaultLunList = {
{0, 0, 0, sizeof(ScsiPortDefaultLunList.Luns)}, // LunListLength
{0, 0, 0, 0}, // Reserved
{{ 0, 0, 0, 0, 0, 0, 0, 0}, // Luns
{ 0, 1, 0, 0, 0, 0, 0, 0},
{ 0, 2, 0, 0, 0, 0, 0, 0},
{ 0, 3, 0, 0, 0, 0, 0, 0},
{ 0, 4, 0, 0, 0, 0, 0, 0},
{ 0, 5, 0, 0, 0, 0, 0, 0},
{ 0, 6, 0, 0, 0, 0, 0, 0},
{ 0, 7, 0, 0, 0, 0, 0, 0}}};
NTSTATUS
SpInquireLogicalUnit(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId,
IN UCHAR Lun,
IN BOOLEAN ExposeDisconnectedLuns,
IN OPTIONAL PLOGICAL_UNIT_EXTENSION RescanLun,
OUT PLOGICAL_UNIT_EXTENSION *LogicalUnit,
OUT PBOOLEAN CheckNextLun
);
VOID
SpSignalEnumerationCompletion (
IN PADAPTER_EXTENSION Adapter,
IN PSP_ENUMERATION_REQUEST Request,
IN NTSTATUS Status
);
BOOLEAN
SpRemoveLogicalUnitFromBinSynchronized(
IN PVOID ServiceContext // PLOGICAL_UNIT_EXTENSION
);
BOOLEAN
SpAddLogicalUnitToBinSynchronized(
IN PLOGICAL_UNIT_EXTENSION LogicalUnitExtension
);
ULONG
SpCountLogicalUnits(
IN PADAPTER_EXTENSION Adapter
);
NTSTATUS
IssueReportLuns(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
OUT PLUN_LIST *LunList
);
PLUN_LIST
AdjustReportLuns(
IN PDRIVER_OBJECT DriverObject,
IN PLUN_LIST RawList
);
VOID
SpScanAdapter(
IN PADAPTER_EXTENSION Adapter
);
NTSTATUS
SpScanBus(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN BOOLEAN ExposeDisconnectedLuns,
IN PLOGICAL_UNIT_EXTENSION RescanLun
);
NTSTATUS
SpScanTarget(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId,
IN BOOLEAN ExposeDisconnectedLuns,
IN PLOGICAL_UNIT_EXTENSION RescanLun
);
NTSTATUS
IssueInquiry(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN BOOLEAN EnableVitalProductData,
IN UCHAR PageCode,
OUT PVOID InquiryData,
OUT PUCHAR BytesReturned
);
VOID
SpSetVerificationMarks(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId
);
VOID
SpClearVerificationMark(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit
);
VOID
SpPurgeTarget(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId
);
NTSTATUS
SpCloneAndSwapLogicalUnit(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN PINQUIRYDATA InquiryData,
IN ULONG InquiryDataSize,
OUT PLOGICAL_UNIT_EXTENSION *NewLun
);
VOID
SpSetLogicalUnitAddress(
IN PLOGICAL_UNIT_EXTENSION RescanLun,
IN UCHAR PathId,
IN UCHAR TargetId,
IN UCHAR Lun
);
VOID
SpClearLogicalUnitAddress(
IN PLOGICAL_UNIT_EXTENSION RescanLun
);
NTSTATUS
SpPrepareLogicalUnitForReuse(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit
);
NTSTATUS
SpCreateLogicalUnit(
IN PADAPTER_EXTENSION Adapter,
IN OPTIONAL UCHAR PathId,
IN OPTIONAL UCHAR TargetId,
IN OPTIONAL UCHAR Lun,
IN BOOLEAN Temporary,
IN BOOLEAN Scsi1,
OUT PLOGICAL_UNIT_EXTENSION *NewLun
);
NTSTATUS
SpSendSrbSynchronous(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN PSCSI_REQUEST_BLOCK Srb,
IN OPTIONAL PIRP Irp,
IN OPTIONAL PMDL Mdl,
IN PVOID DataBuffer,
IN ULONG TransferLength,
IN PVOID SenseInfoBuffer,
IN UCHAR SenseInfoBufferLength,
OUT PULONG BytesReturned
);
BOOLEAN
SpGetDeviceIdentifiers(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN BOOLEAN NewDevice
);
BOOLEAN
FASTCALL
SpCompareInquiryData(
IN PUCHAR InquiryData1,
IN PUCHAR InquiryData2
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, SpEnumerateAdapterSynchronous)
#pragma alloc_text(PAGE, SpEnumerateAdapterAsynchronous)
#pragma alloc_text(PAGE, SpSignalEnumerationCompletion)
#pragma alloc_text(PAGE, SpEnumerationWorker)
#pragma alloc_text(PAGE, SpScanAdapter)
#pragma alloc_text(PAGE, SpScanBus)
#pragma alloc_text(PAGE, SpScanTarget)
#pragma alloc_text(PAGE, SpCompareInquiryData)
#pragma alloc_text(PAGE, SpInquireLogicalUnit)
#pragma alloc_text(PAGE, SpExtractDeviceRelations)
#pragma alloc_text(PAGELOCK, SpCountLogicalUnits)
#pragma alloc_text(PAGELOCK, GetNextLuRequestWithoutLock)
#pragma alloc_text(PAGELOCK, IssueReportLuns)
#pragma alloc_text(PAGELOCK, SpSetVerificationMarks)
#pragma alloc_text(PAGELOCK, SpPurgeTarget)
#pragma alloc_text(PAGE, SpClearVerificationMark)
#pragma alloc_text(PAGE, SpGetInquiryData)
#pragma alloc_text(PAGE, IssueInquiry)
#pragma alloc_text(PAGE, AdjustReportLuns)
#pragma alloc_text(PAGE, SpCreateLogicalUnit)
#pragma alloc_text(PAGE, SpCloneAndSwapLogicalUnit)
#pragma alloc_text(PAGE, SpSetLogicalUnitAddress)
#pragma alloc_text(PAGE, SpClearLogicalUnitAddress)
#pragma alloc_text(PAGE, SpPrepareLogicalUnitForReuse)
#pragma alloc_text(PAGE, SpSendSrbSynchronous)
#pragma alloc_text(PAGE, SpGetDeviceIdentifiers)
LONG SpPAGELOCKLockCount = 0;
#endif
NTSTATUS
SpExtractDeviceRelations(
PADAPTER_EXTENSION Adapter,
DEVICE_RELATION_TYPE RelationType,
PDEVICE_RELATIONS *DeviceRelations
)
/*++
Routine Description:
This routine will allocate a device relations structure and fill in the
count and object array with referenced object pointers
Arguments:
Adapter - the adapter to extract relations from.
RelationType - what type of relationship is being retrieved
DeviceRelations - a place to store the relationships
--*/
{
PDEVICE_OBJECT fdo = Adapter->DeviceObject;
ULONG count = 0;
ULONG relationsSize;
PDEVICE_RELATIONS deviceRelations = NULL;
UCHAR bus, target, lun;
PLOGICAL_UNIT_EXTENSION luExtension;
ULONG i;
NTSTATUS status;
PAGED_CODE();
status = KeWaitForMutexObject(
&(Adapter->EnumerationDeviceMutex),
Executive,
KernelMode,
FALSE,
NULL);
if(status == STATUS_USER_APC) {
status = STATUS_REQUEST_ABORTED;
}
if(!NT_SUCCESS(status)) {
return status;
}
//
// Find out how many devices there are
//
for(bus = 0; bus < Adapter->NumberOfBuses; bus++) {
for(target = 0; target < Adapter->MaximumTargetIds; target++) {
for(lun = 0; lun < SCSI_MAXIMUM_LUNS_PER_TARGET; lun++) {
luExtension = GetLogicalUnitExtension(
Adapter,
bus,
target,
lun,
FALSE,
TRUE);
if(luExtension == NULL) {
continue;
}
//
// Temporary luns only exist while the bus scanning code is
// holding the device lock. we've got it now so we should
// never find one.
//
ASSERT(luExtension->IsTemporary == FALSE);
if(luExtension->IsMissing) {
continue;
}
if(luExtension->IsVisible == FALSE) {
continue;
}
if(luExtension->CommonExtension.IsRemoved >= REMOVE_COMPLETE) {
ASSERT(FALSE);
continue;
}
count++;
}
}
}
//
// Allocate the structure
//
relationsSize = sizeof(DEVICE_RELATIONS) + (count * sizeof(PDEVICE_OBJECT));
deviceRelations = SpAllocatePool(PagedPool,
relationsSize,
SCSIPORT_TAG_DEVICE_RELATIONS,
fdo->DriverObject);
if(deviceRelations == NULL) {
DebugPrint((1, "SpExtractDeviceRelations: unable to allocate "
"%d bytes for device relations\n", relationsSize));
KeReleaseMutex(&(Adapter->EnumerationDeviceMutex), FALSE);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(deviceRelations, relationsSize);
i = 0;
for(bus = 0; bus < Adapter->NumberOfBuses; bus++) {
for(target = 0; target < Adapter->MaximumTargetIds; target++) {
for(lun = 0; lun < SCSI_MAXIMUM_LUNS_PER_TARGET; lun++) {
luExtension = GetLogicalUnitExtension(
Adapter,
bus,
target,
lun,
FALSE,
TRUE);
if(luExtension == NULL) {
continue;
}
//
// Temporary luns only exist while the bus scanning code is
// holding the device lock. we've got it now so we should
// never find one.
//
ASSERT(luExtension->IsTemporary == FALSE);
if(luExtension->IsMissing) {
DebugPrint((1, "SpExtractDeviceRelations: logical unit "
"(%d,%d,%d) is missing and will not be "
"returned\n",
bus, target, lun));
luExtension->IsEnumerated = FALSE;
continue;
} else if(luExtension->CommonExtension.IsRemoved >= REMOVE_COMPLETE) {
ASSERT(FALSE);
luExtension->IsEnumerated = FALSE;
continue;
} else if(luExtension->IsVisible == FALSE) {
luExtension->IsEnumerated = FALSE;
continue;
}
status = ObReferenceObjectByPointer(
luExtension->CommonExtension.DeviceObject,
0,
NULL,
KernelMode);
if(!NT_SUCCESS(status)) {
DebugPrint((1, "SpFdoExtractDeviceRelations: status %#08lx "
"while referenceing object %#p\n",
status,
deviceRelations->Objects[i]));
continue;
}
deviceRelations->Objects[i] =
luExtension->CommonExtension.DeviceObject;
i++;
luExtension->IsEnumerated = TRUE;
}
}
}
deviceRelations->Count = i;
*DeviceRelations = deviceRelations;
KeReleaseMutex(&(Adapter->EnumerationDeviceMutex), FALSE);
return STATUS_SUCCESS;
}
NTSTATUS
IssueReportLuns(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
OUT PLUN_LIST *LunList
)
/*++
Routine Description:
Build IRP, SRB and CDB for SCSI REPORT LUNS command.
Arguments:
LogicalUnit - address of target's device object extension.
LunList - address of buffer for LUN_LIST information.
Return Value:
NTSTATUS
--*/
{
PMDL mdl;
PIRP irp;
PIO_STACK_LOCATION irpStack;
SCSI_REQUEST_BLOCK srb;
PCDB cdb;
KEVENT event;
KIRQL currentIrql;
PLUN_LIST lunListDataBuffer;
PSENSE_DATA senseInfoBuffer = NULL;
NTSTATUS status;
ULONG retryCount = 0;
ULONG lunListSize;
ULONG i;
PAGED_CODE();
#if 0
if ((LogicalUnit->InquiryData.Versions & 7) < 3) {
//
// make sure the device supports scsi3 commands
// without this check, we may hang some scsi2 devices
//
return STATUS_INVALID_DEVICE_REQUEST;
}
#endif
//
// start with the minilun of 16 byte for the lun list
//
lunListSize = 16;
status = STATUS_INVALID_DEVICE_REQUEST;
senseInfoBuffer = LogicalUnit->AdapterExtension->InquirySenseBuffer;
irp = LogicalUnit->AdapterExtension->InquiryIrp;
mdl = NULL;
KeInitializeEvent(&event,
SynchronizationEvent,
FALSE);
//
// This is a two pass operation - for the first pass we just try to figure
// out how large the list should be. On the second pass we'll actually
// reallocate the buffer and try to get the entire lun list.
//
// NOTE - we may want to set an arbitrary limit here so we don't soak up all
// of non-paged pool when some device hands us back a buffer filled
// with 0xff.
//
for (i=0; i<2; i++) {
//
// Allocate a cache aligned LUN_LIST structure.
//
lunListDataBuffer = SpAllocatePool(
NonPagedPoolCacheAligned,
lunListSize,
SCSIPORT_TAG_REPORT_LUNS,
LogicalUnit->DeviceObject->DriverObject);
if (lunListDataBuffer == NULL) {
DebugPrint((1,"IssueReportLuns: Can't allocate report luns data buffer\n"));
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
mdl = SpAllocateMdl(lunListDataBuffer,
lunListSize,
FALSE,
FALSE,
NULL,
LogicalUnit->DeviceObject->DriverObject);
if(mdl == NULL) {
DebugPrint((1,"IssueReportLuns: Can't allocate data buffer MDL\n"));
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
MmBuildMdlForNonPagedPool(mdl);
//
// number of retry
//
retryCount = 3;
while (retryCount--) {
//
// Build IRP for this request.
//
IoInitializeIrp(irp,
IoSizeOfIrp(INQUIRY_STACK_LOCATIONS),
INQUIRY_STACK_LOCATIONS);
irp->MdlAddress = mdl;
irpStack = IoGetNextIrpStackLocation(irp);
//
// Fill in SRB fields.
//
RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK));
//
// Mark the minor function to indicate that this is an internal scsiport
// request and that the start state of the device can be ignored.
//
irpStack->MajorFunction = IRP_MJ_SCSI;
irpStack->MinorFunction = 1;
irpStack->Parameters.Scsi.Srb = &srb;
IoSetCompletionRoutine(irp,
SpSignalCompletion,
&event,
TRUE,
TRUE,
TRUE);
srb.PathId = LogicalUnit->PathId;
srb.TargetId = LogicalUnit->TargetId;
srb.Lun = LogicalUnit->Lun;
srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
srb.Length = sizeof(SCSI_REQUEST_BLOCK);
//
// Set flags to disable synchronous negociation.
//
srb.SrbFlags = SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER;
srb.SrbStatus = srb.ScsiStatus = 0;
srb.NextSrb = 0;
srb.OriginalRequest = irp;
//
// Set timeout to 2 seconds.
//
srb.TimeOutValue = 4;
srb.CdbLength = 12;
//
// Enable auto request sense.
//
srb.SenseInfoBuffer = senseInfoBuffer;
srb.SenseInfoBufferLength = SENSE_BUFFER_SIZE;
srb.DataBuffer = MmGetMdlVirtualAddress(irp->MdlAddress);
srb.DataTransferLength = lunListSize;
cdb = (PCDB)srb.Cdb;
//
// Set CDB operation code.
//
cdb->REPORT_LUNS.OperationCode = SCSIOP_REPORT_LUNS;
cdb->REPORT_LUNS.AllocationLength[0] = (UCHAR) ((lunListSize >> 24) & 0xff);
cdb->REPORT_LUNS.AllocationLength[1] = (UCHAR) ((lunListSize >> 16) & 0xff);
cdb->REPORT_LUNS.AllocationLength[2] = (UCHAR) ((lunListSize >> 8) & 0xff);
cdb->REPORT_LUNS.AllocationLength[3] = (UCHAR) ((lunListSize >> 0) & 0xff);
//
// Call port driver to handle this request.
//
status = IoCallDriver(LogicalUnit->DeviceObject, irp);
//
// Wait for request to complete.
//
KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
NULL);
status = irp->IoStatus.Status;
if (SRB_STATUS(srb.SrbStatus) != SRB_STATUS_SUCCESS) {
DebugPrint((2,"IssueReportLuns: failed SRB status %x\n",
srb.SrbStatus));
//
// Unfreeze queue if necessary
//
if (srb.SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
DebugPrint((3, "IssueInquiry: Unfreeze Queue TID %d\n",
srb.TargetId));
LogicalUnit->LuFlags &= ~LU_QUEUE_FROZEN;
KeAcquireSpinLock(
&(LogicalUnit->AdapterExtension->SpinLock),
&currentIrql);
GetNextLuRequest(LogicalUnit);
KeLowerIrql(currentIrql);
}
if ((srb.SrbStatus & SRB_STATUS_AUTOSENSE_VALID) &&
senseInfoBuffer->SenseKey == SCSI_SENSE_ILLEGAL_REQUEST){
//
// A sense key of illegal request was recieved. This indicates
// that the logical unit number of not valid but there is a
// target device out there.
//
status = STATUS_INVALID_DEVICE_REQUEST;
break;
} else if ((SRB_STATUS(srb.SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT) ||
(SRB_STATUS(srb.SrbStatus) == SRB_STATUS_NO_DEVICE)) {
//
// If the selection times out then give up
//
status = STATUS_NO_SUCH_DEVICE;
break;
}
//
// retry...
//
} else {
status = STATUS_SUCCESS;
break;
}
}
IoFreeMdl(mdl);
if (NT_SUCCESS(status)) {
ULONG listLength;
listLength = lunListDataBuffer->LunListLength[3] << 0;
listLength |= lunListDataBuffer->LunListLength[2] << 8;
listLength |= lunListDataBuffer->LunListLength[1] << 16;
listLength |= lunListDataBuffer->LunListLength[0] << 24;
if (lunListSize < (listLength + sizeof (LUN_LIST))) {
lunListSize = listLength + sizeof (LUN_LIST);
//
// try report lun with a bigger buffer
//
ExFreePool(lunListDataBuffer);
lunListDataBuffer = NULL;
status = STATUS_INVALID_DEVICE_REQUEST;
} else {
//
// lun list is good
//
break;
}
}
}
//
// Return the lun list
//
if(NT_SUCCESS(status)) {
//
// AdjustReportLuns returns lunListDataBuffer if it cannot allocate
// a new list.
//
*LunList = AdjustReportLuns(LogicalUnit->DeviceObject->DriverObject,
lunListDataBuffer);
//
// Only delete lunListDataBuffer if we didn't return it from
// AdjustReportLuns.
//
ASSERT(*LunList != NULL);
ASSERT(lunListDataBuffer != NULL);
if (*LunList != lunListDataBuffer) {
ExFreePool(lunListDataBuffer);
}
} else {
*LunList = NULL;
if (lunListDataBuffer) {
ExFreePool(lunListDataBuffer);
}
}
return status;
} // end IssueReportLuns()
VOID
GetNextLuRequestWithoutLock(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit
)
{
KIRQL oldIrql;
PAGED_CODE();
ASSERT(SpPAGELOCKLockCount != 0);
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
KeAcquireSpinLockAtDpcLevel(&(LogicalUnit->AdapterExtension->SpinLock));
GetNextLuRequest(LogicalUnit);
KeLowerIrql(oldIrql);
PAGED_CODE();
return;
}
ULONG
SpCountLogicalUnits(
IN PADAPTER_EXTENSION Adapter
)
{
ULONG numberOfLus = 0;
PLOGICAL_UNIT_EXTENSION luExtension;
KIRQL oldIrql;
ULONG bin;
#ifdef ALLOC_PRAGMA
PVOID sectionHandle;
#endif
//
// Code is paged until locked down.
//
PAGED_CODE();
//
// Lock this routine down before grabbing the spinlock.
//
#ifdef ALLOC_PRAGMA
sectionHandle = MmLockPagableCodeSection(SpCountLogicalUnits);
#endif
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
for(bin = 0; bin < NUMBER_LOGICAL_UNIT_BINS; bin++) {
KeAcquireSpinLockAtDpcLevel(&(Adapter->LogicalUnitList[bin].Lock));
for(luExtension = Adapter->LogicalUnitList[bin].List;
luExtension != NULL;
luExtension = luExtension->NextLogicalUnit) {
if(luExtension->IsMissing == FALSE) {
numberOfLus++;
}
}
KeReleaseSpinLockFromDpcLevel(&(Adapter->LogicalUnitList[bin].Lock));
}
KeLowerIrql(oldIrql);
#ifdef ALLOC_PRAGMA
MmUnlockPagableImageSection(sectionHandle);
#endif
return numberOfLus;
}
NTSTATUS
SpGetInquiryData(
IN PADAPTER_EXTENSION DeviceExtension,
IN PIRP Irp
)
/*++
Routine Description:
This functions copies the inquiry data to the system buffer. The data
is translate from the port driver's internal format to the user mode
format.
Arguments:
DeviceExtension - Supplies a pointer the SCSI adapter device extension.
Irp - Supplies a pointer to the Irp which made the original request.
Return Value:
Returns a status indicating the success or failure of the operation.
--*/
{
PUCHAR bufferStart;
PIO_STACK_LOCATION irpStack;
UCHAR bin;
PLOGICAL_UNIT_EXTENSION luExtension;
PSCSI_ADAPTER_BUS_INFO adapterInfo;
PSCSI_INQUIRY_DATA inquiryData;
ULONG inquiryDataSize;
ULONG length;
PLOGICAL_UNIT_INFO lunInfo;
ULONG numberOfBuses;
ULONG numberOfLus;
ULONG j;
UCHAR pathId;
UCHAR targetId;
UCHAR lun;
NTSTATUS status;
PAGED_CODE();
ASSERT_FDO(DeviceExtension->CommonExtension.DeviceObject);
status = KeWaitForMutexObject(&(DeviceExtension->EnumerationDeviceMutex),
UserRequest,
UserMode,
FALSE,
NULL);
if(status == STATUS_USER_APC) {
status = STATUS_REQUEST_ABORTED;
}
if(!NT_SUCCESS(status)) {
Irp->IoStatus.Status = status;
return status;
}
DebugPrint((3,"SpGetInquiryData: Enter routine\n"));
//
// Get a pointer to the control block.
//
irpStack = IoGetCurrentIrpStackLocation(Irp);
bufferStart = Irp->AssociatedIrp.SystemBuffer;
//
// Determine the number of SCSI buses and logical units.
//
numberOfBuses = DeviceExtension->NumberOfBuses;
numberOfLus = 0;
numberOfLus = SpCountLogicalUnits(DeviceExtension);
//
// Caculate the size of the logical unit structure and round it to a word
// alignment.
//
inquiryDataSize = ((sizeof(SCSI_INQUIRY_DATA) - 1 + INQUIRYDATABUFFERSIZE +
sizeof(ULONG) - 1) & ~(sizeof(ULONG) - 1));
// Based on the number of buses and logical unit, determine the minimum
// buffer length to hold all of the data.
//
length = sizeof(SCSI_ADAPTER_BUS_INFO) +
(numberOfBuses - 1) * sizeof(SCSI_BUS_DATA);
length += inquiryDataSize * numberOfLus;
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < length) {
Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
KeReleaseMutex(&(DeviceExtension->EnumerationDeviceMutex), FALSE);
return(STATUS_BUFFER_TOO_SMALL);
}
//
// Set the information field.
//
Irp->IoStatus.Information = length;
//
// Fill in the bus information.
//
adapterInfo = (PSCSI_ADAPTER_BUS_INFO) bufferStart;
adapterInfo->NumberOfBuses = (UCHAR) numberOfBuses;
inquiryData = (PSCSI_INQUIRY_DATA)(bufferStart +
sizeof(SCSI_ADAPTER_BUS_INFO) +
((numberOfBuses - 1) *
sizeof(SCSI_BUS_DATA)));
for (pathId = 0; pathId < numberOfBuses; pathId++) {
PSCSI_BUS_DATA busData;
busData = &adapterInfo->BusData[pathId];
busData->InitiatorBusId = DeviceExtension->PortConfig->InitiatorBusId[pathId];
busData->NumberOfLogicalUnits = 0;
busData->InquiryDataOffset = (ULONG)((PUCHAR) inquiryData - bufferStart);
for(targetId = 0;
targetId < DeviceExtension->MaximumTargetIds;
targetId++) {
for(lun = 0;
lun < SCSI_MAXIMUM_LUNS_PER_TARGET;
lun++) {
PLOGICAL_UNIT_EXTENSION luExtension;
luExtension = GetLogicalUnitExtension(DeviceExtension,
pathId,
targetId,
lun,
Irp,
TRUE);
if(luExtension == NULL) {
continue;
}
if((luExtension->IsMissing) ||
(luExtension->CommonExtension.IsRemoved)) {
SpReleaseRemoveLock(
luExtension->CommonExtension.DeviceObject,
Irp);
continue;
}
busData->NumberOfLogicalUnits++;
DebugPrint((1, "InquiryData for (%d, %d, %d) - ",
pathId,
targetId,
lun));
DebugPrint((1, "%d units found\n", busData->NumberOfLogicalUnits));
inquiryData->PathId = pathId;
inquiryData->TargetId = targetId;
inquiryData->Lun = lun;
inquiryData->DeviceClaimed = luExtension->IsClaimed;
inquiryData->InquiryDataLength = INQUIRYDATABUFFERSIZE;
inquiryData->NextInquiryDataOffset = (ULONG)((PUCHAR) inquiryData + inquiryDataSize - bufferStart);
RtlCopyMemory(inquiryData->InquiryData,
&(luExtension->InquiryData),
INQUIRYDATABUFFERSIZE);
inquiryData = (PSCSI_INQUIRY_DATA) ((PUCHAR) inquiryData + inquiryDataSize);
SpReleaseRemoveLock(luExtension->CommonExtension.DeviceObject,
Irp);
}
}
if(busData->NumberOfLogicalUnits == 0) {
busData->InquiryDataOffset = 0;
} else {
((PSCSI_INQUIRY_DATA) ((PCHAR) inquiryData - inquiryDataSize))->NextInquiryDataOffset = 0;
}
}
Irp->IoStatus.Status = STATUS_SUCCESS;
KeReleaseMutex(&(DeviceExtension->EnumerationDeviceMutex), FALSE);
return(STATUS_SUCCESS);
}
VOID
SpAddLogicalUnitToBin (
IN PADAPTER_EXTENSION AdapterExtension,
IN PLOGICAL_UNIT_EXTENSION LogicalUnitExtension
)
/*++
Routine Description:
This routine will synchronize with any interrupt or miniport routines and
add the specified logical unit to the appropriate logical unit list.
The logical unit must not already be in the list.
This routine acquires the bin spinlock and calls the SynchronizeExecution
routine. It cannot be called when the bin spinlock is held or from a
miniport API.
Arguments:
AdapterExtension - the adapter to add this logical unit to.
LogicalUnitExtension - the logical unit to be added.
Return Value:
none
--*/
{
UCHAR hash = ADDRESS_TO_HASH(LogicalUnitExtension->PathId,
LogicalUnitExtension->TargetId,
LogicalUnitExtension->Lun);
PLOGICAL_UNIT_BIN bin = &AdapterExtension->LogicalUnitList[hash];
PLOGICAL_UNIT_EXTENSION lun;
KIRQL oldIrql;
KeAcquireSpinLock(&AdapterExtension->SpinLock, &oldIrql);
KeAcquireSpinLockAtDpcLevel(&bin->Lock);
//
// Run through the list quickly and make sure this lun isn't already there
//
lun = bin->List;
while(lun != NULL) {
if(lun == LogicalUnitExtension) {
break;
}
lun = lun->NextLogicalUnit;
}
ASSERTMSG("Logical Unit already in list: ", lun == NULL);
ASSERTMSG("Logical Unit not properly initialized: ",
(LogicalUnitExtension->AdapterExtension == AdapterExtension));
ASSERTMSG("Logical Unit is already on a list: ",
LogicalUnitExtension->NextLogicalUnit == NULL);
LogicalUnitExtension->NextLogicalUnit = bin->List;
bin->List = LogicalUnitExtension;
KeReleaseSpinLockFromDpcLevel(&bin->Lock);
KeReleaseSpinLock(&AdapterExtension->SpinLock, oldIrql);
return;
}
VOID
SpRemoveLogicalUnitFromBin (
IN PADAPTER_EXTENSION AdapterExtension,
IN PLOGICAL_UNIT_EXTENSION LogicalUnitExtension
)
/*++
Routine Description:
This routine will synchronize with any interrupt or miniport routines and
remove the specified logical unit from the appropriate logical unit list.
The logical unit MUST be in the logical unit list.
This routine acquires the bin spinlock and calls the SynchronizeExecution
routine. It cannot be called when the bin spinlock is held or from
a miniport exported routine.
Arguments:
AdapterExtension - The adapter from which to remove this logical unit
LogicalUnitExtension - the logical unit to be removed
Return Value:
none
--*/
{
KIRQL oldIrql;
PLOGICAL_UNIT_BIN bin =
&AdapterExtension->LogicalUnitList[ADDRESS_TO_HASH(
LogicalUnitExtension->PathId,
LogicalUnitExtension->TargetId,
LogicalUnitExtension->Lun)];
KeAcquireSpinLock(&AdapterExtension->SpinLock, &oldIrql);
KeAcquireSpinLockAtDpcLevel(&bin->Lock);
AdapterExtension->SynchronizeExecution(
AdapterExtension->InterruptObject,
SpRemoveLogicalUnitFromBinSynchronized,
LogicalUnitExtension
);
KeReleaseSpinLockFromDpcLevel(&bin->Lock);
KeReleaseSpinLock(&AdapterExtension->SpinLock, oldIrql);
if(LogicalUnitExtension->IsMismatched) {
DebugPrint((1, "SpRemoveLogicalUnitFromBin: Signalling for rescan "
"after removal of mismatched lun %#p\n",
LogicalUnitExtension));
IoInvalidateDeviceRelations(AdapterExtension->LowerPdo,
BusRelations);
}
}
BOOLEAN
SpRemoveLogicalUnitFromBinSynchronized(
IN PVOID ServiceContext
)
{
PLOGICAL_UNIT_EXTENSION logicalUnitExtension =
(PLOGICAL_UNIT_EXTENSION) ServiceContext;
PADAPTER_EXTENSION adapterExtension =
logicalUnitExtension->AdapterExtension;
UCHAR hash = ADDRESS_TO_HASH(
logicalUnitExtension->PathId,
logicalUnitExtension->TargetId,
logicalUnitExtension->Lun);
PLOGICAL_UNIT_BIN bin;
PLOGICAL_UNIT_EXTENSION *lun;
ASSERT(hash < NUMBER_LOGICAL_UNIT_BINS);
adapterExtension->CachedLogicalUnit = NULL;
bin = &adapterExtension->LogicalUnitList[hash];
lun = &bin->List;
while(*lun != NULL) {
if(*lun == logicalUnitExtension) {
//
// Found a match - unlink it from the list.
//
*lun = logicalUnitExtension->NextLogicalUnit;
logicalUnitExtension->NextLogicalUnit = NULL;
return TRUE;
}
lun = &((*lun)->NextLogicalUnit);
}
return TRUE;
}
PLUN_LIST
AdjustReportLuns(
IN PDRIVER_OBJECT DriverObject,
IN PLUN_LIST RawList
)
{
ULONG newLength;
ULONG numberOfEntries;
ULONG maxLun = 8;
PLUN_LIST newList;
//
// Derive the length of the list and the number of entries currently in
// the list.
//
newLength = RawList->LunListLength[3] << 0;
newLength |= RawList->LunListLength[2] << 8;
newLength |= RawList->LunListLength[1] << 16;
newLength |= RawList->LunListLength[0] << 24;
numberOfEntries = newLength / sizeof (RawList->Lun[0]);
newLength += sizeof(LUN_LIST);
newLength += maxLun * sizeof(RawList->Lun[0]);
//
// Allocate a list with "maxLun" extra entries in it. This might waste
// some space if we have duplicates but it's easy.
//
//
// ALLOCATION
//
newList = SpAllocatePool(NonPagedPool,
newLength,
SCSIPORT_TAG_REPORT_LUNS,
DriverObject);
if(newList == NULL){
newList = RawList;
} else {
UCHAR lunNumber;
ULONG entry;
ULONG newEntryCount = 0;
RtlZeroMemory(newList, newLength);
//
// First make a fake entry for each of the luns from 0 to maxLun - 1
//
for(lunNumber = 0; lunNumber < maxLun; lunNumber++) {
newList->Lun[lunNumber][1] = lunNumber;
newEntryCount++;
};
//
// Now iterate through the entries in the remaining list. For each
// one copy it over iff it's not already a lun 0 -> (maxLun - 1)
//
for(entry = 0; entry < numberOfEntries; entry++) {
USHORT l;
l = (RawList->Lun[entry][0] << 8);
l |= RawList->Lun[entry][1];
l &= 0x3fff;
if(l >= maxLun) {
RtlCopyMemory(&(newList->Lun[lunNumber]),
&(RawList->Lun[entry]),
sizeof(newList->Lun[0]));
lunNumber++;
newEntryCount++;
}
}
//
// Copy over the reserved bytes for the cases where they aren't all
// that reserved.
//
RtlCopyMemory(newList->Reserved,
RawList->Reserved,
sizeof(RawList->Reserved));
//
// Subtract out the number of duplicate entries we found.
//
newLength = newEntryCount * sizeof(RawList->Lun[0]);
newList->LunListLength[0] = (UCHAR) ((newLength >> 24) & 0xff);
newList->LunListLength[1] = (UCHAR) ((newLength >> 16) & 0xff);
newList->LunListLength[2] = (UCHAR) ((newLength >> 8) & 0xff);
newList->LunListLength[3] = (UCHAR) ((newLength >> 0) & 0xff);
}
return newList;
}
VOID
SpCompleteEnumRequest(
IN PADAPTER_EXTENSION Adapter,
IN PIRP Irp
)
/*++
Routine Description:
This routine completes our handling of an asynchronous bus scan. If the
supplied IRP has been completed successfully, we pass it down to the
driver below. If the IRP was failed, we complete the request here.
Arguments:
Adapter - The adapter we're scanning.
Irp - The IRP that prompted this asynchronous bus scan true then a
scan will be done even if one has happend within the minimum
bus scan delta time.
Return Value:
none.
--*/
{
ULONG tempLock;
//
// Acquire a temporary remove lock so we can release the lock acquired
// on behalf of the IRP.
//
SpAcquireRemoveLock(Adapter->DeviceObject, &tempLock);
//
// Release the IRP's remove lock because holding it across completion
// could trip up our remove tracking code since it is based on the
// IRP address which can be recycled.
//
SpReleaseRemoveLock(Adapter->DeviceObject, Irp);
//
// Call down or complete the IRP, depending on the request's completion
// status.
//
if (NT_SUCCESS(Irp->IoStatus.Status)) {
IoCopyCurrentIrpStackLocationToNext(Irp);
IoCallDriver(Adapter->CommonExtension.LowerDeviceObject, Irp);
} else {
SpCompleteRequest(Adapter->DeviceObject,
Irp,
NULL,
IO_NO_INCREMENT);
}
//
// Release the temporary lock.
//
SpReleaseRemoveLock(Adapter->DeviceObject, &tempLock);
}
NTSTATUS
SpEnumerateAdapterSynchronous(
IN PADAPTER_EXTENSION Adapter,
IN BOOLEAN Force
)
/*++
Routine Description:
This routine will call SpEnumerateAdapterAsynchronous and wait for it to
complete.
Arguments:
Adapter - the adapter we're scanning.
Force - if true then a scan will be done even if one has happend within
the minimum bus scan delta time.
Return Value:
none.
--*/
{
SP_ENUMERATION_REQUEST request;
KEVENT event;
NTSTATUS status;
RtlZeroMemory(&request, sizeof(SP_ENUMERATION_REQUEST));
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
request.CompletionRoutine = SpSignalEnumerationCompletion;
request.Context = &event;
request.CompletionStatus = &status;
request.Synchronous = TRUE;
SpEnumerateAdapterAsynchronous(Adapter, &request, Force);
KeWaitForSingleObject(&(event),
Executive,
KernelMode,
FALSE,
NULL);
return status;
}
VOID
SpEnumerateAdapterAsynchronous(
IN PADAPTER_EXTENSION Adapter,
IN PSP_ENUMERATION_REQUEST Request,
IN BOOLEAN Force
)
/*++
Routine Description:
This routine will queue a bus scan and return. When the scan completes the
worker thread will run the callback in the Request passed in by the caller.
Details:
If the force flag (or the ForceNextBusScan flag in the adapter) is set or
the minimum interval between bus scans has passed then this routine will
queue this enumeration request to the work list and, if necessary, start
a new worker thread to process them.
Otherwise it will attempt to acquire the EnumerationDeviceMutex in order to
run the completion routine. If this is not available then it will also
queue the work item and start the thread if necessary.
Arguments:
Adapter - the adapter to be scanned.
Request - the request to be processed when the scan is complete. The
completion routine in this request may free the request structure.
Force - hint as to whether or not we should honor the minimum bus scan
interval.
Return Value:
none
--*/
{
ULONG forceNext;
LONGLONG rescanInterval;
PAGED_CODE();
ASSERT(Request->CompletionRoutine != NULL);
ASSERT(Request->NextRequest == NULL);
ExAcquireFastMutex(&(Adapter->EnumerationWorklistMutex));
//
// Swap out the ForceNextBusScan value for FALSE.
//
forceNext = InterlockedExchange(&(Adapter->ForceNextBusScan), FALSE);
//
// Force the bus scan to happen either way.
//
Force = (Force || forceNext || Adapter->EnumerationRunning) ? TRUE : FALSE;
//
// Calculate the time between bus enumerations.
//
if(Force == FALSE) {
LARGE_INTEGER currentSystemTime;
LONGLONG lastTime;
KeQuerySystemTime(&currentSystemTime);
lastTime = Adapter->LastBusScanTime.QuadPart;
rescanInterval = currentSystemTime.QuadPart - lastTime;
}
//
// If we're required to do the bus scan then queue this request and
// schedule a work item to run in (if necessary).
//
if((Force == TRUE) || (rescanInterval > MINIMUM_BUS_SCAN_INTERVAL)) {
//
// Grab the remove lock for this device so we know it (and the
// associated code) can't be removed.
//
SpAcquireRemoveLock(Adapter->DeviceObject, Request);
//
// Queue the entry to the work list.
//
Request->NextRequest = Adapter->EnumerationWorkList;
Adapter->EnumerationWorkList = Request;
if(Adapter->EnumerationRunning == FALSE) {
//
// Start a new worker thread to run the enumeration.
//
Adapter->EnumerationRunning = TRUE;
ExQueueWorkItem(&(Adapter->EnumerationWorkItem), DelayedWorkQueue);
}
ExReleaseFastMutex(&(Adapter->EnumerationWorklistMutex));
} else {
NTSTATUS status;
PIRP irp = NULL;
//
// We're going to try and satisfy this request immediately.
// If there is currently an enumeration running then we'll try to
// acquire the EnumerationDeviceMutex. If that fails we'll just
// queue the request for the worker to complete. If the worker is
// not running then we just acquire the mutex and process the request.
//
ASSERT(Adapter->EnumerationRunning == FALSE);
ExReleaseFastMutex(&(Adapter->EnumerationWorklistMutex));
status = KeWaitForMutexObject(&(Adapter->EnumerationDeviceMutex),
UserRequest,
UserMode,
FALSE,
NULL);
//
// If this is an async request, save the IRP so we can complete
// it after we've filled in the completion information. We can't
// touch the request after we return from our completion callback.
//
if (Request->Synchronous == FALSE) {
irp = (PIRP) Request->Context;
}
//
// Either we got the mutex (STATUS_SUCCESS) or the thread is being
// terminated (STATUS_USER_APC - since we're not alertable a
// user-mode APC can't be run except in certain special cases).
//
// Either way the completion routine will do the correct thing.
//
Request->CompletionRoutine(Adapter, Request, status);
KeReleaseMutex(&(Adapter->EnumerationDeviceMutex), FALSE);
//
// If this is an async request, complete the IRP or pass it down
// depending on the status.
//
if (irp != NULL) {
SpCompleteEnumRequest(Adapter, irp);
}
}
return;
}
VOID
SpSignalEnumerationCompletion(
IN PADAPTER_EXTENSION Adapter,
IN PSP_ENUMERATION_REQUEST Request,
IN NTSTATUS Status
)
{
if(ARGUMENT_PRESENT(Request->CompletionStatus)) {
*(Request->CompletionStatus) = Status;
}
KeSetEvent((PKEVENT) Request->Context, IO_NO_INCREMENT, FALSE);
return;
}
VOID
SpEnumerationWorker(
IN PADAPTER_EXTENSION Adapter
)
{
NTSTATUS status;
PSP_ENUMERATION_REQUEST request;
PKTHREAD thread;
PIRP currentIrp;
PLIST_ENTRY currentEntry;
LIST_ENTRY completedListHead;
PAGED_CODE();
ASSERT_FDO(Adapter->DeviceObject);
ASSERT(Adapter->EnumerationRunning == TRUE);
//
// Initialize the list of completed IRPs.
//
InitializeListHead(&completedListHead);
Adapter->EnumerationWorkThread = KeGetCurrentThread();
//
// Grab the device mutex and enumerate the bus.
//
KeWaitForMutexObject(&(Adapter->EnumerationDeviceMutex),
UserRequest,
UserMode,
FALSE,
NULL);
SpScanAdapter(Adapter);
//
// Drop the device mutex & grab the WorkList mutex.
//
KeReleaseMutex(&(Adapter->EnumerationDeviceMutex), FALSE);
ExAcquireFastMutex(&(Adapter->EnumerationWorklistMutex));
//
// Update the time of this bus scan.
//
KeQuerySystemTime(&(Adapter->LastBusScanTime));
//
// Grab a temporary remove lock. Use the address of the work item as a
// cheap way of ensuring that we haven't requeued the work item while the
// thread is still running.
//
SpAcquireRemoveLock(Adapter->DeviceObject, &(Adapter->EnumerationWorkItem));
//
// Run through the list of enumeration requests. For each one:
// * remove it from the work list.
// * save the irp if it's an async request
// * call its completion routine
//
for(request = Adapter->EnumerationWorkList;
request != NULL;
request = Adapter->EnumerationWorkList) {
//
// Remove this entry from the list. Clear the next request pointer
// as a bugcatcher.
//
Adapter->EnumerationWorkList = request->NextRequest;
request->NextRequest = NULL;
//
// If this is an asynchronous request, add the IRP to the completed list.
//
if (request->Synchronous == FALSE) {
currentIrp = (PIRP)request->Context;
InsertTailList(&completedListHead, &currentIrp->Tail.Overlay.ListEntry);
}
//
// Release the remove lock we acquired on behalf of the request object
// before we call the completion routine. The temporary lock we
// acquired above protects us.
//
SpReleaseRemoveLock(Adapter->DeviceObject, request);
//
// Call our completion callback routine.
//
request->CompletionRoutine(Adapter, request, STATUS_SUCCESS);
request = NULL;
}
//
// Indicate that the work item is no longer running.
//
Adapter->EnumerationRunning = FALSE;
Adapter->EnumerationWorkThread = NULL;
//
// Release the lock.
//
ExReleaseFastMutex(&(Adapter->EnumerationWorklistMutex));
//
// For asynchronous bus scans, we must wait until we've released the fast
// mutex to complete the IRPs. Doing so while holding the fast mutex
// completes the IRP at APC_LEVEL and this opens the door to filter
// drivers completion routines calling one of our dispatch routines at
// elevated IRQL. This is a problem because some of these dispatch
// routines process requests synchronously by blocking the thread and
// waiting for the IO Manager to set an event upon request completion.
// The problem is that the IO Manager, for synchronous operations,
// schedules an APC for the original thread in order to set the event
// and do buffer copying in the caller's thread context. This of course
// deadlocks because the waiting thread is already at APC_LEVEL.
//
// By releasing the mutex first, we drop the thread's IRQL back to
// PASSIVE_LEVEL and the problem is solved.
//
// The completion callback set the IRP's status and information fields;
// all we have to do is either forward the IRP down the stack if the
// status indicates success or complete it if the request failed.
//
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
while (IsListEmpty(&completedListHead) == FALSE) {
//
// Get the next entry from the list.
//
currentEntry = RemoveHeadList(&completedListHead);
//
// Extract a pointer to the IRP.
//
currentIrp = CONTAINING_RECORD(currentEntry,
IRP,
Tail.Overlay.ListEntry);
//
// Complete the IRP.
//
SpCompleteEnumRequest(Adapter, currentIrp);
}
//
// Release the temporary remove lock we acquired above.
//
SpReleaseRemoveLock(Adapter->DeviceObject, &(Adapter->EnumerationWorkItem));
return;
}
VOID
SpScanAdapter(
IN PADAPTER_EXTENSION Adapter
)
/*++
Routine Description:
This routine scans all of the busses on an adapter. It locks down the
necessary memory pages, checks the registry to see if we should be
exposing disconnected luns, powers up the controller (if needed) and
then scans each bus for devices.
This routine is very much non-reenterant and should not be called outside
of the enumeration mutex (ie. outside of an enumeration request).
Arguments:
Adapter - a pointer to the adapter being enumerated.
Return Value:
none
--*/
{
PDEVICE_OBJECT deviceObject = Adapter->DeviceObject;
UCHAR i;
BOOLEAN exposeDisconnectedLuns = FALSE;
PLOGICAL_UNIT_EXTENSION rescanLun;
#ifdef ALLOC_PRAGMA
PVOID sectionHandle;
#endif
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
DebugPrint((EnumDebug, "SpScanAdapter: Beginning scan of adapter %#p\n", Adapter));
//
// Try to allocate a logical unit to use for probeing new bus addresses.
// Assume that it's going to be a SCSI-2 device.
//
status = SpCreateLogicalUnit(Adapter,
0xff,
0xff,
0xff,
TRUE,
FALSE,
&rescanLun);
if(!NT_SUCCESS(status)) {
return;
}
//
// Lock down the PAGELOCK section - we'll need it in order to call
// IssueInquiry.
//
#ifdef ALLOC_PRAGMA
sectionHandle = MmLockPagableCodeSection(GetNextLuRequestWithoutLock);
InterlockedIncrement(&SpPAGELOCKLockCount);
#endif
//
// Check to see if we should be exposing disconnected LUNs.
//
for(i = 0; i < 3; i++) {
PWCHAR locations[] = {
L"Scsiport",
SCSIPORT_CONTROL_KEY,
DISK_SERVICE_KEY
};
UNICODE_STRING unicodeString;
OBJECT_ATTRIBUTES objectAttributes;
HANDLE instanceHandle = NULL;
HANDLE handle;
PKEY_VALUE_FULL_INFORMATION key = NULL;
if(i == 0) {
status = IoOpenDeviceRegistryKey(Adapter->LowerPdo,
PLUGPLAY_REGKEY_DEVICE,
KEY_READ,
&instanceHandle);
if(!NT_SUCCESS(status)) {
DebugPrint((2, "SpScanAdapter: Error %#08lx opening device registry key\n", status));
continue;
}
}
RtlInitUnicodeString(&unicodeString, locations[i]);
InitializeObjectAttributes(
&objectAttributes,
&unicodeString,
OBJ_CASE_INSENSITIVE,
instanceHandle,
NULL);
status = ZwOpenKey(&handle,
KEY_READ,
&objectAttributes);
if(!NT_SUCCESS(status)) {
DebugPrint((2, "SpScanAdapter: Error %#08lx opening %wZ key\n", status, &unicodeString));
if(instanceHandle != NULL) {
ZwClose(instanceHandle);
instanceHandle = NULL;
}
continue;
}
status = SpGetRegistryValue(deviceObject->DriverObject,
handle,
L"ScanDisconnectedDevices",
&key);
ZwClose(handle);
if(instanceHandle != NULL) {
ZwClose(instanceHandle);
instanceHandle = NULL;
}
if(NT_SUCCESS(status)) {
if(key->Type == REG_DWORD) {
PULONG value;
value = (PULONG) ((PUCHAR) key + key->DataOffset);
if(*value) {
exposeDisconnectedLuns = TRUE;
}
}
ExFreePool(key);
break;
} else {
DebugPrint((2, "SpScanAdapter: Error %#08lx opening %wZ\\ScanDisconnectedDevices value\n", status, &unicodeString));
}
}
//
// We need to be powered up in order to do a bus enumeration - make
// sure that we are. This is because we create new PDO's and new
// PDO's are assumed to be at D0.
//
status = SpRequestValidAdapterPowerStateSynchronous(Adapter);
if(NT_SUCCESS(status)) {
UCHAR pathId;
for (pathId = 0; pathId < Adapter->NumberOfBuses; pathId++) {
status = SpScanBus(Adapter, pathId, exposeDisconnectedLuns, rescanLun);
if(!NT_SUCCESS(status)) {
break;
}
}
}
#ifdef ALLOC_PRAGMA
InterlockedDecrement(&SpPAGELOCKLockCount);
MmUnlockPagableImageSection(sectionHandle);
#endif
SpDeleteLogicalUnit(rescanLun);
ASSERT(Adapter->RescanLun == NULL);
return;
}
NTSTATUS
SpScanBus(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN BOOLEAN ExposeDisconnectedLuns,
IN PLOGICAL_UNIT_EXTENSION RescanLun
)
{
UCHAR targetIndex;
NTSTATUS status = STATUS_SUCCESS;
DebugPrint((EnumDebug, "SpScanBus: Beginning scan of bus %x\n", PathId));
for(targetIndex = 0; targetIndex < Adapter->MaximumTargetIds; targetIndex++) {
UCHAR targetId;
if(Adapter->Capabilities.AdapterScansDown) {
targetId = Adapter->MaximumTargetIds - targetIndex - 1;
} else {
targetId = targetIndex;
}
DebugPrint((EnumDebug, "SpScanBus: targetIndex = %x -> targetId = %x\n",
targetIndex, targetId));
ASSERT(targetId != 255);
ASSERT(Adapter->PortConfig);
if(targetId == Adapter->PortConfig->InitiatorBusId[PathId]) {
DebugPrint((EnumDebug, "SpScanBus: Target ID matches initiator ID - skipping\n"));
continue;
}
//
// Mark all of the logical units as needing verification. At the
// end of scanning the target and LUNs which still need to be verified
// will be purged (marked as missing).
//
SpSetVerificationMarks(Adapter, PathId, targetId);
RescanLun->NeedsVerification = TRUE;
status = SpScanTarget(Adapter,
PathId,
targetId,
ExposeDisconnectedLuns,
RescanLun);
SpPurgeTarget(Adapter, PathId, targetId);
if(!NT_SUCCESS(status)) {
break;
}
}
return status;
}
NTSTATUS
SpScanTarget(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId,
IN BOOLEAN ExposeDisconnectedLuns,
IN PLOGICAL_UNIT_EXTENSION RescanLun
)
{
BOOLEAN sparseLun = FALSE;
PLOGICAL_UNIT_EXTENSION lunZero;
BOOLEAN checkNextLun;
BOOLEAN scsi1 = FALSE;
PLUN_LIST lunList = NULL;
BOOLEAN saveLunList = FALSE;
ULONG numLunsReported;
UCHAR maxLuCount;
ULONG lunIndex;
NTSTATUS resetStatus;
NTSTATUS status;
DebugPrint((EnumDebug, "SpScanTarget: Beginning scan of target %x\n", TargetId));
//
// Use the SCSI-2 dispatch table when checking LUN 0.
//
ASSERT(RescanLun->CommonExtension.MajorFunction == DeviceMajorFunctionTable);
//
// Issue an inquiry to LUN 0.
//
status = SpInquireLogicalUnit(Adapter,
PathId,
TargetId,
(UCHAR) 0,
TRUE,
RescanLun,
&lunZero,
&checkNextLun);
//
// reset the rescan lun so that we can safely use it again. If this fails
// we still continue as far as possible with this target, but we return the
// reset status to the caller so it can abort its scan.
//
resetStatus = SpPrepareLogicalUnitForReuse(RescanLun);
if(!NT_SUCCESS(resetStatus)) {
RescanLun = NULL;
}
if(!NT_SUCCESS(status)) {
//
// There is no device present at LUN 0. Skip to the next target.
// Even if sparse luns is enabled there MUST be a LUN 0 for us to
// continue scanning the target.
//
DebugPrint((EnumDebug, "SpScanTarget: Lun 0 not found - terminating scan "
"(status %#08lx)\n", status));
return resetStatus;
}
//
// Indicate that lun 0 does not require verification.
//
SpClearVerificationMark(lunZero);
//
// Check for the special case of only having one LUN on this target.
//
if(lunZero->SpecialFlags.OneLun) {
DebugPrint((EnumDebug, "SpScanTarget: Target (%x,%x,*) is listed as having "
"only one lun\n", PathId, TargetId));
return resetStatus;
}
//
// Set the rescan LUN to use whatever lun zero uses for a dispatch table.
//
RescanLun->CommonExtension.MajorFunction =
lunZero->CommonExtension.MajorFunction;
//
// Determine if we should be handling sparse LUNs on this target.
//
sparseLun = TEST(lunZero->SpecialFlags.SparseLun);
if(sparseLun) {
DebugPrint((EnumDebug, "SpScanTarget: Target (%x,%x,*) will be checked for "
"sparse luns\n", PathId, TargetId));
}
//
// Issue a report luns command to the device if it supports it.
// If it doesn't support it then use the default LUN list.
//
if((lunZero->InquiryData.HiSupport || lunZero->SpecialFlags.LargeLuns)) {
DebugPrint((EnumDebug, "SpScanTarget: Target (%x,%x,*) may support REPORT_LUNS\n", PathId, TargetId));
//
// Indicate that we should indeed save the lun list. If it turns out
// that we're unable to retrieve one to be saved then we will
// clear the flag below.
//
saveLunList = TRUE;
status = IssueReportLuns(lunZero, &lunList);
//
// If the request fails for some reason then try to use the lun list
// which was saved for this target (in the extension of logical unit
// zero). If that hasn't been set either then we'll use the default
// one down below.
//
if(!NT_SUCCESS(status)) {
DebugPrint((EnumDebug, "SpScanTarget: Target (%x,%x,*) returned %#08lx to REPORT_LUNS command - using old list\n", PathId, TargetId, status));
lunList = lunZero->TargetLunList;
}
//
// If we can now or have in the past gotten a report luns list from the
// device then enable sparse lun scanning. In this case we also assume
// that up to 255 luns can be supported on this target.
//
if(lunList != NULL) {
sparseLun = TRUE;
DebugPrint((EnumDebug, "SpScanTarget: Target (%x,%x,*) will be checked for "
"sparse luns(2)\n", PathId, TargetId));
}
}
//
// if we still don't have a lun list then use the "default" one. In that
// event don't save it.
//
if(lunList == NULL) {
DebugPrint((EnumDebug, "SpScanTarget: Target (%x,%x,*) will use default lun list\n", PathId, TargetId));
lunList = (PLUN_LIST) &(ScsiPortDefaultLunList);
saveLunList = FALSE;
}
numLunsReported = lunList->LunListLength[3] << 0;
numLunsReported |= lunList->LunListLength[2] << 8;
numLunsReported |= lunList->LunListLength[1] << 16;
numLunsReported |= lunList->LunListLength[0] << 24;
numLunsReported /= sizeof (lunList->Lun[0]);
DebugPrint((EnumDebug, "SpScanTarget: Target (%x,%x,*) has reported %d luns\n", PathId, TargetId, numLunsReported));
//
// Walk through each entry in the LUN list. Stop when we run out of entries
// or the logical unit number is > MaximumNumberOfLogicalUnits (the lun
// list is assumed to be sorted in increasing order). For each entry,
// issue an inquiry. If the inquiry succeeds then clear the verification
// mark.
//
for(lunIndex = 0; lunIndex < numLunsReported; lunIndex++) {
PULONGLONG largeLun;
USHORT lun;
PLOGICAL_UNIT_EXTENSION logicalUnit;
NTSTATUS resetStatus;
largeLun = (PULONGLONG) (lunList->Lun[lunIndex]);
lun = lunList->Lun[lunIndex][1] << 0;
lun |= lunList->Lun[lunIndex][0] << 8;
lun &= 0x3fff;
//
// If the target reports a lun 0 just skip it.
//
DebugPrint((EnumDebug, "SpScanTarget: Checking lun %I64lx (%x): ", *largeLun, lun));
if(lun == 0) {
DebugPrint((EnumDebug, "Skipping LUN 0\n"));
continue;
}
//
// If the target reports a lun outside the range the driver can support
// then skip it.
//
if(lun >= Adapter->PortConfig->MaximumNumberOfLogicalUnits) {
DebugPrint((EnumDebug, "Skipping LUN out of range (> %x)\n",
Adapter->PortConfig->MaximumNumberOfLogicalUnits));
continue;
}
//
// Issue an inquiry to each logical unit in the system.
//
status = SpInquireLogicalUnit(Adapter,
PathId,
TargetId,
(UCHAR) lun,
ExposeDisconnectedLuns,
RescanLun,
&logicalUnit,
&checkNextLun);
if(RescanLun != NULL) {
resetStatus = SpPrepareLogicalUnitForReuse(RescanLun);
if(!NT_SUCCESS(resetStatus)) {
RescanLun = NULL;
}
}
if(NT_SUCCESS(status)) {
DebugPrint((EnumDebug, "Inquiry succeeded\n"));
SpClearVerificationMark(logicalUnit);
} else {
DebugPrint((EnumDebug, "inquiry returned %#08lx.", status));
if((sparseLun == FALSE)&&(checkNextLun == FALSE)) {
DebugPrint((EnumDebug, "Aborting\n"));
break;
} else {
DebugPrint((EnumDebug, " - checking next (%c%c)\n",
sparseLun ? 's' : ' ',
checkNextLun ? 'c' : ' '));
}
}
}
//
// If we're supposed to save the lun list then replace the one in lun0
// with this one.
//
if(saveLunList) {
DebugPrint((EnumDebug, "SpScanTarget: Saving LUN list %#08lx\n", lunList));
ASSERT(lunZero->TargetLunList != (PLUN_LIST) &(ScsiPortDefaultLunList));
if(lunZero->TargetLunList != NULL && lunZero->TargetLunList != lunList) {
DebugPrint((EnumDebug, "SpScanTarget: Freeing old LUN list %#08lx\n", lunZero->TargetLunList));
ExFreePool(lunZero->TargetLunList);
}
lunZero->TargetLunList = lunList;
} else {
ASSERT(lunList == (PLUN_LIST) &(ScsiPortDefaultLunList));
}
//
// reset the rescan LUN to use the scsi 2 dispatch table.
//
RescanLun->CommonExtension.MajorFunction = DeviceMajorFunctionTable;
return resetStatus;
}
VOID
SpSetVerificationMarks(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId
)
{
PLOGICAL_UNIT_EXTENSION logicalUnit;
KIRQL oldIrql;
ULONG bin;
//
// Code is paged until locked down.
//
PAGED_CODE();
ASSERT(SpPAGELOCKLockCount != 0);
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
for(bin = 0; bin < NUMBER_LOGICAL_UNIT_BINS; bin++) {
KeAcquireSpinLockAtDpcLevel(&(Adapter->LogicalUnitList[bin].Lock));
for(logicalUnit = Adapter->LogicalUnitList[bin].List;
logicalUnit != NULL;
logicalUnit = logicalUnit->NextLogicalUnit) {
ASSERT(logicalUnit->IsTemporary == FALSE);
if((logicalUnit->PathId == PathId) &&
(logicalUnit->TargetId == TargetId)) {
logicalUnit->NeedsVerification = TRUE;
}
}
KeReleaseSpinLockFromDpcLevel(&(Adapter->LogicalUnitList[bin].Lock));
}
KeLowerIrql(oldIrql);
return;
}
VOID
SpClearVerificationMark(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit
)
{
PAGED_CODE();
ASSERT(LogicalUnit->IsTemporary == FALSE);
ASSERT(LogicalUnit->NeedsVerification == TRUE);
LogicalUnit->NeedsVerification = FALSE;
return;
}
VOID
SpPurgeTarget(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId
)
{
PLOGICAL_UNIT_EXTENSION logicalUnit;
KIRQL oldIrql;
ULONG bin;
//
// Code is paged until locked down.
//
PAGED_CODE();
ASSERT(SpPAGELOCKLockCount != 0);
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
for(bin = 0; bin < NUMBER_LOGICAL_UNIT_BINS; bin++) {
KeAcquireSpinLockAtDpcLevel(&(Adapter->LogicalUnitList[bin].Lock));
for(logicalUnit = Adapter->LogicalUnitList[bin].List;
logicalUnit != NULL;
logicalUnit = logicalUnit->NextLogicalUnit) {
ASSERT(logicalUnit->IsTemporary == FALSE);
if((logicalUnit->PathId == PathId) &&
(logicalUnit->TargetId == TargetId) &&
(logicalUnit->NeedsVerification == TRUE)) {
//
// This device was not found to be present during our bus scan.
//
DebugPrint((EnumDebug, "SpPurgeTarget: Lun (%x,%x,%x) is still marked and will be made missing\n", logicalUnit->PathId, logicalUnit->TargetId, logicalUnit->Lun));
logicalUnit->IsMissing = TRUE;
}
}
KeReleaseSpinLockFromDpcLevel(&(Adapter->LogicalUnitList[bin].Lock));
}
KeLowerIrql(oldIrql);
return;
}
NTSTATUS
SpCreateLogicalUnit(
IN PADAPTER_EXTENSION Adapter,
IN OPTIONAL UCHAR PathId,
IN OPTIONAL UCHAR TargetId,
IN OPTIONAL UCHAR Lun,
IN BOOLEAN Temporary,
IN BOOLEAN Scsi1,
OUT PLOGICAL_UNIT_EXTENSION *NewLun
)
/*++
Routine Description:
This routine will create a physical device object for the specified device
Arguments:
Adapter - the parent adapter for this new lun
PathId, TargetId, Lun - the address of this lun. Not used if Temporary is
TRUE (see below).
Temporary - indicates whether this device is real (FALSE) or simply for
the purposes of scanning the bus (TRUE). If TRUE then the
address info is ignored and this lun is NOT inserted into the
logical unit list.
Scsi1 - indicates that this LUN is a scsi1 lun and needs to use the
dispatch routines which stick the LUN number into the CDB itself.
NewLun - a location to store the pointer to the new lun
Return Value:
status
--*/
{
PIRP senseIrp;
PDEVICE_OBJECT pdo = NULL;
PLOGICAL_UNIT_EXTENSION logicalUnitExtension;
WCHAR wideDeviceName[64];
UNICODE_STRING unicodeDeviceName;
PVOID hwExtension = NULL;
PVOID serialNumberBuffer = NULL;
PVOID idBuffer = NULL;
NTSTATUS status;
PAGED_CODE();
//
// Attempt to allocate all the persistent resources we need before we
// try to create the device object itself.
//
//
// Allocate a request sense irp.
//
senseIrp = SpAllocateIrp(1, FALSE, Adapter->DeviceObject->DriverObject);
if(senseIrp == NULL) {
DebugPrint((0, "SpCreateLogicalUnit: Could not allocate request sense "
"irp\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Build the name for the device
//
if(Temporary == FALSE) {
swprintf(wideDeviceName,
L"%wsPort%xPath%xTarget%xLun%x",
Adapter->DeviceName,
Adapter->PortNumber,
PathId,
TargetId,
Lun);
} else {
swprintf(wideDeviceName,
L"%wsPort%xRescan",
Adapter->DeviceName,
Adapter->PortNumber);
PathId = 0xff;
TargetId = 0xff;
Lun = 0xff;
ASSERT(Adapter->RescanLun == NULL);
}
RtlInitUnicodeString(&unicodeDeviceName, wideDeviceName);
//
// Round the size of the Hardware logical extension to the size of a
// PVOID and add it to the port driver's logical extension.
//
if(Adapter->HwLogicalUnitExtensionSize != 0) {
hwExtension = SpAllocatePool(
NonPagedPoolCacheAligned,
Adapter->HwLogicalUnitExtensionSize,
SCSIPORT_TAG_LUN_EXT,
Adapter->DeviceObject->DriverObject);
if(hwExtension == NULL) {
*NewLun = NULL;
IoFreeIrp(senseIrp);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(hwExtension,
Adapter->HwLogicalUnitExtensionSize);
}
//
// If this is a temporary lun then allocate a large buffer to store the
// identify data.
//
if(Temporary) {
serialNumberBuffer = SpAllocatePool(
PagedPool,
VPD_MAX_BUFFER_SIZE,
SCSIPORT_TAG_TEMP_ID_BUFFER,
Adapter->DeviceObject->DriverObject);
if(serialNumberBuffer == NULL) {
if (hwExtension != NULL) {
ExFreePool(hwExtension);
}
IoFreeIrp(senseIrp);
return STATUS_INSUFFICIENT_RESOURCES;
}
idBuffer = SpAllocatePool(PagedPool,
VPD_MAX_BUFFER_SIZE,
SCSIPORT_TAG_TEMP_ID_BUFFER,
Adapter->DeviceObject->DriverObject);
if(idBuffer == NULL) {
if (hwExtension != NULL) {
ExFreePool(hwExtension);
}
IoFreeIrp(senseIrp);
ExFreePool(serialNumberBuffer);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(serialNumberBuffer, VPD_MAX_BUFFER_SIZE);
RtlZeroMemory(idBuffer, VPD_MAX_BUFFER_SIZE);
}
//
// Create a physical device object
//
status = IoCreateDevice(
Adapter->DeviceObject->DriverObject,
sizeof(LOGICAL_UNIT_EXTENSION),
&unicodeDeviceName,
FILE_DEVICE_MASS_STORAGE,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&pdo
);
if(NT_SUCCESS(status)) {
PCOMMON_EXTENSION commonExtension;
UCHAR i;
ULONG bin;
UCHAR rawDeviceName[64];
ANSI_STRING ansiDeviceName;
//
// Set the device object's stack size
//
//
// We need one stack location for the PDO to do lock tracking and
// one stack location to issue scsi request to the FDO.
//
pdo->StackSize = 1;
pdo->Flags |= DO_BUS_ENUMERATED_DEVICE;
pdo->Flags |= DO_DIRECT_IO;
pdo->AlignmentRequirement = Adapter->DeviceObject->AlignmentRequirement;
//
// Initialize the device extension for the root device
//
commonExtension = pdo->DeviceExtension;
logicalUnitExtension = pdo->DeviceExtension;
RtlZeroMemory(logicalUnitExtension, sizeof(LOGICAL_UNIT_EXTENSION));
commonExtension->DeviceObject = pdo;
commonExtension->IsPdo = TRUE;
commonExtension->LowerDeviceObject = Adapter->DeviceObject;
if(Scsi1) {
commonExtension->MajorFunction = Scsi1DeviceMajorFunctionTable;
} else {
commonExtension->MajorFunction = DeviceMajorFunctionTable;
}
commonExtension->WmiInitialized = FALSE;
commonExtension->WmiMiniPortSupport =
Adapter->CommonExtension.WmiMiniPortSupport;
commonExtension->WmiScsiPortRegInfoBuf = NULL;
commonExtension->WmiScsiPortRegInfoBufSize = 0;
//
// Initialize value to zero. It will be incremented once pnp is aware
// of its existance.
//
commonExtension->RemoveLock = 0;
#if DBG
KeInitializeSpinLock(&commonExtension->RemoveTrackingSpinlock);
commonExtension->RemoveTrackingList = NULL;
ExInitializeNPagedLookasideList(
&(commonExtension->RemoveTrackingLookasideList),
NULL,
NULL,
0,
sizeof(REMOVE_TRACKING_BLOCK),
SCSIPORT_TAG_LOCK_TRACKING,
64);
commonExtension->RemoveTrackingLookasideListInitialized = TRUE;
#else
commonExtension->RemoveTrackingSpinlock = (ULONG) -1L;
commonExtension->RemoveTrackingList = (PVOID) -1L;
#endif
commonExtension->CurrentPnpState = 0xff;
commonExtension->PreviousPnpState = 0xff;
//
// Initialize the remove lock event.
//
KeInitializeEvent(
&(logicalUnitExtension->CommonExtension.RemoveEvent),
SynchronizationEvent,
FALSE);
logicalUnitExtension->PortNumber = Adapter->PortNumber;
logicalUnitExtension->PathId = 0xff;
logicalUnitExtension->TargetId = 0xff;
logicalUnitExtension->Lun = 0xff;
logicalUnitExtension->HwLogicalUnitExtension = hwExtension;
logicalUnitExtension->AdapterExtension = Adapter;
//
// Give the caller the benefit of the doubt.
//
logicalUnitExtension->IsMissing = FALSE;
//
// The device cannot have been enumerated yet.
//
logicalUnitExtension->IsEnumerated = FALSE;
//
// Set timer counters to -1 to inidicate that there are no outstanding
// requests.
//
logicalUnitExtension->RequestTimeoutCounter = -1;
//
// Initialize the maximum queue depth size.
//
logicalUnitExtension->MaxQueueDepth = 0xFF;
//
// Initialize the request list.
//
InitializeListHead(&logicalUnitExtension->RequestList);
//
// Initialize the push/pop list of SRB_DATA blocks for use with bypass
// requests.
//
KeInitializeSpinLock(&(logicalUnitExtension->BypassSrbDataSpinLock));
ExInitializeSListHead(&(logicalUnitExtension->BypassSrbDataList));
for(i = 0; i < NUMBER_BYPASS_SRB_DATA_BLOCKS; i++) {
ExInterlockedPushEntrySList(
&(logicalUnitExtension->BypassSrbDataList),
&(logicalUnitExtension->BypassSrbDataBlocks[i].Reserved),
&(logicalUnitExtension->BypassSrbDataSpinLock));
}
//
// Assume devices are powered on by default.
//
commonExtension->CurrentDeviceState = PowerDeviceD0;
commonExtension->DesiredDeviceState = PowerDeviceUnspecified;
//
// Assume that we're being initialized in a working system.
//
commonExtension->CurrentSystemState = PowerSystemWorking;
//
// Setup the request sense resources.
//
logicalUnitExtension->RequestSenseIrp = senseIrp;
//
// If this is temporary record that fact in the logical unit extension
// and save a pointer in the adapter (cleared when the LUN is
// destroyed). If it's real then stick it into the logical unit list.
//
logicalUnitExtension->IsTemporary = Temporary;
//
// Initialize
RtlInitAnsiString(&(logicalUnitExtension->SerialNumber), serialNumberBuffer);
if(serialNumberBuffer != NULL) {
logicalUnitExtension->SerialNumber.MaximumLength = VPD_MAX_BUFFER_SIZE;
}
logicalUnitExtension->DeviceIdentifierPage = idBuffer;
//
// I guess this is as ready to be opened as it ever will be.
//
pdo->Flags &= ~DO_DEVICE_INITIALIZING;
//
// Initialize the lock & unlock request queue.
//
KeInitializeDeviceQueue(&(logicalUnitExtension->LockRequestQueue));
logicalUnitExtension->CurrentLockRequest = NULL;
} else {
DebugPrint((1, "ScsiBusCreatePdo: Error %#08lx creating device object\n",
status));
logicalUnitExtension = NULL;
if(hwExtension != NULL) {
ExFreePool(hwExtension);
}
IoFreeIrp(senseIrp);
ExFreePool(serialNumberBuffer);
ExFreePool(idBuffer);
}
*NewLun = logicalUnitExtension;
return status;
}
VOID
SpSetLogicalUnitAddress(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN UCHAR PathId,
IN UCHAR TargetId,
IN UCHAR Lun
)
{
UCHAR i;
ULONG bin;
ASSERT_PDO(LogicalUnit->DeviceObject);
ASSERT(LogicalUnit->PathId == 0xff);
ASSERT(LogicalUnit->TargetId == 0xff);
ASSERT(LogicalUnit->Lun == 0xff);
LogicalUnit->PathId = PathId;
LogicalUnit->TargetId = TargetId;
LogicalUnit->Lun = Lun;
SpAddLogicalUnitToBin(LogicalUnit->AdapterExtension, LogicalUnit);
return;
}
VOID
SpClearLogicalUnitAddress(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit
)
{
ASSERT_PDO(LogicalUnit->DeviceObject);
ASSERT(LogicalUnit->IsTemporary == TRUE);
SpRemoveLogicalUnitFromBin(LogicalUnit->AdapterExtension, LogicalUnit);
LogicalUnit->PathId = 0xff;
LogicalUnit->TargetId = 0xff;
LogicalUnit->Lun = 0xff;
return;
}
NTSTATUS
SpCloneAndSwapLogicalUnit(
IN PLOGICAL_UNIT_EXTENSION TemplateLun,
IN PINQUIRYDATA InquiryData,
IN ULONG InquiryDataSize,
OUT PLOGICAL_UNIT_EXTENSION *NewLun
)
/*++
Routine Description:
This routine will create a new logical unit object with the properties of
TemplateLun. The supplied inquiry data will be assigned to the new
logical unit. Finally the new logical unit will be swapped for
TemplateLun in the adapter's logical unit list.
TemplateLun must be a temporary logical unit which has been assigned an
address and is present in the logical unit lists.
Regardless of whether this function succeeds, the TemplateLun will be
removed from the logical unit list (effectively swapped with nothing).
Arguments:
TemplateLun - the logical unit to be cloned
InquiryData, InquiryDataSize - the inquiry data to be used for the new
logical unit
NewLun - a location to store the pointer to the new logical unit.
Return Value:
STATUS_SUCCESS indicates that a new lun has been created and swapped in
the logical unit list.
error status indicates that the new logical unit could not be created for
some reason.
--*/
{
PADAPTER_EXTENSION adapter = TemplateLun->AdapterExtension;
PSCSIPORT_DRIVER_EXTENSION driverExtension =
IoGetDriverObjectExtension(
adapter->DeviceObject->DriverObject,
ScsiPortInitialize);
UCHAR pathId, targetId, lun;
PVOID serialNumber = NULL;
USHORT serialNumberLength = 0;
PVOID identifier = NULL;
ULONG identifierLength = 0;
PLOGICAL_UNIT_EXTENSION newLun;
BOOLEAN scsi1;
NTSTATUS status;
ASSERT_PDO(TemplateLun->DeviceObject);
ASSERT(TemplateLun->IsTemporary);
*NewLun = NULL;
#if DBG
newLun = GetLogicalUnitExtension(adapter,
TemplateLun->PathId,
TemplateLun->TargetId,
TemplateLun->Lun,
NULL,
TRUE);
ASSERT(newLun == TemplateLun);
#endif
//
// Wait for any outstanding i/o on the template lun to complete.
//
SpReleaseRemoveLock(TemplateLun->DeviceObject, SpInquireLogicalUnit);
SpWaitForRemoveLock(TemplateLun->DeviceObject, SP_BASE_REMOVE_LOCK);
//
// Save the address away and then remove the template object from the
// logical unit list.
//
pathId = TemplateLun->PathId;
targetId = TemplateLun->TargetId;
lun = TemplateLun->Lun;
SpClearLogicalUnitAddress(TemplateLun);
//
// Before creating a named object, preallocate any resources we'll need
// that SpCreateLogicalUnit doesn't provide.
//
if(TemplateLun->SerialNumber.Length != 0) {
serialNumberLength = (TemplateLun->SerialNumber.Length +
sizeof(UNICODE_NULL));
serialNumber = SpAllocatePool(PagedPool,
serialNumberLength,
SCSIPORT_TAG_ID_BUFFER,
TemplateLun->DeviceObject->DriverObject);
if(serialNumber == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
}
if(TemplateLun->DeviceIdentifierPageLength != 0) {
identifier = SpAllocatePool(
PagedPool,
TemplateLun->DeviceIdentifierPageLength,
SCSIPORT_TAG_ID_BUFFER,
TemplateLun->DeviceObject->DriverObject);
if(identifier == NULL) {
if(serialNumber != NULL) {
ExFreePool(serialNumber);
}
return STATUS_INSUFFICIENT_RESOURCES;
}
}
//
// If the lun is scsi-1 or if the magic registry flag was set then use the
// scsi 1 dispatch table for this device.
//
if((driverExtension->BusType == BusTypeScsi) &&
((InquiryData->ANSIVersion == 0) ||
(InquiryData->ANSIVersion == 1) ||
(TemplateLun->SpecialFlags.SetLunInCdb))) {
scsi1 = TRUE;
} else {
scsi1 = FALSE;
}
//
// Now create a new logical unit with the same address.
//
status = SpCreateLogicalUnit(adapter,
pathId,
targetId,
lun,
FALSE,
scsi1,
&newLun);
if(!NT_SUCCESS(status)) {
if(serialNumber != NULL) {
ExFreePool(serialNumber);
}
if(identifier) {
ExFreePool(identifier);
}
return status;
}
//
// Copy the important information from the template logical unit over to
// the new one. Zero out the original so that we know to reallocate one
// later.
//
newLun->HwLogicalUnitExtension = TemplateLun->HwLogicalUnitExtension;
TemplateLun->HwLogicalUnitExtension = NULL;
newLun->LuFlags = TemplateLun->LuFlags;
newLun->IsVisible = TemplateLun->IsVisible;
newLun->TargetLunList = TemplateLun->TargetLunList;
newLun->SpecialFlags = TemplateLun->SpecialFlags;
newLun->NeedsVerification = TemplateLun->NeedsVerification;
newLun->CommonExtension.SrbFlags = TemplateLun->CommonExtension.SrbFlags;
//
// Copy over any characteristics flags which were set during enumeration.
//
newLun->DeviceObject->Characteristics |=
(TemplateLun->DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA);
//
// Copy the list of supported vital product data pages.
//
newLun->DeviceIdentifierPageSupported = TemplateLun->DeviceIdentifierPageSupported;
newLun->SerialNumberPageSupported = TemplateLun->SerialNumberPageSupported;
//
// If this device reports a serial number in it's vital product data then
// copy it in to the new lun.
//
if(serialNumber != NULL) {
newLun->SerialNumber.Length = TemplateLun->SerialNumber.Length;
newLun->SerialNumber.MaximumLength = serialNumberLength;
newLun->SerialNumber.Buffer = serialNumber;
RtlCopyMemory(newLun->SerialNumber.Buffer,
TemplateLun->SerialNumber.Buffer,
serialNumberLength);
}
//
// If this has a device identifier page then copy it over two.
//
if(identifier != NULL) {
newLun->DeviceIdentifierPage = identifier;
newLun->DeviceIdentifierPageLength =
TemplateLun->DeviceIdentifierPageLength;
RtlCopyMemory(newLun->DeviceIdentifierPage,
TemplateLun->DeviceIdentifierPage,
newLun->DeviceIdentifierPageLength);
}
//
// Copy the inquiry data over.
//
ASSERT(InquiryDataSize <= sizeof(INQUIRYDATA));
RtlCopyMemory(&(newLun->InquiryData), InquiryData, InquiryDataSize);
//
// Acquire the appropriate remove locks on the new logical unit.
//
SpAcquireRemoveLock(newLun->DeviceObject, SP_BASE_REMOVE_LOCK);
SpAcquireRemoveLock(newLun->DeviceObject, SpInquireLogicalUnit);
//
// Now insert this new lun into the logical unit list.
//
SpSetLogicalUnitAddress(newLun, pathId, targetId, lun);
*NewLun = newLun;
return status;
}
NTSTATUS
SpPrepareLogicalUnitForReuse(
PLOGICAL_UNIT_EXTENSION LogicalUnit
)
{
PADAPTER_EXTENSION adapter = LogicalUnit->AdapterExtension;
PCOMMON_EXTENSION commonExtension = &(LogicalUnit->CommonExtension);
PVOID hwExtension = NULL;
NTSTATUS status;
ASSERT_PDO(LogicalUnit->DeviceObject);
ASSERT(LogicalUnit->CommonExtension.WmiInitialized == FALSE);
ASSERT(LogicalUnit->CommonExtension.WmiScsiPortRegInfoBuf == NULL);
ASSERT(LogicalUnit->CommonExtension.WmiScsiPortRegInfoBufSize == 0);
//
// Clear the remove lock event.
//
ASSERT(LogicalUnit->CommonExtension.RemoveLock == 0);
//
// Initialize the remove lock event.
//
KeClearEvent(&(LogicalUnit->CommonExtension.RemoveEvent));
LogicalUnit->PathId = 0xff;
LogicalUnit->TargetId = 0xff;
LogicalUnit->Lun = 0xff;
//
// Round the size of the Hardware logical extension to the size of a
// PVOID and add it to the port driver's logical extension.
//
if((LogicalUnit->HwLogicalUnitExtension == NULL) &&
(adapter->HwLogicalUnitExtensionSize != 0)) {
hwExtension = SpAllocatePool(NonPagedPoolCacheAligned,
adapter->HwLogicalUnitExtensionSize,
SCSIPORT_TAG_LUN_EXT,
LogicalUnit->DeviceObject->DriverObject);
if(hwExtension == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
LogicalUnit->HwLogicalUnitExtension = hwExtension;
}
if(LogicalUnit->HwLogicalUnitExtension != NULL) {
RtlZeroMemory(LogicalUnit->HwLogicalUnitExtension,
adapter->HwLogicalUnitExtensionSize);
}
LogicalUnit->IsMissing = FALSE;
LogicalUnit->IsVisible = FALSE;
ASSERT(LogicalUnit->IsEnumerated == FALSE);
//
// Device has no longer been removed.
//
LogicalUnit->CommonExtension.IsRemoved = NO_REMOVE;
//
// Clear cached infomation about the device identifier(s).
//
LogicalUnit->DeviceIdentifierPageSupported = FALSE;
LogicalUnit->SerialNumberPageSupported = FALSE;
RtlZeroMemory(LogicalUnit->SerialNumber.Buffer,
LogicalUnit->SerialNumber.MaximumLength);
LogicalUnit->SerialNumber.Length = 0;
return STATUS_SUCCESS;
}
BOOLEAN
FASTCALL
SpCompareInquiryData(
IN PUCHAR InquiryData1,
IN PUCHAR InquiryData2
)
/*++
Routine Description:
This routine compares two sets of inquiry data for equality.
Arguments:
InquiryData1 - Supplies a pointer to the first inquiry data to compare.
InquiryData2 - Supplies a pointer to the second inquiry data to compare.
Return Value:
TRUE if the supplied inquiry data sets match, else FALSE.
--*/
{
BOOLEAN match;
UCHAR save1;
UCHAR save2;
PAGED_CODE();
if (((PINQUIRYDATA)InquiryData1)->ANSIVersion == 3) {
//
// SCSI3 Specific:
// Save bytes 6 and 7. These bytes contain vendor specific bits which
// we're going to exclude from the comparison by just setting them equal
// to the corresponding bits in InquiryData2. We'll restore them after
// the comparison.
//
save1 = InquiryData1[6];
save2 = InquiryData1[7];
//
// Force the vendor specific bits in InquiryData1 to match the
// corresponsing bits in InquiryData2.
//
InquiryData1[6] &= ~0x20;
InquiryData1[7] &= ~0x01;
InquiryData1[6] |= (InquiryData2[6] & 0x20);
InquiryData1[7] |= (InquiryData2[7] & 0x01);
}
//
// Compare the entire inquiry data blob.
//
match = RtlEqualMemory((((PUCHAR) InquiryData1) + 1),
(((PUCHAR) InquiryData2) + 1),
(INQUIRYDATABUFFERSIZE - 1));
if (((PINQUIRYDATA)InquiryData1)->ANSIVersion == 3) {
//
// SCSI3 Specific:
// Restore bytes 6 and 7 to their original state.
//
InquiryData1[6] = save1;
InquiryData1[7] = save2;
}
return match;
}
NTSTATUS
SpInquireLogicalUnit(
IN PADAPTER_EXTENSION Adapter,
IN UCHAR PathId,
IN UCHAR TargetId,
IN UCHAR Lun,
IN BOOLEAN ExposeDisconnectedLuns,
IN OPTIONAL PLOGICAL_UNIT_EXTENSION RescanLun,
OUT PLOGICAL_UNIT_EXTENSION *LogicalUnit,
OUT PBOOLEAN CheckNextLun
)
/*++
Routine Description:
This routine will issue an inquiry to the logical unit at the specified
address. If there is not already a device object allocated for that
logical unit, it will create one. If it turns out the device does not
exist, the logical unit can be destroyed before returning.
If the logical unit exists, this routine will clear the PD_RESCAN_ACTIVE
flag in the LuFlags to indicate that the unit is safe.
If it does not respond, the IsMissing flag will be set to indicate that the
unit should not be reported during enumeration. If the IsRemoved flag has
already been set on the logical unit extension, the device object will be
destroyed. Otherwise the device object will not be destroyed until a
remove can be issued.
Arguments:
Adapter - the adapter which this device would exist on
PathId, TargetId, Lun - the address of the lun to inquire.
ExposeDisconnectedLuns - indicates whether luns with a qualifier of
disconnected should be instantiated.
RescanLun - a pointer to the logical unit extension to be used when
checking logical unit numbers which do not currently have an
extension associated with them.
LogicalUnit - the logical unit created for this address - valid if
success is returned.
CheckNextLun - indicates whether the caller should check the next
address for a logical unit.
Return Value:
STATUS_NO_SUCH_DEVICE if the device does not exist.
STATUS_SUCCESS if the device does exist.
error description otherwise.
--*/
{
PLOGICAL_UNIT_EXTENSION logicalUnit;
INQUIRYDATA inquiryData;
BOOLEAN newDevice = FALSE;
BOOLEAN deviceMismatch = FALSE;
UCHAR bytesReturned;
NTSTATUS status;
*LogicalUnit = NULL;
*CheckNextLun = TRUE;
PAGED_CODE();
ASSERT(TargetId != BreakOnTarget);
//
// Find or create the device object for this address. if it exists we'll
// grab a temporary lock (using SpInquireLogicalUnit as a tag).
//
logicalUnit = GetLogicalUnitExtension(Adapter,
PathId,
TargetId,
Lun,
SpInquireLogicalUnit,
TRUE);
if(logicalUnit == NULL) {
if(!ARGUMENT_PRESENT(RescanLun)) {
//
// No RescanLun was provided (generally means we're low on memory).
// Don't scan this logical unit.
//
return STATUS_INSUFFICIENT_RESOURCES;
}
ASSERT(RescanLun->IsTemporary == TRUE);
//
// Acquire the temporary lock for the rescan lun. We also grab the
// base lock here.
//
SpAcquireRemoveLock(RescanLun->DeviceObject, SP_BASE_REMOVE_LOCK);
SpAcquireRemoveLock(RescanLun->DeviceObject, SpInquireLogicalUnit);
//
// Set the address of the RescanLun appropriately - this operation
// will make the logical unit available for our use.
//
SpSetLogicalUnitAddress(RescanLun, PathId, TargetId, Lun);
logicalUnit = RescanLun;
newDevice = TRUE;
} else {
ASSERT(logicalUnit->IsTemporary == FALSE);
if(logicalUnit->IsMissing) {
DebugPrint((1, "SpInquireLogicalUnit: logical unit @ (%d,%d,%d) "
"(%#p) is marked as missing and will not be "
"rescanned\n",
PathId, TargetId, Lun,
logicalUnit->DeviceObject));
SpReleaseRemoveLock(logicalUnit->DeviceObject, SpInquireLogicalUnit);
return STATUS_DEVICE_DOES_NOT_EXIST;
}
}
//
// Issue an inquiry to the potential logical unit.
//
DebugPrint((2, "SpInquireTarget: Try %s device @ Bus %d, Target %d, "
"Lun %d\n",
(newDevice ? "new" : "existing"),
PathId,
TargetId,
Lun));
status = IssueInquiry(logicalUnit, FALSE, 0, &inquiryData, &bytesReturned);
//
// If the inquiry succeeds then check the data returned to determine if
// there's a device there we should expose.
//
if(NT_SUCCESS(status)) {
UCHAR qualifier;
BOOLEAN present = FALSE;
//
// Check in the registry for special device flags for this lun.
// If this is disconnected then set the qualifier to be 0 so that we
// use the normal hardware ids instead of the "disconnected" ones.
//
qualifier = inquiryData.DeviceTypeQualifier;
SpCheckSpecialDeviceFlags(logicalUnit, &(inquiryData));
//
// The inquiry was successful. Determine whether a device is present.
//
switch(qualifier) {
case DEVICE_QUALIFIER_ACTIVE: {
//
// Active devices are always present.
//
present = TRUE;
break;
}
case DEVICE_QUALIFIER_NOT_ACTIVE: {
if (Lun == 0) {
//
// If we're using REPORT_LUNS commands for LUN 0 of a target
// then we should always indicate that LUN 0 is present.
//
if ((inquiryData.HiSupport == TRUE) ||
(logicalUnit->SpecialFlags.LargeLuns == TRUE)) {
present = TRUE;
}
} else {
//
// Expose inactive luns only if the caller has requested that
// we do so.
//
present = ExposeDisconnectedLuns;
}
break;
}
case DEVICE_QUALIFIER_NOT_SUPPORTED: {
present = FALSE;
break;
}
default: {
present = TRUE;
break;
}
};
if(present == FALSE) {
//
// setup an error value so we'll clean up the logical unit.
// No need to do any more processing in this case.
//
status = STATUS_NO_SUCH_DEVICE;
} else if(newDevice == FALSE) {
//
// Verify that the inquiry data hasn't changed since the last time
// we did a rescan. Ignore the device type qualifier in this
// check.
//
deviceMismatch = FALSE;
if(inquiryData.DeviceType != logicalUnit->InquiryData.DeviceType) {
DebugPrint((1, "SpInquireTarget: Found different type of "
"device @ (%d,%d,%d)\n",
PathId,
TargetId,
Lun));
deviceMismatch = TRUE;
status = STATUS_NO_SUCH_DEVICE;
} else if(inquiryData.DeviceTypeQualifier !=
logicalUnit->InquiryData.DeviceTypeQualifier) {
//
// The device qualifiers don't match. This isn't necessarily
// a device mismatch if the existing device just went offline.
// lower down we'll check the remaining inquiry data to
// ensure that the LUN hasn't changed.
//
DebugPrint((1, "SpInquireLogicalUnit: Device @ (%d,%d,%d) type "
"qualifier was %d is now %d\n",
PathId,
TargetId,
Lun,
logicalUnit->InquiryData.DeviceTypeQualifier,
inquiryData.DeviceTypeQualifier
));
//
// If the device was offline but no longer is then we
// treat this as a device mismatch. If the device has gone
// offline then we pretend it's the same device.
//
// the goal is to provide PNP with a new device object when
// bringing a device online, but to reuse the same device
// object when bringing the device offline.
//
if(logicalUnit->InquiryData.DeviceTypeQualifier ==
DEVICE_QUALIFIER_NOT_ACTIVE) {
DebugPrint((1, "SpInquireLogicalUnit: device mismatch\n"));
deviceMismatch = TRUE;
status = STATUS_NO_SUCH_DEVICE;
} else {
DebugPrint((1, "SpInquireLogicalUnit: device went offline\n"));
deviceMismatch = FALSE;
status = STATUS_SUCCESS;
}
}
if (deviceMismatch == FALSE) {
//
// Ok, the device type and qualifier are compatible. Now we
// need to compare all applicable parts of the inquiry
// data with the data we already have on the device at this
// address to see if the device that answered this time is the
// same one we found last time.
//
BOOLEAN same = SpCompareInquiryData(
(PUCHAR)&(inquiryData),
(PUCHAR)&(logicalUnit->InquiryData));
if (same == FALSE) {
//
// Despite the fact that the device type & qualifier are
// compatible, a mismatch still occurred.
//
deviceMismatch = TRUE;
status = STATUS_NO_SUCH_DEVICE;
DebugPrint((1, "SpInquireLogicalUnit: Device @ (%d,%d,%d) has "
"changed\n",
PathId,
TargetId,
Lun));
} else {
//
// The device that answered is the same one we found
// earlier. Depending on the SCSI version of the device,
// we might need to update the vendor specific portions of
// the existing inquiry data for this device.
//
if (inquiryData.ANSIVersion == 3) {
//
// For SCSI 3 devices, bytes 6 and 7 contain vendor
// specific bits that may differ between bus scans.
// Update these bytes of the existing inquiry data.
//
((PUCHAR)&(logicalUnit->InquiryData))[6] =
((PUCHAR)&(inquiryData))[6];
((PUCHAR)&(logicalUnit->InquiryData))[7] =
((PUCHAR)&(inquiryData))[7];
}
}
}
} else {
DebugPrint((1, "SpInquireTarget: Found new %sDevice at address "
"(%d,%d,%d)\n",
(inquiryData.RemovableMedia ? "Removable " : ""),
PathId,
TargetId,
Lun));
}
if(NT_SUCCESS(status) && (deviceMismatch == FALSE)) {
deviceMismatch = SpGetDeviceIdentifiers(logicalUnit, newDevice);
if(deviceMismatch == FALSE) {
ASSERT(newDevice);
status = STATUS_NO_SUCH_DEVICE;
}
}
} else {
*CheckNextLun = FALSE;
}
if(!NT_SUCCESS(status)) {
//
// Nothing was found at this address. If it's a new lun which hasn't
// been enumerated yet then just destroy it here. If, however, it
// has been enumerated we have to mark it as missing and wait for
// PNP to learn that it's gone and ask us to remove it. Then we can
// destroy it.
//
// If we were just using the RescanLun to check this address then do
// nothing - the rescan lun will be reset down below.
//
logicalUnit->IsMissing = TRUE;
if(newDevice) {
//
// Release the temporary lock. the base one will be released at
// the end of this routine.
//
SpReleaseRemoveLock(logicalUnit->DeviceObject,
SpInquireLogicalUnit);
logicalUnit = NULL;
} else if (logicalUnit->IsEnumerated == FALSE) {
//
// It's safe to destroy this device object ourself since it's not
// a device PNP is aware of. However we may have outstanding i/o
// due to pass-through requests or legacy class driver so we need
// to properly wait for all i/o to complete.
//
logicalUnit->CommonExtension.CurrentPnpState =
IRP_MN_REMOVE_DEVICE;
SpReleaseRemoveLock(logicalUnit->DeviceObject, SpInquireLogicalUnit);
//
// Mark this device temporarily as visible so that
// SpRemoveLogicalUnit will do the right thing. Since the rescan
// active bit is set the enumeration code won't return this device.
//
logicalUnit->IsVisible = TRUE;
ASSERT(logicalUnit->IsEnumerated == FALSE);
ASSERT(logicalUnit->IsMissing == TRUE);
ASSERT(logicalUnit->IsVisible == TRUE);
SpRemoveLogicalUnit(logicalUnit, IRP_MN_REMOVE_DEVICE);
if(deviceMismatch) {
//
// Call this routine again. This is the only recursion and
// since we've deleted the device object there should be no
// cause for a mismatch there.
//
status = SpInquireLogicalUnit(Adapter,
PathId,
TargetId,
Lun,
ExposeDisconnectedLuns,
RescanLun,
LogicalUnit,
CheckNextLun);
}
return status;
} else {
//
// CODEWORK - freeze and flush the queue. This way we don't need
// to deal with handling get next request calls
//
//
// Mark the device as being mismatched so that it's destruction
// will cause us to rescan the bus (and pickup the new device).
//
if(deviceMismatch) {
logicalUnit->IsMismatched = TRUE;
}
}
} else {
logicalUnit->IsMissing = FALSE;
if(newDevice) {
status = SpCloneAndSwapLogicalUnit(logicalUnit,
&(inquiryData),
bytesReturned,
&logicalUnit);
if(!NT_SUCCESS(status)) {
logicalUnit = NULL;
}
ASSERT(logicalUnit != RescanLun);
//
// Clear the new device flag so we don't attempt to clear the
// address of the RescanLun down below.
//
newDevice = FALSE;
} else {
//
// Update the state of the device and the device map entry if
// necessary.
//
if(logicalUnit->InquiryData.DeviceTypeQualifier !=
inquiryData.DeviceTypeQualifier) {
logicalUnit->InquiryData.DeviceTypeQualifier =
inquiryData.DeviceTypeQualifier;
SpUpdateLogicalUnitDeviceMapEntry(logicalUnit);
}
}
if(logicalUnit != NULL) {
if(logicalUnit->InquiryData.DeviceTypeQualifier ==
DEVICE_QUALIFIER_NOT_ACTIVE) {
logicalUnit->IsVisible = FALSE;
//
// Scsiport won't create a device-map entry for this device since
// it's never been exposed to PNP (and definately won't be now).
// Create one here. If the init-device routine tries to generate
// one later on down the road it will deal with this case just fine.
//
SpBuildDeviceMapEntry(&(logicalUnit->CommonExtension));
} else {
logicalUnit->IsVisible = TRUE;
}
if(inquiryData.RemovableMedia) {
SET_FLAG(logicalUnit->DeviceObject->Characteristics,
FILE_REMOVABLE_MEDIA);
}
ASSERT(logicalUnit->IsTemporary != TRUE);
}
*LogicalUnit = logicalUnit;
}
//
// If this was a new device then clean up the RescanLun.
//
if(newDevice) {
SpWaitForRemoveLock(RescanLun->DeviceObject, SP_BASE_REMOVE_LOCK);
SpClearLogicalUnitAddress(RescanLun);
}
if(logicalUnit) {
ASSERT(logicalUnit != RescanLun);
SpReleaseRemoveLock(logicalUnit->DeviceObject, SpInquireLogicalUnit);
}
return status;
}
NTSTATUS
SpSendSrbSynchronous(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN PSCSI_REQUEST_BLOCK Srb,
IN OPTIONAL PIRP Irp,
IN OPTIONAL PMDL Mdl,
IN PVOID DataBuffer,
IN ULONG TransferLength,
IN OPTIONAL PVOID SenseInfoBuffer,
IN OPTIONAL UCHAR SenseInfoBufferLength,
OUT PULONG BytesReturned
)
{
KEVENT event;
BOOLEAN irpAllocated = FALSE;
BOOLEAN mdlAllocated = FALSE;
PIO_STACK_LOCATION irpStack;
PSENSE_DATA senseInfo = SenseInfoBuffer;
ULONG retryCount = 0;
NTSTATUS status;
PAGED_CODE();
SendSrbSynchronousRetry:
KeInitializeEvent(&event, NotificationEvent, FALSE);
//
// If the caller provided an IRP we'll use it - if not we allocate one
// here.
//
if(!ARGUMENT_PRESENT(Irp)) {
Irp = SpAllocateIrp(LogicalUnit->DeviceObject->StackSize,
FALSE,
LogicalUnit->DeviceObject->DriverObject);
if(Irp == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
irpAllocated = TRUE;
}
if(ARGUMENT_PRESENT(DataBuffer)) {
ASSERT(TransferLength != 0);
if(!ARGUMENT_PRESENT(Mdl)) {
Mdl = SpAllocateMdl(DataBuffer,
TransferLength,
FALSE,
FALSE,
NULL,
LogicalUnit->DeviceObject->DriverObject);
if(Mdl == NULL) {
IoFreeIrp(Irp);
return STATUS_INSUFFICIENT_RESOURCES;
}
MmBuildMdlForNonPagedPool(Mdl);
}
Irp->MdlAddress = Mdl;
} else {
ASSERT(TransferLength == 0);
ASSERT(!ARGUMENT_PRESENT(Mdl));
}
irpStack = IoGetNextIrpStackLocation(Irp);
//
// Mark the minor function to indicate that this is an internal scsiport
// request and that the start state of the device can be ignored.
//
irpStack->MajorFunction = IRP_MJ_SCSI;
irpStack->MinorFunction = 1;
irpStack->Parameters.Scsi.Srb = Srb;
Srb->SrbStatus = Srb->ScsiStatus = 0;
Srb->OriginalRequest = Irp;
//
// Enable auto request sense.
//
if(ARGUMENT_PRESENT(SenseInfoBuffer)) {
Srb->SenseInfoBuffer = SenseInfoBuffer;
Srb->SenseInfoBufferLength = SenseInfoBufferLength;
} else {
Srb->SenseInfoBuffer = NULL;
Srb->SenseInfoBufferLength = 0;
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DISABLE_AUTOSENSE);
}
if(ARGUMENT_PRESENT(Mdl)) {
Srb->DataBuffer = MmGetMdlVirtualAddress(Mdl);
Srb->DataTransferLength = TransferLength;
} else {
Srb->DataBuffer = NULL;
Srb->DataTransferLength = 0;
}
//
// Call port driver to handle this request.
//
IoSetCompletionRoutine(Irp,
SpSignalCompletion,
&event,
TRUE,
TRUE,
TRUE);
KeEnterCriticalRegion();
status = IoCallDriver(LogicalUnit->DeviceObject, Irp);
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = Irp->IoStatus.Status;
*BytesReturned = (ULONG) Irp->IoStatus.Information;
if(Srb->SrbStatus == SRB_STATUS_PENDING) {
//
// Request was never even issued to the controller.
//
ASSERT(!NT_SUCCESS(status));
} else if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) {
DebugPrint((2,"SpSendSrbSynchronous: Command failed SRB status %x\n",
Srb->SrbStatus));
//
// Unfreeze queue if necessary
//
if (Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
DebugPrint((3, "SpSendSrbSynchronous: Unfreeze Queue TID %d\n",
Srb->TargetId));
LogicalUnit->LuFlags &= ~LU_QUEUE_FROZEN;
GetNextLuRequestWithoutLock(LogicalUnit);
}
//
// NOTE: if INQUIRY fails with a data underrun,
// indicate success and let the class drivers
// determine whether the inquiry information
// is useful.
//
if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_DATA_OVERRUN) {
//
// Copy INQUIRY buffer to LUNINFO.
//
DebugPrint((1,"SpSendSrbSynchronous: Data underrun at TID %d\n",
LogicalUnit->TargetId));
status = STATUS_SUCCESS;
} else if ((Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) &&
(senseInfo->SenseKey == SCSI_SENSE_ILLEGAL_REQUEST)) {
//
// A sense key of illegal request was recieved. This indicates
// that the logical unit number of not valid but there is a
// target device out there.
//
status = STATUS_INVALID_DEVICE_REQUEST;
} else {
//
// If the selection did not time out then retry the request.
//
if ((SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SELECTION_TIMEOUT) &&
(SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_NO_DEVICE) &&
(retryCount++ < INQUIRY_RETRY_COUNT)) {
DebugPrint((2,"SpSendSrbSynchronous: Retry %d\n", retryCount));
KeLeaveCriticalRegion();
goto SendSrbSynchronousRetry;
}
status = SpTranslateScsiStatus(Srb);
}
} else {
status = STATUS_SUCCESS;
}
KeLeaveCriticalRegion();
return status;
}
NTSTATUS
IssueInquiry(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN BOOLEAN EnableVitalProductData,
IN UCHAR PageCode,
OUT PVOID InquiryData,
OUT PUCHAR BytesReturned
)
/*++
Routine Description:
Build IRP, SRB and CDB for SCSI INQUIRY command.
This routine MUST be called while holding the enumeration lock.
Arguments:
LogicalUnit - address of the logical unit extension
EnableVitalProductData - indicates whether the EVPD bit should be set in
the inquiry data causing the LUN to return product
data pages (specified by page code below) rather
than the standard inquiry data.
PageCode - which VPD page to retrieve
InquiryData - the location to store the inquiry data for the LUN.
BytesReturned - the number of bytes of inquiry data returned.
Return Value:
NTSTATUS
--*/
{
PIRP irp;
SCSI_REQUEST_BLOCK srb;
PCDB cdb;
PVOID dataBuffer;
PSENSE_DATA senseInfoBuffer;
UCHAR allocationLength;
ULONG bytesReturned;
NTSTATUS status;
PAGED_CODE();
dataBuffer = LogicalUnit->AdapterExtension->InquiryBuffer;
senseInfoBuffer = LogicalUnit->AdapterExtension->InquirySenseBuffer;
ASSERT(dataBuffer != NULL);
ASSERT(senseInfoBuffer != NULL);
irp = LogicalUnit->AdapterExtension->InquiryIrp;
IoInitializeIrp(irp,
IoSizeOfIrp(INQUIRY_STACK_LOCATIONS),
INQUIRY_STACK_LOCATIONS);
//
// Fill in SRB fields.
//
RtlZeroMemory(dataBuffer, SP_INQUIRY_BUFFER_SIZE);
RtlZeroMemory(senseInfoBuffer, SENSE_BUFFER_SIZE);
RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK));
srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
srb.Length = sizeof(SCSI_REQUEST_BLOCK);
//
// Set flags to disable synchronous negociation.
//
srb.SrbFlags = SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER;
//
// Set timeout to 2 seconds.
//
srb.TimeOutValue = 4;
srb.CdbLength = 6;
cdb = (PCDB)srb.Cdb;
//
// Set CDB operation code.
//
cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
//
// Set allocation length to inquiry data buffer size.
//
if(EnableVitalProductData) {
allocationLength = VPD_MAX_BUFFER_SIZE;
} else {
allocationLength = INQUIRYDATABUFFERSIZE;
}
cdb->CDB6INQUIRY3.AllocationLength = allocationLength;
cdb->CDB6INQUIRY3.EnableVitalProductData = TEST(EnableVitalProductData);
if(EnableVitalProductData == FALSE) {
ASSERT(PageCode == 0);
}
cdb->CDB6INQUIRY3.PageCode = PageCode;
status = SpSendSrbSynchronous(LogicalUnit,
&srb,
irp,
LogicalUnit->AdapterExtension->InquiryMdl,
dataBuffer,
allocationLength,
senseInfoBuffer,
SENSE_BUFFER_SIZE,
&bytesReturned
);
ASSERT(bytesReturned <= allocationLength);
//
// Return the inquiry data for the device if the call was successful.
// Otherwise cleanup.
//
if(NT_SUCCESS(status)) {
//
// If the caller passed in the inquiry buffer then don't bother to copy
// the data.
//
if(InquiryData != dataBuffer) {
RtlCopyMemory(InquiryData, dataBuffer, bytesReturned);
}
*BytesReturned = (UCHAR) bytesReturned;
} else if(BreakOnMissingLun) {
ASSERT(LogicalUnit->IsTemporary == TRUE);
}
return status;
}
BOOLEAN
SpGetDeviceIdentifiers(
IN PLOGICAL_UNIT_EXTENSION LogicalUnit,
IN BOOLEAN NewDevice
)
/*++
Routine Description:
This routine retreives the device identifiers supported by the logical
unit in question and compares them to the ones (if any) which are currently
saved in the LogicalUnit extension. If they do not match this routine
will return false to indicate a device mismatch.
As a side effect this routine will save the serial numbers for new devices
in the logical unit extension, as well as a list of the supported vital
product data pages.
Arguments:
LogicalUnit - the logical unit being prodded.
NewDevice - whether this device has been prodded before or not. If it has
not been then the list of supported EVPD pages will need to be
retreived.
Return Value:
TRUE if the data retrieved matches the data which was stored in the
logical unit extension (TRUE is always returned for a new device).
FALSE otherwise.
--*/
{
PVOID buffer = LogicalUnit->AdapterExtension->InquiryBuffer;
UCHAR bytesReturned;
NTSTATUS status;
PAGED_CODE();
//
// If this is a new device then get the list of supported VPD pages and
// process it.
//
if(NewDevice) {
PVPD_SUPPORTED_PAGES_PAGE supportedPages = buffer;
UCHAR i;
ASSERT(LogicalUnit->DeviceIdentifierPageSupported == FALSE);
ASSERT(LogicalUnit->SerialNumberPageSupported == FALSE);
//
// If this device is a known non-compliant device that does not support
// VPD 0x00 but does support VPDs 0x80 and/or 0x83, bypass the INQUIRY
// and just indicate that the LU does support the other VPDs based on
// the special flags.
//
if (LogicalUnit->SpecialFlags.NonStandardVPD == 0) {
status = IssueInquiry(LogicalUnit,
TRUE,
VPD_SUPPORTED_PAGES,
buffer,
&bytesReturned);
if(!NT_SUCCESS(status)) {
return TRUE;
}
if(bytesReturned < sizeof(VPD_SUPPORTED_PAGES_PAGE)) {
//
// If the device didn't return enough data to include any pages
// then we're done.
//
return TRUE;
}
for(i = 0; i < supportedPages->PageLength; i++) {
switch(supportedPages->SupportedPageList[i]) {
case VPD_SERIAL_NUMBER: {
LogicalUnit->SerialNumberPageSupported = TRUE;
break;
}
case VPD_DEVICE_IDENTIFIERS: {
LogicalUnit->DeviceIdentifierPageSupported = TRUE;
break;
}
default: {
break;
}
}
}
} else {
ULONG vpdFlags = LogicalUnit->SpecialFlags.NonStandardVPD;
//
// This is one of the devices that does not support VPD 0x00 but
// does support one or more of the other VPD pages.
//
LogicalUnit->SerialNumberPageSupported =
(vpdFlags & NON_STANDARD_VPD_SUPPORTS_PAGE80) ? TRUE : FALSE;
LogicalUnit->DeviceIdentifierPageSupported =
(vpdFlags & NON_STANDARD_VPD_SUPPORTS_PAGE83) ? TRUE : FALSE;
}
}
//
// If this device supports the serial number page then retrieve it,
// convert it into an ansi string, and compare it to the one previously
// retreived (if there was a previous attempt).
//
if(LogicalUnit->SerialNumberPageSupported) {
PVPD_SERIAL_NUMBER_PAGE serialNumberPage = buffer;
ANSI_STRING serialNumber;
status = IssueInquiry(LogicalUnit,
TRUE,
VPD_SERIAL_NUMBER,
serialNumberPage,
&bytesReturned);
if(!NT_SUCCESS(status)) {
DebugPrint((0, "SpGetDeviceIdentifiers: Error %#08lx retreiving "
"serial number page from lun %#p\n",
status, LogicalUnit));
//
// We can't get the serial number - give this device the benefit
// of the doubt.
//
return TRUE;
}
//
// Fix for bug #143313:
// On rare occasions, junk appears to get copied into the serial
// number buffer. This causes us problems because the junk is
// interpreted as part of the serial number. When we compare the
// string containing junk to a previously acquired serial number, the
// comparison fails. In an effort to fix, I'll zero out all bytes
// in the buffer following the actual serial number. This will only
// work if the PageSize reported by the device does NOT include the
// junk bytes.
//
RtlZeroMemory(
serialNumberPage->SerialNumber + serialNumberPage->PageLength,
SP_INQUIRY_BUFFER_SIZE - 4 - serialNumberPage->PageLength);
//
// If this is a device known to return binary SN data, convert the
// returned bytes to ascii.
//
// Note: It is assumed that the SN data is numeric. Any bytes that
// cannot be converted to an ASCII hex number, are left alone.
//
if (LogicalUnit->SpecialFlags.BinarySN != 0) {
int i;
PUCHAR p = serialNumberPage->SerialNumber;
for (i = 0; i < serialNumberPage->PageLength; i++) {
if (*p < 0xa) {
*p += '0';
} else if (*p < 0x10) {
*p += 'A';
} else {
ASSERT(FALSE && "Data out of range");
}
p++;
}
}
//
// Create a string using the serial number. The buffer was zeroed
// before transfer (and is one character longer than the max buffer
// which can be returned) so the string is null terminated.
//
RtlInitAnsiString(&(serialNumber), serialNumberPage->SerialNumber);
if(NewDevice) {
//
// A new device will always have a large buffer into which we can
// copy the string. The clone & swap process will take care of
// moving this into a smaller sized buffer.
//
ASSERT(LogicalUnit->SerialNumber.MaximumLength != 0);
ASSERT(LogicalUnit->SerialNumber.Buffer != NULL);
RtlCopyString(&(LogicalUnit->SerialNumber), &serialNumber);
} else if(LogicalUnit->SerialNumber.Buffer == NULL &&
serialNumber.Length != 0) {
//
// ISSUE-2000-25-02-peterwie
// We didn't previously have a serial number. Since the device
// claimed that it supported one it's likely we got an error back
// when we tried to retreive it. Since we didn't get back one
// now it was a transient error (ie. not likely to be a violation
// of the spec). Should we assign the serial number to the device
// here? Or should we have failed to instantiate a device with
// a serial number we couldn't retreive?
//
ASSERT(FALSE);
} else if(RtlEqualString(&serialNumber,
&(LogicalUnit->SerialNumber),
FALSE) == FALSE) {
DebugPrint((1, "SpInquireLogicalUnit: serial number mismatch\n"));
return FALSE;
}
}
//
// If this device supports the device identifiers page then read it out.
// We don't use this page to check for mismatches at the moment, so we
// just read it out of the device if this is a new device.
//
if((NewDevice == TRUE) && (LogicalUnit->DeviceIdentifierPageSupported)) {
status = IssueInquiry(LogicalUnit,
TRUE,
VPD_DEVICE_IDENTIFIERS,
buffer,
&bytesReturned);
if(NT_SUCCESS(status)) {
//
// Copy the page into the buffer allocated in the template logical
// unit. The clone & swap process will take care of moving this
// into an appropriately sized buffer in the new lun.
//
ASSERT(LogicalUnit->DeviceIdentifierPage != NULL);
RtlCopyMemory(LogicalUnit->DeviceIdentifierPage,
buffer,
bytesReturned);
LogicalUnit->DeviceIdentifierPageLength = bytesReturned;
} else {
DebugPrint((1, "SpGetDeviceIdentifiers: Error %#08lx retreiving "
"serial number page from lun %#p\n",
status, LogicalUnit));
LogicalUnit->DeviceIdentifierPageLength = 0;
}
}
return TRUE;
}