windows-nt/Source/XPSP1/NT/drivers/storage/classpnp/utils.c
2020-09-26 16:20:57 +08:00

565 lines
17 KiB
C

/*++
Copyright (C) Microsoft Corporation, 1991 - 1999
Module Name:
utils.c
Abstract:
SCSI class driver routines
Environment:
kernel mode only
Notes:
Revision History:
--*/
#include "classp.h"
#include "debug.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, ClassGetDeviceParameter)
#pragma alloc_text(PAGE, ClassScanForSpecial)
#pragma alloc_text(PAGE, ClassSetDeviceParameter)
#endif
// custom string match -- careful!
BOOLEAN ClasspMyStringMatches(IN PCHAR StringToMatch OPTIONAL, IN PCHAR TargetString)
{
ULONG length; // strlen returns an int, not size_t (!)
PAGED_CODE();
ASSERT(TargetString);
// if no match requested, return TRUE
if (StringToMatch == NULL) {
return TRUE;
}
// cache the string length for efficiency
length = strlen(StringToMatch);
// ZERO-length strings may only match zero-length strings
if (length == 0) {
return (strlen(TargetString) == 0);
}
// strncmp returns zero if the strings match
return (strncmp(StringToMatch, TargetString, length) == 0);
}
VOID ClassGetDeviceParameter(
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
IN PWSTR SubkeyName OPTIONAL,
IN PWSTR ParameterName,
IN OUT PULONG ParameterValue // also default value
)
{
NTSTATUS status;
RTL_QUERY_REGISTRY_TABLE queryTable[2];
HANDLE deviceParameterHandle;
HANDLE deviceSubkeyHandle;
ULONG defaultParameterValue;
PAGED_CODE();
//
// open the given parameter
//
status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
PLUGPLAY_REGKEY_DEVICE,
KEY_READ,
&deviceParameterHandle);
if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
UNICODE_STRING subkeyName;
OBJECT_ATTRIBUTES objectAttributes;
RtlInitUnicodeString(&subkeyName, SubkeyName);
InitializeObjectAttributes(&objectAttributes,
&subkeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
deviceParameterHandle,
NULL);
status = ZwOpenKey(&deviceSubkeyHandle,
KEY_READ,
&objectAttributes);
if (!NT_SUCCESS(status)) {
ZwClose(deviceParameterHandle);
}
}
if (NT_SUCCESS(status)) {
RtlZeroMemory(queryTable, sizeof(queryTable));
defaultParameterValue = *ParameterValue;
queryTable->Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
queryTable->Name = ParameterName;
queryTable->EntryContext = ParameterValue;
queryTable->DefaultType = REG_DWORD;
queryTable->DefaultData = NULL;
queryTable->DefaultLength = 0;
status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
(PWSTR)(SubkeyName ?
deviceSubkeyHandle :
deviceParameterHandle),
queryTable,
NULL,
NULL);
if (!NT_SUCCESS(status)) {
*ParameterValue = defaultParameterValue; // use default value
}
//
// close what we open
//
if (SubkeyName) {
ZwClose(deviceSubkeyHandle);
}
ZwClose(deviceParameterHandle);
}
return;
} // end ClassGetDeviceParameter()
NTSTATUS ClassSetDeviceParameter(
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
IN PWSTR SubkeyName OPTIONAL,
IN PWSTR ParameterName,
IN ULONG ParameterValue)
{
NTSTATUS status;
HANDLE deviceParameterHandle;
HANDLE deviceSubkeyHandle;
PAGED_CODE();
//
// open the given parameter
//
status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
PLUGPLAY_REGKEY_DEVICE,
KEY_READ | KEY_WRITE,
&deviceParameterHandle);
if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
UNICODE_STRING subkeyName;
OBJECT_ATTRIBUTES objectAttributes;
RtlInitUnicodeString(&subkeyName, SubkeyName);
InitializeObjectAttributes(&objectAttributes,
&subkeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
deviceParameterHandle,
NULL);
status = ZwCreateKey(&deviceSubkeyHandle,
KEY_READ | KEY_WRITE,
&objectAttributes,
0, NULL, 0, NULL);
if (!NT_SUCCESS(status)) {
ZwClose(deviceParameterHandle);
}
}
if (NT_SUCCESS(status)) {
status = RtlWriteRegistryValue(
RTL_REGISTRY_HANDLE,
(PWSTR) (SubkeyName ?
deviceSubkeyHandle :
deviceParameterHandle),
ParameterName,
REG_DWORD,
&ParameterValue,
sizeof(ULONG));
//
// close what we open
//
if (SubkeyName) {
ZwClose(deviceSubkeyHandle);
}
ZwClose(deviceParameterHandle);
}
return status;
} // end ClassSetDeviceParameter()
/*
* ClassScanForSpecial
*
* This routine was written to simplify scanning for special
* hardware based upon id strings. it does not check the registry.
*/
VOID ClassScanForSpecial(
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
IN CLASSPNP_SCAN_FOR_SPECIAL_INFO DeviceList[],
IN PCLASS_SCAN_FOR_SPECIAL_HANDLER Function)
{
PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor;
PUCHAR vendorId;
PUCHAR productId;
PUCHAR productRevision;
UCHAR nullString[] = "";
ULONG j;
PAGED_CODE();
ASSERT(DeviceList);
ASSERT(Function);
deviceDescriptor = FdoExtension->DeviceDescriptor;
if (DeviceList == NULL) {
return;
}
if (Function == NULL) {
return;
}
//
// SCSI sets offsets to -1, ATAPI sets to 0. check for both.
//
if (deviceDescriptor->VendorIdOffset != 0 &&
deviceDescriptor->VendorIdOffset != -1) {
vendorId = ((PUCHAR)deviceDescriptor);
vendorId += deviceDescriptor->VendorIdOffset;
} else {
vendorId = nullString;
}
if (deviceDescriptor->ProductIdOffset != 0 &&
deviceDescriptor->ProductIdOffset != -1) {
productId = ((PUCHAR)deviceDescriptor);
productId += deviceDescriptor->ProductIdOffset;
} else {
productId = nullString;
}
if (deviceDescriptor->VendorIdOffset != 0 &&
deviceDescriptor->VendorIdOffset != -1) {
productRevision = ((PUCHAR)deviceDescriptor);
productRevision += deviceDescriptor->ProductRevisionOffset;
} else {
productRevision = nullString;
}
//
// loop while the device list is valid (not null-filled)
//
for (;(DeviceList->VendorId != NULL ||
DeviceList->ProductId != NULL ||
DeviceList->ProductRevision != NULL);DeviceList++) {
if (ClasspMyStringMatches(DeviceList->VendorId, vendorId) &&
ClasspMyStringMatches(DeviceList->ProductId, productId) &&
ClasspMyStringMatches(DeviceList->ProductRevision, productRevision)
) {
DebugPrint((1, "ClasspScanForSpecialByInquiry: Found matching "
"controller Ven: %s Prod: %s Rev: %s\n",
vendorId, productId, productRevision));
//
// pass the context to the call back routine and exit
//
(Function)(FdoExtension, DeviceList->Data);
//
// for CHK builds, try to prevent wierd stacks by having a debug
// print here. it's a hack, but i know of no other way to prevent
// the stack from being wrong.
//
DebugPrint((16, "ClasspScanForSpecialByInquiry: "
"completed callback\n"));
return;
} // else the strings did not match
} // none of the devices matched.
DebugPrint((1, "ClasspScanForSpecialByInquiry: no match found for %p\n",
FdoExtension->DeviceObject));
return;
} // end ClasspScanForSpecialByInquiry()
//
// In order to provide better performance without the need to reboot,
// we need to implement a self-adjusting method to set and clear the
// srb flags based upon current performance.
//
// whenever there is an error, immediately grab the spin lock. the
// MP perf hit here is acceptable, since we're in an error path. this
// is also neccessary because we are guaranteed to be modifying the
// SRB flags here, setting SuccessfulIO to zero, and incrementing the
// actual error count (which is always done within this spinlock).
//
// whenever there is no error, increment a counter. if there have been
// errors on the device, and we've enabled dynamic perf, *and* we've
// just crossed the perf threshhold, then grab the spin lock and
// double check that the threshhold has, indeed been hit(*). then
// decrement the error count, and if it's dropped sufficiently, undo
// some of the safety changes made in the SRB flags due to the errors.
//
// * this works in all cases. even if lots of ios occur after the
// previous guy went in and cleared the successfulio counter, that
// just means that we've hit the threshhold again, and so it's proper
// to run the inner loop again.
//
VOID
ClasspPerfIncrementErrorCount(
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
)
{
PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
KIRQL oldIrql;
ULONG errors;
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
fdoData->Perf.SuccessfulIO = 0; // implicit interlock
errors = InterlockedIncrement(&FdoExtension->ErrorCount);
if (errors >= CLASS_ERROR_LEVEL_1) {
//
// If the error count has exceeded the error limit, then disable
// any tagged queuing, multiple requests per lu queueing
// and sychronous data transfers.
//
// Clearing the no queue freeze flag prevents the port driver
// from sending multiple requests per logical unit.
//
CLEAR_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
CLEAR_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE);
SET_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
DebugPrint((ClassDebugError, "ClasspPerfIncrementErrorCount: "
"Too many errors; disabling tagged queuing and "
"synchronous data tranfers.\n"));
}
if (errors >= CLASS_ERROR_LEVEL_2) {
//
// If a second threshold is reached, disable disconnects.
//
SET_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_DISABLE_DISCONNECT);
DebugPrint((ClassDebugError, "ClasspPerfIncrementErrorCount: "
"Too many errors; disabling disconnects.\n"));
}
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
return;
}
VOID
ClasspPerfIncrementSuccessfulIo(
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
)
{
PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
KIRQL oldIrql;
ULONG errors;
ULONG succeeded = 0;
//
// don't take a hit from the interlocked op unless we're in
// a degraded state and we've got a threshold to hit.
//
if (FdoExtension->ErrorCount == 0) {
return;
}
if (fdoData->Perf.ReEnableThreshhold == 0) {
return;
}
succeeded = InterlockedIncrement(&fdoData->Perf.SuccessfulIO);
if (succeeded < fdoData->Perf.ReEnableThreshhold) {
return;
}
//
// if we hit the threshold, grab the spinlock and verify we've
// actually done so. this allows us to ignore the spinlock 99%
// of the time.
//
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
//
// re-read the value, so we don't run this multiple times
// for a single threshhold being hit. this keeps errorcount
// somewhat useful.
//
succeeded = fdoData->Perf.SuccessfulIO;
if ((FdoExtension->ErrorCount != 0) &&
(fdoData->Perf.ReEnableThreshhold <= succeeded)
) {
fdoData->Perf.SuccessfulIO = 0; // implicit interlock
ASSERT(FdoExtension->ErrorCount > 0);
errors = InterlockedDecrement(&FdoExtension->ErrorCount);
//
// note: do in reverse order of the sets "just in case"
//
if (errors < CLASS_ERROR_LEVEL_2) {
if (errors == CLASS_ERROR_LEVEL_2 - 1) {
DebugPrint((ClassDebugError, "ClasspPerfIncrementSuccessfulIo: "
"Error level 2 no longer required.\n"));
}
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_DISABLE_DISCONNECT)) {
CLEAR_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_DISABLE_DISCONNECT);
}
}
if (errors < CLASS_ERROR_LEVEL_1) {
if (errors == CLASS_ERROR_LEVEL_1 - 1) {
DebugPrint((ClassDebugError, "ClasspPerfIncrementSuccessfulIo: "
"Error level 1 no longer required.\n"));
}
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_DISABLE_SYNCH_TRANSFER)) {
CLEAR_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
}
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_QUEUE_ACTION_ENABLE)) {
SET_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_QUEUE_ACTION_ENABLE);
}
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_NO_QUEUE_FREEZE)) {
SET_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_NO_QUEUE_FREEZE);
}
}
} // end of threshhold definitely being hit for first time
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
return;
}
PMDL BuildDeviceInputMdl(PVOID Buffer, ULONG BufferLen)
{
PMDL mdl;
mdl = IoAllocateMdl(Buffer, BufferLen, FALSE, FALSE, NULL);
if (mdl){
try {
/*
* We are reading from the device.
* Therefore, the device is WRITING to the locked memory.
* So we request IoWriteAccess.
*/
MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess);
} except(EXCEPTION_EXECUTE_HANDLER) {
NTSTATUS status = GetExceptionCode();
DBGWARN(("BuildReadMdl: MmProbeAndLockPages failed with %xh.", status));
IoFreeMdl(mdl);
mdl = NULL;
}
}
else {
DBGWARN(("BuildReadMdl: IoAllocateMdl failed"));
}
return mdl;
}
VOID FreeDeviceInputMdl(PMDL Mdl)
{
MmUnlockPages(Mdl);
IoFreeMdl(Mdl);
}
#if 0
VOID
ClasspPerfResetCounters(
IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
)
{
PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
KIRQL oldIrql;
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
DebugPrint((ClassDebugError, "ClasspPerfResetCounters: "
"Resetting all perf counters.\n"));
fdoData->Perf.SuccessfulIO = 0;
FdoExtension->ErrorCount = 0;
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_DISABLE_DISCONNECT)) {
CLEAR_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_DISABLE_DISCONNECT);
}
if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_DISABLE_SYNCH_TRANSFER)) {
CLEAR_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
}
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_QUEUE_ACTION_ENABLE)) {
SET_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_QUEUE_ACTION_ENABLE);
}
if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
SRB_FLAGS_NO_QUEUE_FREEZE)) {
SET_FLAG(FdoExtension->SrbFlags,
SRB_FLAGS_NO_QUEUE_FREEZE);
}
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
return;
}
#endif