/*++ 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] = {0}; 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 = {0}; 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)) { 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); } if (!NT_SUCCESS(status)) { // // Windows 2000 SP3 uses the driver-specific key, so look in there // status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo, PLUGPLAY_REGKEY_DRIVER, KEY_READ, &deviceParameterHandle); if (NT_SUCCESS(status) && (SubkeyName != NULL)) { UNICODE_STRING subkeyName; OBJECT_ATTRIBUTES objectAttributes = {0}; 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)) { 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)) { // // Migrate the value over to the device-specific key // ClassSetDeviceParameter(FdoExtension, SubkeyName, ParameterName, *ParameterValue); } else { // // Use the default value // *ParameterValue = defaultParameterValue; } 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->ProductRevisionOffset != 0 && deviceDescriptor->ProductRevisionOffset != -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