565 lines
17 KiB
C
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
|
||
|
|