summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2018-05-12 22:34:53 +0800
committerruki <[email protected]>2018-05-12 22:34:53 +0800
commit4fd5668b925ee315389808673539dd8e430f35b2 (patch)
tree5b5aa7b870eef0d5808dfcb9b7f17a9468e9d8c2
parent0d8e537bc562c39695de25371ba6e8a0adeac5dc (diff)
add mof rule
-rw-r--r--tests/projects/wdk/kmdf/msdsm/SampleDSM.infbin0 -> 5354 bytes
-rw-r--r--tests/projects/wdk/kmdf/msdsm/dsmmain.c9752
-rw-r--r--tests/projects/wdk/kmdf/msdsm/dsmtrace.mof111
-rw-r--r--tests/projects/wdk/kmdf/msdsm/intrface.c5198
-rw-r--r--tests/projects/wdk/kmdf/msdsm/msdsm.h1403
-rw-r--r--tests/projects/wdk/kmdf/msdsm/msdsm.mof82
-rw-r--r--tests/projects/wdk/kmdf/msdsm/msdsm.rc24
-rw-r--r--tests/projects/wdk/kmdf/msdsm/msdsmdsm.mof141
-rw-r--r--tests/projects/wdk/kmdf/msdsm/precomp.h34
-rw-r--r--tests/projects/wdk/kmdf/msdsm/precompsrc.c1
-rw-r--r--tests/projects/wdk/kmdf/msdsm/prototypes.h1436
-rw-r--r--tests/projects/wdk/kmdf/msdsm/trace.h35
-rw-r--r--tests/projects/wdk/kmdf/msdsm/utils.c7946
-rw-r--r--tests/projects/wdk/kmdf/msdsm/wmi.c3822
-rw-r--r--tests/projects/wdk/kmdf/msdsm/xmake.lua14
-rw-r--r--tests/projects/wdk/kmdf/serial/xmake.lua3
-rw-r--r--xmake/rules/wdk/mof/xmake.lua108
-rw-r--r--xmake/rules/wdk/xmake.lua8
18 files changed, 30111 insertions, 7 deletions
diff --git a/tests/projects/wdk/kmdf/msdsm/SampleDSM.inf b/tests/projects/wdk/kmdf/msdsm/SampleDSM.inf
new file mode 100644
index 000000000..f632f6e50
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/SampleDSM.inf
Binary files differ
diff --git a/tests/projects/wdk/kmdf/msdsm/dsmmain.c b/tests/projects/wdk/kmdf/msdsm/dsmmain.c
new file mode 100644
index 000000000..bdf875184
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/dsmmain.c
@@ -0,0 +1,9752 @@
+/*++
+
+Copyright (C) 2004-2010 Microsoft Corporation
+
+Module Name:
+
+ dsmmain.c
+
+Abstract:
+
+ This driver is the Microsoft Device Specific Module (DSM).
+ It exports behaviours that mpio.sys will use to determine how to
+ multipath SPC-3 conforming devices.
+
+ This file contains routines that are internal to MSDSM.
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+--*/
+
+#include "precomp.h"
+
+#ifdef DEBUG_USE_WPP
+#include "dsmmain.tmh"
+#endif
+
+#pragma warning (disable:4305)
+
+extern BOOLEAN DoAssert;
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(PAGE, DsmpRegisterPersistentReservationKeys)
+#endif
+
+VOID
+DsmpFreeDSMResources(
+ _In_ IN PDSM_CONTEXT DsmContext
+ )
+/*++
+
+Routine Description:
+
+ This routine will free the resources allocated by the DSM. This routine
+ should be called when the DSM is being unloaded.
+
+Arguements:
+
+ DsmContext - DSM context given to MPIO during initialization
+
+Return Value:
+
+ None
+--*/
+{
+ PDSM_WMILIB_CONTEXT wmiInfo;
+ PVOID tempAddress = (PVOID)DsmContext;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmpFreeDSMResources (DsmCtxt %p): Entering function.\n",
+ DsmContext));
+
+ //
+ // First free the buffer allocated for storing the registry path.
+ //
+ wmiInfo = &gDsmInitData.DsmWmiInfo;
+
+ if (wmiInfo->RegistryPath.Buffer) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_INIT,
+ "DsmpFreeDSMResources (DsmCtxt %p): Freeing wmiInfo's registry buffer.\n",
+ DsmContext));
+
+ DsmpFreePool(wmiInfo->RegistryPath.Buffer);
+ }
+
+ if (DsmContext) {
+ PLIST_ENTRY entry;
+ PDSM_DEVICE_INFO deviceInfo;
+ PDSM_GROUP_ENTRY groupEntry;
+ PDSM_FAILOVER_GROUP failGroup;
+ PDSM_CONTROLLER_LIST_ENTRY controllerEntry;
+
+ ExDeleteNPagedLookasideList(&(DsmContext->CompletionContextList));
+
+ //
+ // Free up the devices (DeviceInfo) list.
+ //
+ while (!IsListEmpty(&DsmContext->DeviceList)) {
+
+ entry = DsmContext->DeviceList.Flink;
+
+ NT_ASSERT(entry);
+
+ deviceInfo = CONTAINING_RECORD(entry, DSM_DEVICE_INFO, ListEntry);
+
+ if (deviceInfo) {
+
+ DsmpRemoveDeviceFailGroup(DsmContext, deviceInfo->FailGroup, deviceInfo, TRUE);
+ DsmpRemoveDeviceEntry(DsmContext, deviceInfo->Group, deviceInfo);
+ }
+ }
+
+ NT_ASSERT(!DsmContext->NumberDevices &&
+ !DsmContext->NumberFOGroups &&
+ !DsmContext->NumberGroups);
+
+ //
+ // By now, there should be no group entries left but play it safe and
+ // free up the GROUP list.
+ //
+ while (!IsListEmpty(&DsmContext->GroupList)) {
+
+ entry = DsmContext->GroupList.Flink;
+
+ NT_ASSERT(entry);
+
+ groupEntry = CONTAINING_RECORD(entry, DSM_GROUP_ENTRY, ListEntry);
+
+ if (groupEntry) {
+
+ DsmpRemoveGroupEntry(DsmContext, groupEntry, TRUE);
+
+ DsmpFreePool(groupEntry);
+ }
+ }
+
+ //
+ // By now there should be no FOG entries left but we play it safe and
+ // free up the FOG list.
+ //
+ while (!IsListEmpty(&DsmContext->FailGroupList)) {
+
+ entry = RemoveHeadList(&DsmContext->FailGroupList);
+
+ if (entry) {
+
+ failGroup = CONTAINING_RECORD(entry, DSM_FAILOVER_GROUP, ListEntry);
+
+ if (failGroup) {
+
+ PDSM_FOG_DEVICELIST_ENTRY fogDeviceListEntry = NULL;
+ PLIST_ENTRY deviceEntry = NULL;
+
+ while (!IsListEmpty(&failGroup->FOG_DeviceList)) {
+
+ deviceEntry = RemoveHeadList(&failGroup->FOG_DeviceList);
+
+ if (deviceEntry) {
+
+ fogDeviceListEntry = CONTAINING_RECORD(deviceEntry, DSM_FOG_DEVICELIST_ENTRY, ListEntry);
+
+ if (!fogDeviceListEntry) {
+ continue;
+ }
+
+ (fogDeviceListEntry->DeviceInfo)->FailGroup = NULL;
+
+ DsmpFreePool(fogDeviceListEntry);
+ InterlockedDecrement((LONG volatile*)&failGroup->Count);
+ }
+ }
+
+ DsmpFreeZombieGroupList(failGroup);
+ DsmpFreePool(failGroup);
+ InterlockedDecrement((LONG volatile*)&DsmContext->NumberFOGroups);
+ }
+ }
+ }
+
+ //
+ // Free up the controller list.
+ //
+ while (!IsListEmpty(&DsmContext->ControllerList)) {
+
+ entry = RemoveHeadList(&DsmContext->ControllerList);
+
+ if (entry) {
+
+ controllerEntry = CONTAINING_RECORD(entry, DSM_CONTROLLER_LIST_ENTRY, ListEntry);
+
+ if (controllerEntry) {
+
+ DsmpFreeControllerEntry(DsmContext, controllerEntry);
+
+ InterlockedDecrement((LONG volatile*)&DsmContext->NumberControllers);
+ }
+ }
+ }
+
+ NT_ASSERT(!DsmContext->NumberControllers);
+
+ //
+ // Free up the stale FOG list.
+ //
+ while (!IsListEmpty(&DsmContext->StaleFailGroupList)) {
+
+ entry = RemoveHeadList(&DsmContext->StaleFailGroupList);
+
+ if (entry) {
+
+ failGroup = CONTAINING_RECORD(entry, DSM_FAILOVER_GROUP, ListEntry);
+
+ if (failGroup) {
+
+ InterlockedDecrement((LONG volatile*)&DsmContext->NumberStaleFOGroups);
+ NT_ASSERT(IsListEmpty(&failGroup->FOG_DeviceList));
+ DsmpFreeZombieGroupList(failGroup);
+ DsmpFreePool(failGroup);
+ }
+ }
+ }
+
+ //
+ // Free up the supported devices list buffer.
+ //
+ DsmpFreePool(DsmContext->SupportedDevices.Buffer);
+
+ //
+ // It's the responsibility of the mpio bus driver to have already
+ // destroyed all devices and paths. As those functions free allocations
+ // for the objects, the only thing needed here is to free the DsmContext.
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_INIT,
+ "DsmpFreeDSMResources (DsmCtxt %p): Freeing the DsmContext.\n",
+ DsmContext));
+
+ DsmpFreePool(DsmContext);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmpFreeDSMResources (DsmCtxt %p): Exiting function.\n",
+ tempAddress));
+
+ return;
+}
+
+
+PDSM_GROUP_ENTRY
+DsmpFindDevice(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN BOOLEAN AcquireDSMLockExclusive
+ )
+/*++
+
+Routine Description:
+
+ This routine searches for a serial number match between DeviceInfo and
+ the rest of the devices currently being driven by this DSM.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ DeviceInfo - The deviceInfo containing serial number for which to search.
+ AcquireDSMLockExclusive - If TRUE this routine should acquire DsmContextLock Exclusively
+
+Return Value:
+
+ The multi-path group entry in which the device resides.
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo;
+ PLIST_ENTRY entry;
+ PDSM_GROUP_ENTRY groupEntry = NULL;
+ ULONG i;
+ KIRQL irql = PASSIVE_LEVEL; // Initialize variable to prevent C4701 error
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFindDevice (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ if (AcquireDSMLockExclusive) {
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ }
+
+ //
+ // Run through the DeviceInfo List
+ //
+ entry = DsmContext->DeviceList.Flink;
+ for (i = 0; i < DsmContext->NumberDevices; i++, entry = entry->Flink) {
+
+ //
+ // Extract the deviceInfo structure.
+ //
+ deviceInfo = CONTAINING_RECORD(entry, DSM_DEVICE_INFO, ListEntry);
+ DSM_ASSERT(deviceInfo);
+
+ if (deviceInfo) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpFindDevice (DevInfo %p): Comparing with %p.\n",
+ DeviceInfo,
+ deviceInfo));
+
+ //
+ // Call the Serial Number compare routine.
+ //
+ if (DsmCompareDevices(DsmContext,
+ DeviceInfo,
+ deviceInfo)) {
+
+ groupEntry = deviceInfo->Group;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpFindDevice (DevInfo %p): Found matching multi-path group %p.\n",
+ DeviceInfo,
+ groupEntry));
+
+ break;
+ }
+ }
+ }
+
+ if (AcquireDSMLockExclusive) {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFindDevice (DevInfo %p): Exiting function with groupEntry %p.\n",
+ DeviceInfo,
+ groupEntry));
+
+ return groupEntry;
+}
+
+
+PDSM_GROUP_ENTRY
+DsmpBuildGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ )
+/*++
+
+Routine Description:
+
+ This will allocate and partially initialise a multi-path group entry.
+
+ N.B: This routine must be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ DeviceInfo - The first device to be added to the group.
+
+Return Value:
+
+ The new group entry.
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildGroupEntry (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ //
+ // Allocate the memory for the multi-path group.
+ //
+ group = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_GROUP_ENTRY),
+ DSM_TAG_GROUP_ENTRY);
+
+ if (group) {
+
+ InitializeListHead(&group->FailingDevInfoList);
+ group->GroupNumber = InterlockedIncrement((LONG volatile*)&DsmContext->NumberGroups);
+ group->GroupSig = DSM_GROUP_SIG;
+ group->State = DSM_GP_NORMAL;
+
+ //
+ // Add it to the list of multi-path groups.
+ //
+ InsertTailList(&DsmContext->GroupList, &group->ListEntry);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildGroupEntry (DevInfo %p): Failed to allocate memory for the group.\n",
+ DeviceInfo));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildGroupEntry (DevInfo %p): Exiting function with group %p.\n",
+ DeviceInfo,
+ group));
+
+ return group;
+}
+
+
+NTSTATUS
+DsmpParseTargetPortGroupsInformation(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_reads_bytes_(TargetPortGroupsInfoLength) IN PUCHAR TargetPortGroupsInfo,
+ _In_ IN ULONG TargetPortGroupsInfoLength
+ )
+/*++
+
+Routine Description:
+
+ This will parse the information returned back from a previously
+ made call to ReportTargetPortGroups and build new TPG entries or
+ update old ones.
+
+ N.B: This routine must be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DsmContext
+ Group - group entry
+ TargetPortGroupsInfo - Pointer to the ReportTPG returned buffer.
+ TargetPortGroupsInfoLength - length of the buffer.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate error code.
+
+--*/
+{
+ PUCHAR targetPortGroupsInfoIndex;
+ ULONG bytes = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroupEntry = NULL;
+ ULONG descriptorSize = 0;
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG index;
+ DSM_DEVICE_STATE tpgState = DSM_DEV_NOT_USED_STATE;
+ ULONG bytesLeft;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpParseTargetPortGroupsInformation (Group %p): Entering function.\n",
+ Group));
+
+ targetPortGroupsInfoIndex = TargetPortGroupsInfo + bytes;
+ bytesLeft = TargetPortGroupsInfoLength - bytes;
+
+ while (bytes < TargetPortGroupsInfoLength && NT_SUCCESS(status)) {
+
+ targetPortGroupEntry = DsmpFindTargetPortGroupEntry(DsmContext,
+ Group,
+ targetPortGroupsInfoIndex,
+ bytesLeft);
+
+ if (targetPortGroupEntry) {
+
+ targetPortGroupEntry = DsmpUpdateTargetPortGroupEntry(DsmContext,
+ targetPortGroupEntry,
+ targetPortGroupsInfoIndex,
+ bytesLeft,
+ &descriptorSize);
+ } else {
+
+ targetPortGroupEntry = DsmpBuildTargetPortGroupEntry(DsmContext,
+ Group,
+ targetPortGroupsInfoIndex,
+ bytesLeft,
+ &descriptorSize);
+
+ if (targetPortGroupEntry) {
+
+ //
+ // Insert this TPG entry into array
+ //
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ if (!Group->TargetPortGroupList[index]) {
+
+ Group->TargetPortGroupList[index] = targetPortGroupEntry;
+ InterlockedIncrement((LONG volatile*)&Group->NumberTargetPortGroups);
+ targetPortGroupEntry->Group = Group;
+ break;
+ }
+ }
+
+ if (index == DSM_MAX_PATHS) {
+
+ NT_ASSERT(index < DSM_MAX_PATHS);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpParseTargetPortGroupsInformation (Group %p): Number of paths exceeded max supported.\n",
+ Group));
+
+ status = STATUS_UNSUCCESSFUL;
+ goto __Exit_DsmpParseTargetPortGroupsInformation;
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpParseTargetPortGroupsInformation (Group %p): Insufficient resources to build TPG.\n",
+ Group));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // If this is the first TPG being parsed, save off its AA state.
+ //
+ if (tpgState == DSM_DEV_NOT_USED_STATE) {
+
+ tpgState = targetPortGroupEntry->AsymmetricAccessState;
+
+ } else {
+
+ //
+ // Check if this TPG's AA state differs from the previous one's.
+ // Symmetric LU access means that TPG access states must be the
+ // same for TPGs. If this one is different, we know that the
+ // device supports Asymmetric LU access.
+ //
+ if (tpgState != targetPortGroupEntry->AsymmetricAccessState) {
+
+ Group->Symmetric = FALSE;
+ }
+ }
+ }
+
+ if (targetPortGroupEntry) {
+
+ //
+ // Set the flag to indicate that we've encountered this TPG in the RTPG information.
+ //
+ targetPortGroupEntry->Traversed = TRUE;
+ }
+
+ bytes += descriptorSize;
+ targetPortGroupsInfoIndex += descriptorSize;
+ bytesLeft -= descriptorSize;
+ }
+
+ //
+ // Since we've gone through the entire information reported by back RTPG, it
+ // is now time to delete the stale entries.
+ //
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ targetPortGroupEntry = Group->TargetPortGroupList[index];
+
+ if (targetPortGroupEntry) {
+
+ if (targetPortGroupEntry->Traversed) {
+
+ //
+ // Entry needs to continue to exist. Reset the flag and continue.
+ //
+ targetPortGroupEntry->Traversed = FALSE;
+ continue;
+
+ } else {
+
+ PLIST_ENTRY entry;
+ PLIST_ENTRY tempEntry;
+ PDSM_TARGET_PORT_LIST_ENTRY targetPort;
+
+ //
+ // For this target port group, clean up all its target ports if
+ // the port doesn't expose any instance of this device.
+ //
+ for (entry = targetPortGroupEntry->TargetPortList.Flink;
+ entry != NULL && entry != &targetPortGroupEntry->TargetPortList;
+ entry = entry->Flink) {
+
+ targetPort = CONTAINING_RECORD(entry, DSM_TARGET_PORT_LIST_ENTRY, ListEntry);
+
+ if (targetPort) {
+
+ //
+ // If the TP doesn't expose this device, it is safe
+ // to delete it.
+ //
+ if (IsListEmpty(&targetPort->TP_DeviceList)) {
+
+ tempEntry = entry;
+ entry = entry->Blink;
+
+ RemoveEntryList(tempEntry);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpParseTargetPortGroupsInformation (Group %p): Deleting empty target port %p from TPG %p list.\n",
+ Group,
+ targetPort,
+ targetPortGroupEntry));
+
+ DsmpFreePool(targetPort);
+
+ InterlockedDecrement((LONG volatile*)&targetPortGroupEntry->NumberTargetPorts);
+ }
+ }
+ }
+
+ //
+ // If the TPG doesn't have any TPs, it is safe to delete it.
+ //
+ if (!targetPortGroupEntry->NumberTargetPorts) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpParseTargetPortGroupsInformation (Group %p): Deleting target port group %p.\n",
+ Group,
+ targetPortGroupEntry));
+
+ DsmpFreePool(targetPortGroupEntry);
+
+ InterlockedDecrement((LONG volatile*)&Group->NumberTargetPortGroups);
+
+ Group->TargetPortGroupList[index] = NULL;
+ }
+ }
+ }
+ }
+
+__Exit_DsmpParseTargetPortGroupsInformation:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpParseTargetPortGroupsInformation (Group %p): Exiting function with status %x\n",
+ Group,
+ status));
+
+ return status;
+}
+
+
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpFindTargetPortGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_reads_bytes_(TPGs_BufferLength) IN PUCHAR TargetPortGroupsDescriptor,
+ _In_ IN ULONG TPGs_BufferLength
+ )
+/*++
+
+Routine Description:
+
+ This will search the group's TPG array to look for an identifier match.
+
+ N.B: This routine must be called with DsmContextLock held in either Shared
+ or Exclusive mode.
+
+Arguments:
+
+ DsmContext - DsmContext
+ Group - group entry
+ TargetPortGroupsDescriptor - Pointer to the TPG descriptor.
+ TPGs_BufferLength - Length of the passed in TargetPortGroupsDescriptor buffer.
+
+Return Value:
+
+ Pointer to the array element that matches, else NULL.
+
+--*/
+{
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup = NULL;
+ PSPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR descriptor = (PSPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR)TargetPortGroupsDescriptor;
+ ULONG index;
+ BOOLEAN found = FALSE;
+ USHORT identifier = ((descriptor->TPG_Identifier & 0x00FF) << 8) | ((descriptor->TPG_Identifier & 0xFF00) >> 8);
+
+ UNREFERENCED_PARAMETER(TPGs_BufferLength);
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPortGroupEntry (Group %p): Entering function.\n",
+ Group));
+
+ for (index = 0; index < DSM_MAX_PATHS && !found; index++) {
+
+ targetPortGroup = Group->TargetPortGroupList[index];
+
+ if (targetPortGroup) {
+
+ if (targetPortGroup->Identifier == identifier) {
+
+ found = TRUE;
+ }
+ }
+ }
+
+ if (!found) {
+ targetPortGroup = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPortGroupEntry (Group %p): Exiting function with targetPortGroup %p.\n",
+ Group,
+ targetPortGroup));
+
+ return targetPortGroup;
+}
+
+_Success_(return!=0)
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpUpdateTargetPortGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_reads_bytes_(TPGs_BufferLength) IN PUCHAR TargetPortGroupsDescriptor,
+ _In_ IN ULONG TPGs_BufferLength,
+ _Out_ OUT PULONG DescriptorSize
+ )
+/*++
+
+Routine Description:
+
+ This routine will update the target port group with information contained
+ in the passed in descriptor.
+
+ N.B: This routine must be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DsmContext
+ TargetPortGroup - Pointer to the TPG entry to update.
+ TargetPortGroupsDescriptor - Pointer to the TPG descriptor.
+ TPGs_BufferLength - Length of the passed in TargetPortGroupsDescriptor buffer.
+ DescriptorSize - return value of the size of the descriptor.
+
+Return Value:
+
+ The updated target port group entry on success, NULL in case of failure.
+
+--*/
+{
+ PLIST_ENTRY entry;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup = TargetPortGroup;
+ PSPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR descriptor = (PSPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR)TargetPortGroupsDescriptor;
+ ULONG numberTargetPorts = 0;
+ PULONG descriptorIndex;
+ ULONG index;
+ PDSM_TARGET_PORT_LIST_ENTRY listEntry;
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG identifier;
+ PLIST_ENTRY tempEntry = NULL;
+ ULONG delCount;
+ PUCHAR endOfBuffer = TargetPortGroupsDescriptor + TPGs_BufferLength - 1;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupEntry (TPG %p): Entering function.\n",
+ TargetPortGroup));
+
+ if (DescriptorSize == NULL) {
+ status = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupEntry (TPG %p): Status %x due to null passed in DescriptorSize pointer\n",
+ TargetPortGroup,
+ status));
+
+ goto __Exit_DsmpUpdateTargetPortGroupEntry;
+ }
+
+ *DescriptorSize = sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) +
+ (targetPortGroup->NumberTargetPorts * sizeof(ULONG));
+
+ if (((PUCHAR)descriptor + sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) - 1) > endOfBuffer) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupEntry (TPG %p): Status %x due to incorrect passed in TPG buffer size (%u).\n",
+ TargetPortGroup,
+ status,
+ TPGs_BufferLength));
+
+ goto __Exit_DsmpUpdateTargetPortGroupEntry;
+ }
+
+ identifier = ((descriptor->TPG_Identifier & 0x00FF) << 8) | ((descriptor->TPG_Identifier & 0xFF00) >> 8);
+ NT_ASSERT(targetPortGroup->Identifier == (USHORT)identifier);
+ NT_ASSERT(targetPortGroup->ActiveOptimizedSupported == (descriptor->ActiveOptimizedSupported) ? TRUE : FALSE);
+ NT_ASSERT(targetPortGroup->ActiveUnoptimizedSupported == (descriptor->ActiveUnoptimizedSupported) ? TRUE : FALSE);
+ NT_ASSERT(targetPortGroup->StandBySupported == (descriptor->StandbySupported) ? TRUE : FALSE);
+ NT_ASSERT(targetPortGroup->UnavailableSupported == (descriptor->UnavailableSupported) ? TRUE : FALSE);
+ NT_ASSERT(targetPortGroup->TransitioningSupported == (descriptor->TransitioningSupported) ? TRUE : FALSE);
+ DSM_ASSERT(targetPortGroup->VendorUnique == descriptor->VendorUnique);
+
+ //
+ // It is possible that the asymmetric access state, status code and number of port
+ // may have changed
+ //
+ if ((targetPortGroup->AsymmetricAccessState) != (descriptor->AsymmetricAccessState & 0xF))
+ {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupEntry (TPG %p): Asymmetric access state has changed.\n",
+ TargetPortGroup));
+
+
+ targetPortGroup->AsymmetricAccessState = descriptor->AsymmetricAccessState & 0xF;
+ }
+
+ targetPortGroup->Preferred = (descriptor->Preferred) ? TRUE : FALSE;
+
+ targetPortGroup->StatusCode = descriptor->StatusCode;
+
+ numberTargetPorts = descriptor->NumberTargetPorts;
+
+ NT_ASSERT(numberTargetPorts > 0);
+
+ //
+ // Point to first target port identifier
+ //
+ descriptorIndex = descriptor->TargetPortIds;
+
+ for (index = 0; index < numberTargetPorts && NT_SUCCESS(status); index++) {
+
+ if (((PUCHAR)descriptorIndex + ((index + 1) * sizeof(ULONG)) - 1) > endOfBuffer) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupEntry (TPG %p): Status %x due to incorrect TPG buffer size (%u) passed in.\n",
+ TargetPortGroup,
+ status,
+ TPGs_BufferLength));
+
+ goto __Exit_DsmpUpdateTargetPortGroupEntry;
+ }
+
+ GetUlongFrom4ByteArray((PUCHAR)(&descriptorIndex[index]), identifier);
+
+ listEntry = DsmpFindTargetPortListEntry(DsmContext,
+ targetPortGroup,
+ identifier);
+
+ if (listEntry) {
+
+ RemoveEntryList(&listEntry->ListEntry);
+ InsertHeadList(&targetPortGroup->TargetPortList, &listEntry->ListEntry);
+
+ } else {
+
+ listEntry = DsmpBuildTargetPortListEntry(DsmContext,
+ targetPortGroup,
+ identifier);
+
+ if (listEntry) {
+
+ InsertHeadList(&targetPortGroup->TargetPortList, &listEntry->ListEntry);
+ InterlockedIncrement((LONG volatile*)&targetPortGroup->NumberTargetPorts);
+
+ } else {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupEntry (TPG %p): Failed to allocate TargetPort (identifier %x).\n",
+ TargetPortGroup,
+ identifier));
+ }
+ }
+ }
+
+ //
+ // Ignore the status & carry on. Even if we weren't able to build TP entries
+ // for the new target ports, we are no worse off than before.
+ //
+ DSM_ASSERT(NT_SUCCESS(status));
+
+ *DescriptorSize = sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) +
+ (numberTargetPorts * sizeof(ULONG));
+
+ for (index = 0, entry = targetPortGroup->TargetPortList.Flink;
+ index < numberTargetPorts;
+ index++, entry = entry->Flink);
+
+ delCount = targetPortGroup->NumberTargetPorts - numberTargetPorts;
+
+ for (index = 0; index < delCount; index++) {
+
+ tempEntry = entry;
+ entry = entry->Flink;
+
+ RemoveEntryList(tempEntry);
+ InterlockedDecrement((LONG volatile*)&targetPortGroup->NumberTargetPorts);
+
+ listEntry = CONTAINING_RECORD(tempEntry, DSM_TARGET_PORT_LIST_ENTRY, ListEntry);
+ NT_ASSERT(listEntry);
+
+ if (listEntry) {
+
+ PLIST_ENTRY deviceEntry;
+ PDSM_TARGET_PORT_DEVICELIST_ENTRY tp_device;
+
+ while (!IsListEmpty(&listEntry->TP_DeviceList)) {
+
+ deviceEntry = RemoveHeadList(&listEntry->TP_DeviceList);
+ InterlockedDecrement((LONG volatile*)&listEntry->Count);
+
+ if (deviceEntry) {
+
+ tp_device = CONTAINING_RECORD(deviceEntry, DSM_TARGET_PORT_DEVICELIST_ENTRY, ListEntry);
+
+ if (tp_device) {
+
+ if (tp_device->DeviceInfo) {
+
+ tp_device->DeviceInfo->TargetPort = NULL;
+ }
+
+ DsmpFreePool(tp_device);
+ }
+ }
+ }
+
+ DsmpFreePool(listEntry);
+ }
+ }
+
+__Exit_DsmpUpdateTargetPortGroupEntry:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupEntry (TPG %p): Exiting function.\n",
+ targetPortGroup));
+
+ return targetPortGroup;
+}
+
+
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpBuildTargetPortGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_reads_bytes_(TPGs_BufferLength) IN PUCHAR TargetPortGroupsDescriptor,
+ _In_ IN ULONG TPGs_BufferLength,
+ _Out_ OUT PULONG DescriptorSize
+ )
+/*++
+
+Routine Description:
+
+ This will allocate and partially initialise a target port group entry.
+
+ N.B: This routine must be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DsmContext
+ Group - The group that this newly going to be built TPG belongs to.
+ TargetPortGroupsDescriptor - Pointer to the TPG descriptor.
+ TPGs_BufferLength - Length of the passed in TargetPortGroupsDescriptor buffer.
+ DescriptorSize - return value of the size of the descriptor.
+
+Return Value:
+
+ The new target port group entry.
+
+--*/
+{
+ PDSM_TARGET_PORT_GROUP_ENTRY entry = NULL;
+ PSPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR descriptor = (PSPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR)TargetPortGroupsDescriptor;
+ ULONG numberTargetPorts = 0;
+ PULONG descriptorIndex;
+ ULONG index = 0;
+ PDSM_TARGET_PORT_LIST_ENTRY listEntry;
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG identifier;
+ PUCHAR endOfBuffer = TargetPortGroupsDescriptor + TPGs_BufferLength - 1;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Entering function.\n",
+ Group));
+
+ if (DescriptorSize == NULL) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Status %x due to null passed in DescriptorSize pointer\n",
+ Group,
+ status));
+
+ goto __Exit_DsmpBuildTargetPortGroupEntry;
+ }
+
+ *DescriptorSize = 0;
+
+ if (((PUCHAR)descriptor + sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) - 1) > endOfBuffer) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Status %x due to incorrect passed in TPG buffer size (%u).\n",
+ Group,
+ status,
+ TPGs_BufferLength));
+
+ goto __Exit_DsmpBuildTargetPortGroupEntry;
+ }
+
+ //
+ // Allocate the memory for the multi-path group.
+ //
+ entry = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_TARGET_PORT_GROUP_ENTRY),
+ DSM_TAG_TARGET_PORT_GROUP_ENTRY);
+
+ if (entry) {
+
+ entry->TargetPortGroupSig = DSM_TARGET_PORT_GROUP_SIG;
+
+ //
+ // Target Port Group's access state
+ //
+ entry->AsymmetricAccessState = descriptor->AsymmetricAccessState & 0xF;
+
+ //
+ // Target Port Group's supported states
+ //
+ entry->ActiveOptimizedSupported = (descriptor->ActiveOptimizedSupported) ? TRUE : FALSE;
+ entry->ActiveUnoptimizedSupported = (descriptor->ActiveUnoptimizedSupported) ? TRUE : FALSE;
+ entry->StandBySupported = (descriptor->StandbySupported) ? TRUE : FALSE;
+ entry->UnavailableSupported = (descriptor->UnavailableSupported) ? TRUE : FALSE;
+
+ //
+ // Target Port Group's Preference and support for reporting transitioning
+ //
+ entry->Preferred = (descriptor->Preferred) ? TRUE : FALSE;
+ entry->TransitioningSupported = (descriptor->TransitioningSupported) ? TRUE : FALSE;
+
+ //
+ // Target Port Group's identifier
+ //
+ entry->Identifier = ((descriptor->TPG_Identifier & 0x00FF) << 8) | ((descriptor->TPG_Identifier & 0xFF00) >> 8);
+
+ //
+ // Target Port Group's status code
+ //
+ entry->StatusCode = descriptor->StatusCode;
+
+ //
+ // Vendor unique
+ //
+ entry->VendorUnique = descriptor->VendorUnique;
+
+ //
+ // Number of target ports
+ //
+ numberTargetPorts = descriptor->NumberTargetPorts;
+
+ NT_ASSERT(numberTargetPorts > 0);
+
+ //
+ // Point to first target port identifier
+ //
+ descriptorIndex = descriptor->TargetPortIds;
+
+ InitializeListHead(&entry->TargetPortList);
+
+ for (index = 0; index < numberTargetPorts && NT_SUCCESS(status); index++) {
+
+ if (((PUCHAR)descriptorIndex + ((index + 1) * sizeof(ULONG)) - 1) > endOfBuffer) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Status %x due to incorrect TPG buffer size (%u) passed in.\n",
+ Group,
+ status,
+ TPGs_BufferLength));
+
+ break;
+ }
+
+ GetUlongFrom4ByteArray((PUCHAR)(&descriptorIndex[index]), identifier);
+
+ listEntry = DsmpBuildTargetPortListEntry(DsmContext,
+ entry,
+ identifier);
+
+ if (listEntry) {
+
+ InsertTailList(&entry->TargetPortList, &listEntry->ListEntry);
+ InterlockedIncrement((LONG volatile*)&entry->NumberTargetPorts);
+
+ } else {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Failed to allocate memory for TP (identifier %x) of TPG %p.\n",
+ Group,
+ identifier,
+ entry));
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+ *DescriptorSize = sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) +
+ (numberTargetPorts * sizeof(ULONG));
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Failed to allocate memory for the TPG.\n",
+ Group));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ //
+ // Delete the target port list and the target port group entry
+ //
+ numberTargetPorts = index - 1;
+
+ if (entry) {
+
+ PLIST_ENTRY delEntry;
+
+ for (index = 0; index < numberTargetPorts; index++) {
+
+ delEntry = RemoveHeadList(&entry->TargetPortList);
+ listEntry = CONTAINING_RECORD(delEntry, DSM_TARGET_PORT_LIST_ENTRY, ListEntry);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Cleaning up TPG %p's TP %x.\n",
+ Group,
+ entry,
+ listEntry->Identifier));
+
+ DsmpFreePool(listEntry);
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Cleaning up TPG %p.\n",
+ Group,
+ entry));
+
+ DsmpFreePool(entry);
+ entry = NULL;
+ }
+ }
+
+__Exit_DsmpBuildTargetPortGroupEntry:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortGroupEntry (Group %p): Exiting function with entry %p.\n",
+ Group,
+ entry));
+
+ return entry;
+}
+
+
+PDSM_TARGET_PORT_LIST_ENTRY
+DsmpFindTargetPortListEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN ULONG RelativeTargetPortId
+ )
+/*++
+
+Routine Description:
+
+ This will search the passed in TPG's target port list for an identifier match.
+
+ N.B: This routine must be called with DsmContextLock held in either Shared or
+ Exclusive mode.
+
+Arguments:
+
+ DsmContext - DsmContext
+ TargetPortGroup - The Target Port Group whose target ports need to be searched.
+ RelativeTargetPortId - Identifier of the target port entry being matched.
+
+Return Value:
+
+ The target port list entry if match found, else NULL.
+
+--*/
+{
+ PLIST_ENTRY entry = NULL;
+ PDSM_TARGET_PORT_LIST_ENTRY targetPort = NULL;
+ BOOLEAN found = FALSE;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPortListEntry (TPG %p): Entering function.\n",
+ TargetPortGroup));
+
+ for (entry = TargetPortGroup->TargetPortList.Flink;
+ entry != &TargetPortGroup->TargetPortList && !found;
+ entry = entry->Flink) {
+
+ targetPort = CONTAINING_RECORD(entry, DSM_TARGET_PORT_LIST_ENTRY, ListEntry);
+ NT_ASSERT(targetPort);
+
+ if (targetPort) {
+
+ if (targetPort->Identifier == RelativeTargetPortId) {
+
+ NT_ASSERT(targetPort->TargetPortGroup == TargetPortGroup);
+
+ found = TRUE;
+ }
+ }
+ }
+
+ if (!found) {
+ targetPort = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPortListEntry (TPG %p): Exiting function with target port %p.\n",
+ TargetPortGroup,
+ targetPort));
+
+ return targetPort;
+}
+
+
+PDSM_TARGET_PORT_LIST_ENTRY
+DsmpBuildTargetPortListEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN ULONG RelativeTargetPortId
+ )
+/*++
+
+Routine Description:
+
+ This will allocate and partially initialize a target port list entry.
+
+ N.B: This routine must be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DsmContext
+ TargetPortGroup - The Target Port Group that this target port belongs to.
+ RelativeTargetPortId - Identifier of the target port entry being added.
+
+Return Value:
+
+ The new target port list entry.
+
+--*/
+{
+ PDSM_TARGET_PORT_LIST_ENTRY entry;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortListEntry (TPG %p): Entering function.\n",
+ TargetPortGroup));
+
+ entry = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_TARGET_PORT_LIST_ENTRY),
+ DSM_TAG_TARGET_PORT_LIST_ENTRY);
+
+ if (entry) {
+
+ InitializeListHead(&entry->TP_DeviceList);
+
+ entry->Identifier = RelativeTargetPortId;
+ entry->TargetPortGroup = TargetPortGroup;
+ entry->TargetPortSig = DSM_TARGET_PORT_SIG;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortListEntry (TPG %p): Failed to allocate memory for target port (identifier %x).\n",
+ TargetPortGroup,
+ RelativeTargetPortId));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpBuildTargetPortListEntry (TPG %p): Exiting function with entry %p.\n",
+ TargetPortGroup,
+ entry));
+
+ return entry;
+}
+
+
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpFindTargetPortGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PUSHORT TargetPortGroupId
+ )
+/*++
+
+Routine Description:
+
+ This routine searches the list of TargetPortGroups of a Group to
+ find a match for the passed in TargetPortGroupId.
+
+ N.B: This routine must be called with DsmContextLock held in either Shared or
+ Exclusive mode.
+
+Arguments:
+
+ DsmContext - DSM context.
+ Group - The group whose target port groups to search for a match.
+ TargetPortGroupId - Identifier of the target port group entry being searched.
+
+Return Value:
+
+ The target port group entry which matches the passed in identifier.
+
+--*/
+{
+ ULONG index;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroupEntry = NULL;
+ BOOLEAN found = FALSE;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPortGroup (Group %p): Entering function.\n",
+ Group));
+
+ //
+ // Run through the target port group array
+ //
+ for (index = 0; index < DSM_MAX_PATHS && !found; index++) {
+
+ targetPortGroupEntry = Group->TargetPortGroupList[index];
+
+ if (targetPortGroupEntry) {
+
+ if (targetPortGroupEntry->Identifier == *TargetPortGroupId) {
+
+ found = TRUE;
+ }
+ }
+ }
+
+ if (!found) {
+
+ targetPortGroupEntry = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPortGroup (Group %p): Exiting function with targetPortGroupEntry %p.\n",
+ Group,
+ targetPortGroupEntry));
+
+ return targetPortGroupEntry;
+}
+
+
+PDSM_TARGET_PORT_LIST_ENTRY
+DsmpFindTargetPort(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN PULONG TargetPortGroupId
+ )
+/*++
+
+Routine Description:
+
+ This routine searches the list of TargetPorts to
+ find a match for the passed in TargetPortGroup and RelativeTargetPortId.
+
+ N.B. Spin lock must be held by caller.
+
+Arguments:
+
+ DsmContext - DSM context.
+ TargetPortGroup - the Target Port Group of which this target port is a member.
+ RelativeTargetPortId - Identifier of the target port entry being searched.
+
+Return Value:
+
+ The target port entry which matches the passed in identifier.
+
+--*/
+{
+ PLIST_ENTRY entry;
+ PDSM_TARGET_PORT_LIST_ENTRY targetPortListEntry = NULL;
+ BOOLEAN found = FALSE;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPort (TPG %p): Entering function.\n",
+ TargetPortGroup));
+
+ //
+ // Run through the Target Port List
+ //
+ for (entry = TargetPortGroup->TargetPortList.Flink;
+ entry != &TargetPortGroup->TargetPortList && !found;
+ entry = entry->Flink) {
+
+ //
+ // Extract the target port group structure.
+ //
+ targetPortListEntry = CONTAINING_RECORD(entry,
+ DSM_TARGET_PORT_LIST_ENTRY,
+ ListEntry);
+ NT_ASSERT(targetPortListEntry);
+
+ if (targetPortListEntry) {
+
+ NT_ASSERT(TargetPortGroup == targetPortListEntry->TargetPortGroup);
+
+ //
+ // Compare with passed in identifier.
+ //
+ if (targetPortListEntry->Identifier == *TargetPortGroupId) {
+
+ found = TRUE;
+ }
+ }
+ }
+
+ if (!found) {
+
+ targetPortListEntry = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindTargetPort (TPG %p): Exiting function with targetPortListEntry %p.\n",
+ TargetPortGroup,
+ targetPortListEntry));
+
+ return targetPortListEntry;
+}
+
+
+NTSTATUS
+DsmpAddDeviceEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ )
+/*++
+
+Routine Description:
+
+ This routine adds DeviceInfo to an existing multi-path group.
+
+ N.B: This routine MUST be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ Group - The multi-path group to which DeviceInfo should be added.
+ DeviceInfo - The new device.
+ DeviceState - The initial device state (active, passive,...)
+
+Return Value:
+
+ UNSUCCESSFUL - If there are too many paths already.
+ SUCCESS
+
+--*/
+{
+ ULONG numberDevices;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpAddDeviceEntry (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ //
+ // Ensure that this is a valid config - namely, it hasn't
+ // exceeded the number of paths supported.
+ //
+ numberDevices = * (volatile ULONG *) &Group->NumberDevices;
+ if (numberDevices < DSM_MAX_PATHS) {
+
+#if DBG
+ ULONG i;
+
+ //
+ // Ensure that this isn't a second copy of the same pdo.
+ //
+ for (i = 0; i < numberDevices; i++) {
+ if (Group->DeviceList[i]->PortPdo == DeviceInfo->PortPdo) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpAddDeviceEntry (DevInfo %p): Received same PDO %p twice.\n",
+ DeviceInfo,
+ DeviceInfo->PortPdo));
+ }
+ }
+#endif
+
+ //
+ // Indicate one more device is present in this group.
+ //
+ Group->DeviceList[numberDevices] = DeviceInfo;
+
+ //
+ // Indicate one more in the list.
+ //
+ InterlockedIncrement((LONG volatile*)&Group->NumberDevices);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpAddDeviceEntry (DevInfo %p): Adding Device to Group %p\n",
+ DeviceInfo,
+ Group));
+
+ //
+ // Set-up this device's group id.
+ //
+ DeviceInfo->Group = Group;
+
+ //
+ // One more deviceInfo entry.
+ //
+ InterlockedIncrement((LONG volatile*)&DsmContext->NumberDevices);
+
+ //
+ // Finally, add it to the global list of devices.
+ //
+ InsertTailList(&DsmContext->DeviceList,
+ &DeviceInfo->ListEntry);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpAddDeviceEntry (DevInfo %p): Max Paths already added for Group %p.\n",
+ DeviceInfo,
+ Group));
+
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpAddDeviceEntry (DevInfo %p): Exiting function with status %x.\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+PDSM_CONTROLLER_LIST_ENTRY
+DsmpFindControllerEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDEVICE_OBJECT PortObject,
+ _In_ IN PSCSI_ADDRESS ScsiAddress,
+ _In_reads_(ControllerSerialNumberLength) IN PSTR ControllerSerialNumber,
+ _In_ IN SIZE_T ControllerSerialNumberLength,
+ _In_ IN STORAGE_IDENTIFIER_CODE_SET CodeSet,
+ _In_ IN BOOLEAN AcquireLock
+ )
+/*++
+
+Routine Description:
+
+ This routine compares the passed in serial number and SCSI address with the
+ entries in the list of controller objects.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization.
+ PortObject - Port FDO exposing the controller.
+ ScsiAddress - The scsi address to match.
+ ControllerSerialNumber - The serial number for which to find a match.
+ ControllerSerialNumberLength - Length of the passed in serial number, in bytes.
+ CodeSet - Code set used when building the passed in serial number.
+ AcquireLock - FALSE indicates that the caller has already acquired the spin lock.
+
+Return Value:
+
+ Controller list entry if a match is found, else NULL
+
+--*/
+{
+ KIRQL oldIrql = PASSIVE_LEVEL; // Initialize variable to prevent C4701 error
+ PLIST_ENTRY entry;
+ PDSM_CONTROLLER_LIST_ENTRY controllerEntry = NULL;
+ BOOLEAN found = FALSE;
+ PDSM_CONTROLLER_LIST_ENTRY candidate = NULL;
+
+ UNREFERENCED_PARAMETER(CodeSet);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFindControllerEntry (SN %s): Entering function.\n",
+ ControllerSerialNumber));
+
+ if (AcquireLock) {
+
+ oldIrql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ }
+
+ for (entry = DsmContext->ControllerList.Flink;
+ entry != &DsmContext->ControllerList && !found;
+ entry = entry->Flink) {
+
+ controllerEntry = CONTAINING_RECORD(entry, DSM_CONTROLLER_LIST_ENTRY, ListEntry);
+ NT_ASSERT(controllerEntry);
+
+ if (!controllerEntry) {
+
+ continue;
+ }
+
+ //
+ // Serial numbers and Portal, Bus, and Target of the SCSI address must match.
+ //
+ if (!strncmp((const char*)controllerEntry->Identifier,
+ ControllerSerialNumber,
+ ControllerSerialNumberLength) &&
+ (controllerEntry->ScsiAddress->PortNumber == ScsiAddress->PortNumber &&
+ controllerEntry->ScsiAddress->PathId == ScsiAddress->PathId &&
+ controllerEntry->ScsiAddress->TargetId == ScsiAddress->TargetId)) {
+
+ if (controllerEntry->IdLength == ControllerSerialNumberLength) {
+
+ found = TRUE;
+
+ } else {
+
+ if ((!candidate) ||
+ (controllerEntry->IdLength > ControllerSerialNumberLength && ControllerSerialNumberLength == 32)) {
+
+ candidate = controllerEntry;
+ }
+ }
+ }
+ }
+
+ if (!found) {
+
+ if (candidate) {
+
+ controllerEntry = candidate;
+
+ } else {
+
+ controllerEntry = NULL;
+ }
+ }
+
+ //
+ // If we found a matching controller entry, we need to make sure the Port
+ // Object (FDO) is updated. We also don't care about the LUN part of the
+ // SCSI address so we just set it to zero.
+ //
+ if (controllerEntry) {
+ controllerEntry->PortObject = PortObject;
+ controllerEntry->ScsiAddress->Lun = 0;
+ }
+
+ if (AcquireLock) {
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), oldIrql);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFindControllerEntry (SN %s): Exiting function with controllerEntry %p\n",
+ ControllerSerialNumber,
+ controllerEntry));
+
+ return controllerEntry;
+}
+
+
+_Ret_maybenull_
+_Must_inspect_result_
+_When_(return != NULL, __drv_allocatesMem(Mem))
+PDSM_CONTROLLER_LIST_ENTRY
+DsmpBuildControllerEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_opt_ IN PDEVICE_OBJECT DeviceObject,
+ _In_ IN PDEVICE_OBJECT PortObject,
+ _In_ IN PSCSI_ADDRESS ScsiAddress,
+ _In_ IN PSTR ControllerSerialNumber,
+ _In_ IN STORAGE_IDENTIFIER_CODE_SET CodeSet,
+ _In_ IN BOOLEAN AcquireLock
+ )
+/*++
+
+Routine Description:
+
+ This routine builds a new controller list entry with the passed in serial number info.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization.
+ DeviceObject - Controller's PDO.
+ PortObject - Port FDO exposing the controller.
+ ScsiAddress - scsi address of the controller.
+ ControllerSerialNumber - The serial number to associate with new entry.
+ CodeSet - Code set of the identifier that was used to build the serial number.
+ AcquireLock - TRUE indicates that the function must grab the spinlock. FALSE indicates
+ that caller has the spin lock held.
+
+Return Value:
+
+ New controller list entry if we successfully built one, else NULL
+
+--*/
+{
+ KIRQL oldIrql = PASSIVE_LEVEL; // Initialize variable to prevent C4701 error
+ PDSM_CONTROLLER_LIST_ENTRY controllerEntry = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildControllerEntry (SN %s): Entering function - Controller %p seen through PortFDO %p.\n",
+ ControllerSerialNumber,
+ DeviceObject,
+ PortObject));
+
+ if (AcquireLock) {
+
+ oldIrql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ }
+
+ controllerEntry = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_CONTROLLER_LIST_ENTRY),
+ DSM_TAG_CONTROLLER_LIST_ENTRY);
+
+ if (controllerEntry) {
+
+ //
+ // Note:
+ // ControllerSerialNumber's length fits in a 32-bit value.
+ // See implementation in DsmpParseDeviceID()
+ //
+ ULONG length = (ULONG)strlen(ControllerSerialNumber);
+
+ controllerEntry->Identifier = DsmpAllocatePool(NonPagedPoolNx,
+ length + 1,
+ DSM_TAG_SERIAL_NUM);
+
+ if (controllerEntry->Identifier) {
+
+ controllerEntry->ScsiAddress = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(SCSI_ADDRESS),
+ DSM_TAG_SCSI_ADDRESS);
+ if (controllerEntry->ScsiAddress) {
+
+ RtlCopyMemory(controllerEntry->ScsiAddress, ScsiAddress, sizeof(SCSI_ADDRESS));
+
+ controllerEntry->DeviceObject = DeviceObject;
+ controllerEntry->PortObject = PortObject;
+ controllerEntry->ControllerSig = DSM_CONTROLLER_SIG;
+ controllerEntry->IdLength = length;
+ controllerEntry->IdCodeSet = CodeSet;
+
+ RtlCopyMemory(controllerEntry->Identifier,
+ ControllerSerialNumber,
+ length);
+
+ controllerEntry->RefCount = 0;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildControllerEntry (SN %s): Failed to allocate resources for scsiaddress (controllerEntry %p).\n",
+ ControllerSerialNumber,
+ controllerEntry));
+
+ DsmpFreePool(controllerEntry->Identifier);
+ controllerEntry->Identifier = NULL;
+ DsmpFreePool(controllerEntry);
+ controllerEntry = NULL;
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildControllerEntry (SN %s): Failed to allocate resources for identifier (controllerEntry %p).\n",
+ ControllerSerialNumber,
+ controllerEntry));
+
+ DsmpFreePool(controllerEntry);
+ controllerEntry = NULL;
+ }
+
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildControllerEntry (SN %s): Failed to allocate memory for ControllerEntry.\n",
+ ControllerSerialNumber));
+ }
+
+ if (AcquireLock) {
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), oldIrql);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildControllerEntry (SN %s): Exiting function with controllerEntry %p\n",
+ ControllerSerialNumber,
+ controllerEntry));
+
+ return controllerEntry;
+}
+
+
+VOID
+DsmpFreeControllerEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ __drv_freesMem(Mem) IN PDSM_CONTROLLER_LIST_ENTRY ControllerEntry
+ )
+/*++
+
+Routine Description:
+
+ This routine frees the allocations of the passed in controller list entry.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization.
+ ControllerEntry - Controller list entry.
+
+Return Value:
+
+ Nothing
+
+--*/
+{
+ PVOID tempAddress = (PVOID)ControllerEntry;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFreeControllerEntry (Entry %p): Entering function.\n",
+ ControllerEntry));
+
+ if (ControllerEntry->Identifier) {
+ DsmpFreePool(ControllerEntry->Identifier);
+ }
+
+ if (ControllerEntry->ScsiAddress) {
+ DsmpFreePool(ControllerEntry->ScsiAddress);
+ }
+
+ DsmpFreePool(ControllerEntry);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFreeControllerEntry (Entry %p): Exiting function.\n",
+ tempAddress));
+
+ return;
+}
+
+
+BOOLEAN
+DsmpIsDeviceBelongsToController(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PDSM_CONTROLLER_LIST_ENTRY ControllerEntry
+ )
+/*++
+
+Routine Description:
+
+ This routine determines if the device passed in was exposed via the passed
+ in controller.
+ The match is to be based on VID and SCSI Address (using the Port, Bus
+ and Target comparison).
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization.
+ DeviceInfo - The device instance to match.
+ ControllerEntry - The controller object which we need to determine whether
+ DeviceInfo is exposed from.
+
+Return Value:
+
+ TRUE - if the controller's VID and scsi address match
+ FALSE - not matched
+
+--*/
+{
+ BOOLEAN saMatch = FALSE;
+ BOOLEAN vMatch = FALSE;
+ BOOLEAN match = FALSE;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpIsDeviceBelongsToController (DevInfo %p): Entering function - ControllerEntry is %p.\n",
+ DeviceInfo,
+ ControllerEntry));
+
+ if (DeviceInfo->ScsiAddress && ControllerEntry->ScsiAddress) {
+
+ saMatch = (DeviceInfo->ScsiAddress->PathId == ControllerEntry->ScsiAddress->PathId &&
+ DeviceInfo->ScsiAddress->PortNumber == ControllerEntry->ScsiAddress->PortNumber &&
+ DeviceInfo->ScsiAddress->TargetId == ControllerEntry->ScsiAddress->TargetId);
+ }
+
+ if (saMatch) {
+
+ INQUIRYDATA inquiryData = {0};
+ UCHAR controllerVID[9] = {0};
+ UCHAR deviceVID[9] = {0};
+
+ if (NT_SUCCESS(DsmpGetStandardInquiryData(ControllerEntry->DeviceObject, &inquiryData))) {
+
+ RtlStringCchCopyA((PSTR)controllerVID,
+ ARRAYSIZE(controllerVID),
+ (PCSTR)(&inquiryData.VendorId));
+
+ RtlStringCchCopyA((PSTR)deviceVID,
+ ARRAYSIZE(deviceVID),
+ (PCSTR)(&DeviceInfo->Descriptor) + DeviceInfo->Descriptor.VendorIdOffset);
+
+
+ if (!strcmp((const char*)controllerVID, (const char*)deviceVID)) {
+
+ vMatch = TRUE;
+ }
+ }
+ }
+
+ match = saMatch & vMatch;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpIsDeviceBelongsToController (DevInfo %p): ControllerEntry %p. Exiting function with match = %x.\n",
+ DeviceInfo,
+ ControllerEntry,
+ match));
+
+ return match;
+}
+
+
+PDSM_DEVICE_INFO
+DsmpFindDevInfoFromGroupAndFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_FAILOVER_GROUP FOGroup
+ )
+/*++
+
+Routine Description:
+
+ This routine will find the deviceInfo that is part of both the passed in Group
+ as well as passed in Fail-Over group.
+
+ N.B: This routine MUST be called with DsmContextLock held in either Shared or
+ Exclusive mode.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ Group - The group that represents the device.
+ FOGroup - The FOG that the device is part of.
+
+Return Value:
+
+ The deviceInfo that is part of both.
+ NULL - if not found.
+
+--*/
+{
+ ULONG i;
+ PDSM_DEVICE_INFO deviceInfo = NULL;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpFindDevInfoFromGroupAndFOGroup (Group %p FOG %p): Entering function.\n",
+ Group,
+ FOGroup));
+
+ if (Group && FOGroup) {
+
+ //
+ // Run through the list of devInfos in passed in Group
+ //
+ for (i = 0; i < DSM_MAX_PATHS; i++) {
+
+ deviceInfo = Group->DeviceList[i];
+
+ if (deviceInfo) {
+
+ if (deviceInfo->FailGroup == FOGroup) {
+
+ break;
+
+ } else {
+
+ deviceInfo = NULL;
+ }
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpFindFOGroup (Group %p FOG %p): Exiting function with deviceInfo %p.\n",
+ Group,
+ FOGroup,
+ deviceInfo));
+
+ return deviceInfo;
+}
+
+
+PDSM_FAILOVER_GROUP
+DsmpFindFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PVOID PathId
+ )
+/*++
+
+Routine Description:
+
+ This routine will find the Fail-Over group that corresponds to PathId.
+
+ N.B: This routine MUST be called with DsmContextLock held in either Shared or
+ Exclusive mode.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ PathId - The Path Identifier that corresponds to
+ an adapter/adapter-controller
+
+Return Value:
+
+ The fail-over group.
+ NULL - if not found.
+
+--*/
+{
+ PDSM_FAILOVER_GROUP failOverGroup = NULL;
+ PDSM_FAILOVER_GROUP retFOGroup = NULL;
+ PLIST_ENTRY entry;
+ ULONG i;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindFOGroup (PathId %p): Entering function.\n",
+ PathId));
+
+ //
+ // Run through the list of Fail-Over Groups
+ //
+ entry = DsmContext->FailGroupList.Flink;
+ for (i = 0; i < DsmContext->NumberFOGroups; i++, entry = entry->Flink) {
+
+ //
+ // Extract the fail-over group structure.
+ //
+ failOverGroup = CONTAINING_RECORD(entry, DSM_FAILOVER_GROUP, ListEntry);
+ NT_ASSERT(failOverGroup);
+
+ if (!failOverGroup) {
+ continue;
+ }
+
+ //
+ // Check for a match of the PathId.
+ //
+ if (failOverGroup->PathId == PathId) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindFOGroup (PathId %p): Found a FO group %p.\n",
+ PathId,
+ failOverGroup));
+
+ retFOGroup = failOverGroup;
+
+ break;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindFOGroup (PathId %p): Exiting function with retFOGroup %p.\n",
+ PathId,
+ retFOGroup));
+
+ return retFOGroup;
+}
+
+
+PDSM_FAILOVER_GROUP
+DsmpBuildFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PVOID *PathId
+ )
+/*++
+
+Routine Description:
+
+ This routine will build and partially initialise a fail-over group entry.
+ The FOG corresponds to the device list which will fail as a group.
+
+ N.B: This routine MUST be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ DeviceInfo - The first device to add to the group.
+ PathId - An identifier that is returned to mpio that id's the path.
+
+Return Value:
+
+ The fail-over group entry.
+ NULL - on failed allocation.
+
+--*/
+{
+ PDSM_FAILOVER_GROUP failOverGroup;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildFOGroup (PathId %p): Entering function.\n", PathId));
+
+ //
+ // Allocate a new Fail Over Group
+ //
+ failOverGroup = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_FAILOVER_GROUP),
+ DSM_TAG_FO_GROUP);
+ if (failOverGroup) {
+
+ InitializeListHead(&failOverGroup->FOG_DeviceList);
+ InitializeListHead(&failOverGroup->ZombieGroupList);
+
+ //
+ // Get the current number of groups, and add the one that's being created.
+ //
+ InterlockedIncrement((LONG volatile*)&DsmContext->NumberFOGroups);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpBuildFOGroup (PathId %p): Path that will be used for %p is %p.\n",
+ PathId,
+ DeviceInfo,
+ *PathId));
+
+ failOverGroup->PathId = *PathId;
+
+ //
+ // Set the initial state to NORMAL.
+ //
+ failOverGroup->State = DSM_FG_NORMAL;
+
+ failOverGroup->FailOverSig = DSM_FOG_SIG;
+
+ //
+ // Add it to the global list.
+ //
+ InsertTailList(&DsmContext->FailGroupList,
+ &failOverGroup->ListEntry);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpBuildFOGroup (PathId %p): Added new FOGroup %p with path %p. Count of FO Group %d.\n",
+ PathId,
+ failOverGroup,
+ *PathId,
+ DsmContext->NumberFOGroups));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildFOGroup (PathId %p): Failed to allocate memory for FailOverGroup.\n",
+ PathId));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildFOGroup (PathId %p): Exiting function with failOverGroup %p.\n",
+ PathId,
+ failOverGroup));
+
+ return failOverGroup;
+}
+
+
+NTSTATUS
+DsmpUpdateFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_FAILOVER_GROUP FailGroup,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ )
+/*++
+
+Routine Description:
+
+ This routine will add DeviceInfo to an existing FOG.
+
+ N.B: This routine MUST be called with DsmContextLock held in Exclusive mode.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ FailGroup - The fail-over group entry.
+ DeviceInfo - The new device.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate error code.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSM_FOG_DEVICELIST_ENTRY fogDeviceListEntry;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpUpdateFOGroup (FOG %p): Entering function. DeviceInfo %p.\n",
+ FailGroup,
+ DeviceInfo));
+
+ if (DeviceInfo && FailGroup) {
+
+ fogDeviceListEntry = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_FOG_DEVICELIST_ENTRY),
+ DSM_TAG_FOG_DEV_ENTRY);
+
+ if (fogDeviceListEntry) {
+
+ //
+ // Add the device to the list of devices that are on this path.
+ //
+ fogDeviceListEntry->DeviceInfo = DeviceInfo;
+ InterlockedIncrement((LONG volatile*)&FailGroup->Count);
+ InsertTailList(&FailGroup->FOG_DeviceList, &fogDeviceListEntry->ListEntry);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpUpdateFOGroup (FOG %p): DevInfo %p added (current count: %d)\n",
+ FailGroup,
+ DeviceInfo,
+ FailGroup->Count));
+
+ //
+ // Set the device's F.O. Group.
+ //
+ DeviceInfo->FailGroup = FailGroup;
+
+ } else {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpUpdateFOGroup (FOG %p): Failed to allocate memory for FOG devlist entry.\n",
+ FailGroup));
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpUpdateFOGroup (FOG %p): Exiting function with status %x.\n",
+ FailGroup,
+ status));
+
+ return status;
+}
+
+
+VOID
+DsmpRemoveDeviceFailGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_FAILOVER_GROUP FailGroup,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN BOOLEAN AcquireDSMLockExclusive
+ )
+/*++
+
+Routine Description:
+
+ This routine will remove DeviceInfo from the FOG.
+ This routine is called in response to a removal of the device.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ FailGroup - The FOG from which DeviceInfo should be removed.
+ DeviceInfo - The now missing device.
+ AcquireDSMLockExclusive - If TRUE this routine should acquire DsmContextLock Exclusively
+
+Return Value:
+
+ NOTHING
+
+--*/
+{
+ KIRQL irql = PASSIVE_LEVEL; // Initialize variable to prevent C4701 warnings
+ PLIST_ENTRY entry;
+ PDSM_FOG_DEVICELIST_ENTRY fogDeviceListEntry;
+ PLIST_ENTRY zombieEntry;
+ PDSM_ZOMBIEGROUP_ENTRY zombieGroup;
+ PDSM_ZOMBIEGROUP_ENTRY newZombieGroup;
+ BOOLEAN groupInZombieList = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceFailGroup (FOG %p): Entering function. DeviceInfo %p.\n",
+ FailGroup,
+ DeviceInfo));
+
+ if (FailGroup && DeviceInfo) {
+
+ if (AcquireDSMLockExclusive) {
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ }
+
+ for (entry = FailGroup->FOG_DeviceList.Flink;
+ entry != &FailGroup->FOG_DeviceList;
+ entry = entry->Flink) {
+
+ fogDeviceListEntry = CONTAINING_RECORD(entry,
+ DSM_FOG_DEVICELIST_ENTRY,
+ ListEntry);
+ DSM_ASSERT(fogDeviceListEntry);
+
+ if (!fogDeviceListEntry) {
+ continue;
+ }
+
+ if (fogDeviceListEntry->DeviceInfo == DeviceInfo) {
+
+ DeviceInfo->FailGroup = NULL;
+ RemoveEntryList(entry);
+ DsmpFreePool(fogDeviceListEntry);
+
+ InterlockedDecrement((LONG volatile*)&FailGroup->Count);
+
+ //
+ // If a DeviceInfo is removed, we need to keep its group in a
+ // "zombie" list so that we can still access a fail-over group's
+ // associated groups even when all its devices are gone.
+ //
+ for (zombieEntry = FailGroup->ZombieGroupList.Flink;
+ zombieEntry != &(FailGroup->ZombieGroupList);
+ zombieEntry = zombieEntry->Flink) {
+
+ zombieGroup = CONTAINING_RECORD(zombieEntry, DSM_ZOMBIEGROUP_ENTRY, ListEntry);
+ if (zombieGroup != NULL &&
+ zombieGroup->Group != NULL &&
+ zombieGroup->Group == DeviceInfo->Group) {
+
+ groupInZombieList = TRUE;
+ break;
+ }
+ }
+
+ //
+ // Create a new entry if the group does not exist in the zombie group list.
+ //
+ if (groupInZombieList == FALSE) {
+ newZombieGroup = (PDSM_ZOMBIEGROUP_ENTRY)DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_ZOMBIEGROUP_ENTRY),
+ DSM_TAG_ZOMBIEGROUP_ENTRY);
+ if (newZombieGroup != NULL) {
+ newZombieGroup->Group = DeviceInfo->Group;
+ InsertTailList(&FailGroup->ZombieGroupList, &newZombieGroup->ListEntry);
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceFailGroup (DevInfo %p): Failed to allocate memory for the zombie group.\n",
+ DeviceInfo));
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceFailGroup (FOG %p): DevInfo %p removed from FOG (current count: %d)\n",
+ FailGroup,
+ DeviceInfo,
+ FailGroup->Count));
+
+ break;
+ }
+ }
+
+ if (AcquireDSMLockExclusive) {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceFailGroup (FOG %p): Exiting function.\n",
+ FailGroup));
+
+ return;
+}
+
+
+ULONG
+DsmpRemoveDeviceEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ )
+/*++
+
+Routine Description:
+
+ This routine will remove DeviceInfo from Group. If it is the last DeviceInfo
+ in the Group, it has the added side-effect of cleaning up the Group entry
+ also.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ Group - The multi-path group from which DeviceInfo should be removed.
+ DeviceInfo - The device to remove.
+
+Return Value:
+
+ Number of devices left in group.
+
+--*/
+{
+ KIRQL irql;
+ ULONG i;
+ ULONG j;
+ ULONG numberDevices;
+ BOOLEAN freeGroup = FALSE;
+ PVOID tempAddress = (PVOID)Group;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceEntry (Group %p): Entering function. DeviceInfo %p.\n",
+ Group,
+ DeviceInfo));
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // Find it's offset in the array of devices.
+ //
+ for (i = 0; i < Group->NumberDevices; i++) {
+
+ if (Group->DeviceList[i] == DeviceInfo) {
+
+ //
+ // Zero out it's entry.
+ //
+ Group->DeviceList[i] = NULL;
+
+ //
+ // Reduce the number in the group.
+ //
+ InterlockedDecrement((LONG volatile*)&Group->NumberDevices);
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceEntry (Group %p): Removing Device %p (desiredState %u) from Group\n",
+ Group,
+ DeviceInfo,
+ DeviceInfo->DesiredState));
+
+ //
+ // Collapse the array.
+ // Holding the spinlock, so that the state is consistent in other
+ // routines.
+ //
+ for (j = i; j < Group->NumberDevices; j++) {
+
+ //
+ // Shuffle all entries down to fill the hole.
+ //
+ Group->DeviceList[j] = Group->DeviceList[j + 1];
+ }
+
+ //
+ // Zero out the last one.
+ //
+ Group->DeviceList[j] = NULL;
+ break;
+ }
+ }
+
+ //
+ // Remove this devInfo from the TargetPort deviceList
+ //
+ DsmpRemoveDeviceFromTargetPortList(DeviceInfo);
+
+ numberDevices = Group->NumberDevices;
+
+ //
+ // See if anything is left in the Group.
+ //
+ if (Group->NumberDevices == 0) {
+
+ Group->State = DSM_GP_FAILED;
+
+ //
+ // Yank it from the Group list.
+ //
+ DsmpRemoveGroupEntry(DsmContext, Group, FALSE);
+
+ freeGroup = TRUE;
+ }
+
+ //
+ // Yank the device out of the Global list.
+ //
+ RemoveEntryList(&DeviceInfo->ListEntry);
+ InterlockedDecrement((LONG volatile*)&DsmContext->NumberDevices);
+
+ //
+ // If the serial number buffer was allocated, need to free it.
+ //
+ if (DeviceInfo->SerialNumberAllocated) {
+ DsmpFreePool(DeviceInfo->SerialNumber);
+ }
+
+ if (DeviceInfo->ScsiAddress) {
+ DsmpFreePool(DeviceInfo->ScsiAddress);
+ }
+
+ //
+ // Fix up the Reservation List, if needed.
+ //
+ if (!freeGroup && Group->ReservationList) {
+ ULONG oldList;
+
+ //
+ // Capture the list for debugging.
+ //
+ oldList = Group->ReservationList;
+ Group->ReservationList = 0;
+
+ //
+ // Go through all devices in this group and find the one(s) registered.
+ //
+ for (i = 0; i < Group->NumberDevices; i++) {
+
+ if (Group->DeviceList[i]->RegisterServiced) {
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmRemoveDeviceEntry (Group %p): Device %p at %d registered.\n",
+ Group,
+ Group->DeviceList[i],
+ i));
+
+ //
+ // Indicate its place.
+ //
+ Group->ReservationList |= (1 << i);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmRemoveDeviceEntry (Group %p): Reservations Old (%x) New (%x).\n",
+ Group,
+ oldList,
+ Group->ReservationList));
+ }
+
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ //
+ // Free the allocation.
+ //
+ DsmpFreePool(DeviceInfo);
+
+ if (freeGroup) {
+
+ //
+ // Free the allocations.
+ //
+ if (Group->RegistryKeyName) {
+ DsmpFreePool(Group->RegistryKeyName);
+ }
+
+ if (Group->HardwareId) {
+ DsmpFreePool(Group->HardwareId);
+ }
+
+ DsmpFreePool(Group);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceEntry (Group %p): Exiting function - numberDevices = %x.\n",
+ tempAddress,
+ numberDevices));
+
+ return numberDevices;
+}
+
+
+VOID
+DsmpRemoveDeviceFromTargetPortList(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ )
+/*++
+
+Routine Description:
+
+ This will remove a DeviceInfo from its target port device list.
+
+ The caller should ensure that the DsmContext->SpinLock is held before
+ calling this function.
+
+Arguments:
+
+ DeviceInfo - The DeviceInfo to be removed.
+
+Return Value:
+
+ None
+
+--*/
+{
+ if (DeviceInfo->TargetPort) {
+
+ PLIST_ENTRY entry;
+ PDSM_TARGET_PORT_DEVICELIST_ENTRY listEntry;
+
+ for (entry = DeviceInfo->TargetPort->TP_DeviceList.Flink;
+ entry != NULL && entry != &DeviceInfo->TargetPort->TP_DeviceList;
+ entry = entry->Flink) {
+
+ listEntry = CONTAINING_RECORD(entry, DSM_TARGET_PORT_DEVICELIST_ENTRY, ListEntry);
+
+ if (listEntry) {
+
+ if (listEntry->DeviceInfo == DeviceInfo) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveDeviceFromTargetPortList: Removing device %p from target port entry %p.\n",
+ DeviceInfo,
+ listEntry));
+
+ RemoveEntryList(entry);
+ InterlockedDecrement((LONG volatile*)&DeviceInfo->TargetPort->Count);
+ DsmpFreePool(listEntry);
+
+ DeviceInfo->TargetPort = NULL;
+ DeviceInfo->TargetPortGroup = NULL;
+
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+VOID
+DsmpRemoveZombieGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY ZombieGroup
+ )
+/* ++
+
+Routine Description:
+
+ This will scan through all the Failover Groups and remove the given Group
+ from each Failover Group's zombie group list.
+
+ The DSM lock should be aquired by the caller.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ ZombieGroup - Group entry that should be removed from FOGs' ZombieGroupList
+
+Return Value:
+
+ None
+
+-- */
+{
+ //
+ // Run through the list of Fail-Over Groups
+ //
+ ULONG i;
+ PDSM_FAILOVER_GROUP failOverGroup = NULL;
+ PLIST_ENTRY fogEntry;
+ PLIST_ENTRY groupEntry;
+ PDSM_ZOMBIEGROUP_ENTRY zombieGroupEntry;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveZombieGroupEntry (Group %p): Entering function.\n",
+ ZombieGroup));
+
+ fogEntry = DsmContext->FailGroupList.Flink;
+ for (i = 0; fogEntry != NULL && i < DsmContext->NumberFOGroups; i++, fogEntry = fogEntry->Flink) {
+
+ failOverGroup = CONTAINING_RECORD(fogEntry, DSM_FAILOVER_GROUP, ListEntry);
+ if (failOverGroup != NULL) {
+
+ for (groupEntry = failOverGroup->ZombieGroupList.Flink;
+ groupEntry != &(failOverGroup->ZombieGroupList);
+ groupEntry = groupEntry->Flink) {
+
+ zombieGroupEntry = CONTAINING_RECORD(groupEntry, DSM_ZOMBIEGROUP_ENTRY, ListEntry);
+ if (zombieGroupEntry != NULL &&
+ zombieGroupEntry->Group != NULL &&
+ zombieGroupEntry->Group == ZombieGroup) {
+
+ RemoveEntryList(groupEntry);
+ DsmpFreePool(zombieGroupEntry);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveZombieGroupEntry (Group %p): Found and removed a zombie group in (FOG %p)\n",
+ ZombieGroup,
+ failOverGroup));
+
+ //
+ // We removed the zombie group entry from this fail-over
+ // group, so we can move on to the next fail-over group.
+ //
+ break;
+ }
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveZombieGroupEntry (Group %p): Exiting function.\n",
+ ZombieGroup));
+}
+
+
+VOID
+DsmpRemoveGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY GroupEntry,
+ _In_ IN BOOLEAN AcquireDSMLockExclusive
+ )
+/*++
+
+Routine Description:
+
+ This will remove a group entry from the DSM's list.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ GroupEntry - Group entry that should be removed from DSM's list
+ AcquireDSMLockExclusive - If TRUE this routine should acquire DsmContextLock Exclusively
+
+Return Value:
+
+ None
+
+--*/
+{
+ KIRQL irql = PASSIVE_LEVEL; // Initialize variable to prevent C4701 warnings
+ ULONG index;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup;
+ PLIST_ENTRY entry;
+ PDSM_TARGET_PORT_LIST_ENTRY targetPort;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveGroupEntry (Group %p): Entering function.\n",
+ GroupEntry));
+
+ if (AcquireDSMLockExclusive) {
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ }
+
+ NT_ASSERT(GroupEntry && GroupEntry->ListEntry.Flink && GroupEntry->ListEntry.Blink);
+
+ //
+ // Since this group is being removed, we need to make sure it is also
+ // removed from all fail-over groups' zombie group lists.
+ //
+ DsmpRemoveZombieGroupEntry(DsmContext, GroupEntry);
+
+ //
+ // Add it to the list of multi-path groups.
+ //
+ RemoveEntryList(&GroupEntry->ListEntry);
+
+ GroupEntry->ListEntry.Flink = GroupEntry->ListEntry.Blink = NULL;
+
+ InterlockedDecrement((LONG volatile*)&DsmContext->NumberGroups);
+
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ //
+ // Clean up all its Target Port Groups
+ //
+ targetPortGroup = GroupEntry->TargetPortGroupList[index];
+
+ if (targetPortGroup) {
+
+ GroupEntry->TargetPortGroupList[index] = NULL;
+
+ //
+ // For each target port group, clean up all its target ports
+ //
+ while (!IsListEmpty(&targetPortGroup->TargetPortList)) {
+
+ entry = RemoveHeadList(&targetPortGroup->TargetPortList);
+
+ if (entry) {
+
+ targetPort = CONTAINING_RECORD(entry, DSM_TARGET_PORT_LIST_ENTRY, ListEntry);
+
+ if (targetPort) {
+
+ PLIST_ENTRY deviceEntry;
+ PDSM_TARGET_PORT_DEVICELIST_ENTRY listEntry;
+
+ while (!IsListEmpty(&targetPort->TP_DeviceList)) {
+
+ deviceEntry = RemoveHeadList(&targetPort->TP_DeviceList);
+
+ if (deviceEntry) {
+
+ listEntry = CONTAINING_RECORD(deviceEntry,
+ DSM_TARGET_PORT_DEVICELIST_ENTRY,
+ ListEntry);
+
+ if (listEntry) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveGroupEntry (Group %p): Deleting device %p from TP %p list (TPG %p).\n",
+ GroupEntry,
+ listEntry->DeviceInfo,
+ targetPort,
+ targetPortGroup));
+
+ DsmpFreePool(listEntry);
+
+ InterlockedDecrement((LONG volatile*)&targetPort->Count);
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveGroupEntry (Group %p): Deleting target port %p from TPG %p list.\n",
+ GroupEntry,
+ targetPort,
+ targetPortGroup));
+
+ DsmpFreePool(targetPort);
+
+ InterlockedDecrement((LONG volatile*)&targetPortGroup->NumberTargetPorts);
+ }
+ }
+ }
+
+ NT_ASSERT(targetPortGroup->NumberTargetPorts == 0);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveGroupEntry (Group %p): Deleting target port group %p.\n",
+ GroupEntry,
+ targetPortGroup));
+
+ DsmpFreePool(targetPortGroup);
+
+ InterlockedDecrement((LONG volatile*)&GroupEntry->NumberTargetPortGroups);
+ }
+ }
+
+ NT_ASSERT(GroupEntry->NumberTargetPortGroups == 0);
+
+ if (AcquireDSMLockExclusive) {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRemoveGroupEntry (Group %p): Exiting function.\n",
+ GroupEntry));
+
+ return;
+}
+
+
+PDSM_FAILOVER_GROUP
+DsmpSetNewPath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDevice
+ )
+/*++
+
+Routine Description:
+
+ This routine will assign a new path to the multi-path group in
+ which FailingDevice resides.
+
+ Caller must NOT hold spin lock.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+
+ FailingDevice - The device-path that is being moved
+ (due to failure, or admin. request)
+
+Return Value:
+
+ The FOG containing the new path.
+
+--*/
+{
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetNewPath (DevInfo %p): Entering function.\n",
+ FailingDevice));
+
+ if (DsmpIsSymmetricAccess(FailingDevice)) {
+
+ DsmpSetLBForPathRemoval(DsmContext, FailingDevice, NULL, SpecialHandlingFlag);
+
+ } else {
+
+ DsmpSetLBForPathRemovalALUA(DsmContext, FailingDevice, NULL, SpecialHandlingFlag);
+ }
+
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetNewPath (DevInfo %p): Exiting function with path (failGroup) %p.\n",
+ FailingDevice,
+ FailingDevice->Group->PathToBeUsed));
+
+ return FailingDevice->Group->PathToBeUsed;
+}
+
+
+PDSM_FAILOVER_GROUP
+DsmpSetNewPathUsingGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group
+ )
+/*++
+
+Routine Description:
+
+ This routine will try to assign a new path using the given multi-path group.
+ This function should only be called during failover in the event that there
+ is no DeviceInfo with which to call DsmpSetNewPath().
+
+ Typically this will be called with one of a fail-over group's zombie groups.
+
+ Caller must NOT hold spin lock.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization.
+
+ Group - The multi-path group which to assign a new path.
+
+Return Value:
+
+ The FOG containing the new path or NULL if no path was found.
+
+--*/
+
+{
+ ULONG i;
+ PDSM_DEVICE_INFO pDevInfo = NULL;
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetNewPathUsingGroup (Group %p): Entering function.\n",
+ Group));
+
+ //
+ // Get the first available DeviceInfo.
+ //
+ for (i = 0; i < DSM_MAX_PATHS; i ++) {
+ if (Group->DeviceList[i] != NULL) {
+ pDevInfo = Group->DeviceList[i];
+ break;
+ }
+ }
+
+ if (pDevInfo == NULL) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetNewPathForZombieGroup (ZombieGroup %p): No failover group can be found.\n",
+ Group));
+ return NULL;
+ }
+
+
+ if (DsmpIsSymmetricAccess(pDevInfo)) {
+
+ DsmpSetLBForPathRemoval(DsmContext, pDevInfo, Group, SpecialHandlingFlag);
+
+ } else {
+
+ DsmpSetLBForPathRemovalALUA(DsmContext, pDevInfo, Group, SpecialHandlingFlag);
+ }
+
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetNewPathForZombieGroup (ZombieGroup %p): Exiting function with path (failGroup) %p.\n",
+ Group,
+ Group->PathToBeUsed));
+
+ return Group->PathToBeUsed;
+
+}
+
+
+NTSTATUS
+DsmpUpdateTargetPortGroupDevicesStates(
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN DSM_DEVICE_STATE NewState
+ )
+/*++
+
+Routine Description:
+
+ This routine will update the target port group and all its appropriate
+ devInfos (ones not in remove pending, removed, or invalidated) with the
+ new state. The ALUAState will only be updated, NOT the real State.
+ Caller needs to update the real State based on the current LB policy.
+
+ Note: This should be called with DsmContext Lock held and should only be
+ called after a SetTargetPortGroups request was sent down.
+
+Arguments:
+
+ TargetPortGroup - TargetPortGroup whose state and deviceInfos need to be
+ updated
+
+ NewState - The new state
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PLIST_ENTRY entry = NULL;
+ PDSM_TARGET_PORT_LIST_ENTRY targetPort = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupDevicesStates (TPG %p): Entering function.\n",
+ TargetPortGroup));
+
+ if (!TargetPortGroup) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupDevicesStates (TPG %p): Invalid TPG passed in.\n",
+ TargetPortGroup));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpUpdateTargetPortGroupDevicesStates;
+ }
+
+ //
+ // First update TPG's asymmetric access state.
+ //
+ TargetPortGroup->AsymmetricAccessState = NewState;
+
+ //
+ // Now update state of each of the devices belonging to this TPG.
+ //
+ for (entry = TargetPortGroup->TargetPortList.Flink;
+ entry != &TargetPortGroup->TargetPortList;
+ entry = entry->Flink) {
+
+ targetPort = CONTAINING_RECORD(entry, DSM_TARGET_PORT_LIST_ENTRY, ListEntry);
+ NT_ASSERT(targetPort);
+
+ if (targetPort) {
+
+ PLIST_ENTRY deviceEntry;
+ PDSM_TARGET_PORT_DEVICELIST_ENTRY tp_device;
+
+ for (deviceEntry = targetPort->TP_DeviceList.Flink;
+ deviceEntry != &targetPort->TP_DeviceList;
+ deviceEntry = deviceEntry->Flink) {
+
+ tp_device = CONTAINING_RECORD(deviceEntry,
+ DSM_TARGET_PORT_DEVICELIST_ENTRY,
+ ListEntry);
+
+ if (tp_device) {
+
+ tp_device->DeviceInfo->ALUAState = NewState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupDevicesStates (TPG %p): Updated device %p alua state to %x.\n",
+ TargetPortGroup,
+ tp_device->DeviceInfo,
+ tp_device->DeviceInfo->ALUAState));
+ }
+ }
+ }
+ }
+
+__Exit_DsmpUpdateTargetPortGroupDevicesStates:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpUpdateTargetPortGroupDevicesStates (TPG %p): Exiting function with status %x.\n",
+ TargetPortGroup,
+ status));
+
+ return status;
+}
+
+
+VOID
+DsmpIncrementCounters(
+ _In_ PDSM_FAILOVER_GROUP FailGroup,
+ _In_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ ULONG bytes = 0;
+ PCDB cdb = NULL;
+ ULONG cdbLength = 0;
+ BOOLEAN isReadWrite = FALSE;
+ ULONGLONG lastLba = 0;
+ ULONG numBlocks = 0;
+ ULONGLONG startLba = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpIncrementCounters (FOG %p): Entering function.\n",
+ FailGroup));
+
+ if (Srb) {
+
+ cdb = SrbGetCdb(Srb);
+
+ if (cdb && DsmIsReadWrite(cdb->AsByte[0])) {
+
+ isReadWrite = TRUE;
+ }
+ }
+
+ InterlockedIncrement(&FailGroup->NumberOfRequestsInFlight);
+
+ //
+ // Update counters that apply to read/write requests
+ //
+ if (isReadWrite) {
+
+ bytes = SrbGetDataTransferLength(Srb);
+
+ InterlockedExchangeAdd64((LONGLONG volatile*)&FailGroup->OutstandingBytesOfIO, bytes);
+
+ cdbLength = SrbGetCdbLength(Srb);
+
+ if (cdbLength == 16) {
+
+ REVERSE_BYTES_QUAD(&startLba, &cdb->CDB16.LogicalBlock);
+ REVERSE_BYTES(&numBlocks, &cdb->CDB16.TransferLength);
+
+ } else {
+
+ REVERSE_BYTES(&startLba, &cdb->CDB10.LogicalBlockByte0);
+ REVERSE_BYTES_SHORT(&numBlocks, &cdb->CDB10.TransferBlocksMsb);
+ }
+
+ lastLba = startLba + numBlocks - 1;
+
+ InterlockedExchange64((LONGLONG volatile*)&FailGroup->LastLba, lastLba);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpIncrementCounters (FOG %p): Exiting function.\n",
+ FailGroup));
+
+ return;
+}
+
+
+BOOLEAN
+DsmpDecrementCounters(
+ _In_ PDSM_FAILOVER_GROUP FailGroup,
+ _In_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ ULONG bytes = 0;
+ PCDB cdb = NULL;
+ BOOLEAN isReadWrite = FALSE;
+ BOOLEAN isDeletionEligible = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpDecrementCounters (FOG %p): Entering function.\n",
+ FailGroup));
+
+ if (Srb) {
+
+ cdb = SrbGetCdb(Srb);
+
+ if (cdb && DsmIsReadWrite(cdb->AsByte[0])) {
+
+ isReadWrite = TRUE;
+ }
+ }
+
+ //
+ // Update counters that apply to read/write requests
+ //
+ if (isReadWrite) {
+
+ bytes = SrbGetDataTransferLength(Srb);
+
+ InterlockedExchangeAdd64((LONGLONG volatile*)&FailGroup->OutstandingBytesOfIO, -(LONGLONG)bytes);
+ }
+
+ NT_ASSERT(FailGroup->NumberOfRequestsInFlight > 0);
+ if (InterlockedCompareExchange(&FailGroup->NumberOfRequestsInFlight, 0, 0) > 0) {
+
+ if(InterlockedDecrement(&FailGroup->NumberOfRequestsInFlight) == 0){
+
+ //
+ // If the inflight requests on the path is zero, if needed path can be removed.
+ //
+ isDeletionEligible = TRUE;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpDecrementCounters (FOG %p): Exiting function.\n",
+ FailGroup));
+
+ return isDeletionEligible;
+}
+
+
+PDSM_FAILOVER_GROUP
+DsmpGetPath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmList,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine will pick a path, for processing a request, based
+ on the current LoadBalance policy that is set.
+
+ N.B: This routine must be called with DSM Context Lock held in Shared mode.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ DsmList - List of DSM Ids sent by MPIO
+ Srb - The read/write/verify request
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ FailOver Group that should be used for processing the request
+--*/
+{
+ //
+ // Algorithm:
+ // ==========
+ // Failover-only:
+ // --------------
+ // If symmetric LUA (ie. ALUA not supported, or symmetric LUA using ALUA semantics (viz. storage reports
+ // implicit-only transitions and all TPGs in A/O):
+ // One path AO, <- this will be the only path used for I/O
+ // M paths in SB, <- one of these will be made active on failure of the above active path
+ // Rest of the paths Failed (Invalidated/PendingRemove/Removed)
+ //
+ // If ALUA:
+ // One path AO, <- this will be the only path used for I/O
+ // M paths in AU, SB or UA <- one of these made active on failover. Pref: AU > SB > UA. Also, controller affinity.
+ // Rest of the paths Failed
+ //
+ // Automatic failback will happen only if Preferred path has been set.
+ // This is the only policy that will support failback.
+ //
+ // Round-Robin:
+ // ------------
+ // If symmetric LUA:
+ // N paths AO, <- round robin among these
+ // Rest of the paths Failed
+ //
+ // If ALUA:
+ // Round Robin policy not supported since all paths can't be in A/O state.
+ //
+ // Round-Robin With Subset:
+ // ------------------------
+ // If symmetric LUA:
+ // N paths AO, <- round robin among these
+ // M paths SB, <- if no active paths left, make one of these active
+ // Rest of the paths Failed
+ //
+ // If ALUA:
+ // N paths AO, <- round robin among these (NOTE: paths in AU not considered)
+ // M paths AU, SB or UA <- if no active paths, make subset of these active (based on TPG states after transition)
+ // Rest of the paths Failed
+ //
+ // Least-Queue Depth:
+ // ------------------
+ // If symmetric LUA:
+ // N paths AO, <- one with least outstanding I/O is chosen
+ // Rest of the paths Failed
+ //
+ // If ALUA:
+ // N paths AO, <- one with least outstanding I/O is chosen
+ // M paths AU, SB or UA <- if no AO paths available, subset of these become active (based on TPG
+ // states after transition) - one with least outstanding I/O is chosen.
+ // Rest of the paths Failed
+ //
+ // Least-Weighted:
+ // ---------------
+ // If symmetric LUA:
+ // N paths AO, <- every path has an associated weight, path with least weight is used.
+ // Rest of the paths Failed
+ //
+ // If ALUA:
+ // N paths AO,
+ // M paths AU, SB or UA <- if no AO paths available, subset of these become active (based on TPG
+ // states after transition) - path with least weight used.
+ // Rest of the paths Failed
+ //
+ // Least-Blocks:
+ // -------------
+ // If symmetric LUA:
+ // N paths AO, <- one with least cumulative outstanding IO is chosen
+ // Rest of the paths Failed
+ //
+ // If ALUA:
+ // N paths AO, <- one with least cumulative outstanding IO is chosen
+ // M paths AU, SB or UA <- if no AO paths available, subset of these become active (based on TPG
+ // states after transition) - one with least cumulative outstanding is chosen.
+ //
+ // Actual implementation of algorithm happens in the following routines: DsmpGetAnyActivePath,
+ // DsmpGetActivePathToBeUsed, flavors of DsmpSetLBForPathXXX.
+ //
+
+ PDSM_FAILOVER_GROUP failGroup = NULL;
+ PDSM_DEVICE_INFO deviceInfo = DsmList->IdList[0];
+ PDSM_GROUP_ENTRY groupEntry;
+ ULONG inx = 0;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+ UNREFERENCED_PARAMETER(SpecialHandlingFlag);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Entering function.\n",
+ DsmList));
+
+ if (!(DsmList->Count && deviceInfo)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Called with no available paths.\n",
+ DsmList));
+
+ goto __Exit_DsmpGetPath;
+ }
+
+ groupEntry = deviceInfo->Group;
+ DSM_ASSERT(groupEntry->GroupSig == DSM_GROUP_SIG);
+
+ switch (groupEntry->LoadBalanceType) {
+
+ case DSM_LB_FAILOVER:
+ case DSM_LB_WEIGHTED_PATHS: {
+
+ //
+ // For FailOverOnly there is only one active path so we can
+ // just grab it from the cached location and go with it.
+ // For LeastWeightPath we always choose the lowest weighted
+ // one so we grab that and go
+ //
+ failGroup = groupEntry->PathToBeUsed;
+
+ break;
+ }
+
+ case DSM_LB_ROUND_ROBIN:
+ case DSM_LB_ROUND_ROBIN_WITH_SUBSET: {
+
+ PDSM_DEVICE_INFO candidateDevice = NULL;
+ PDSM_GROUP_ENTRY newGroup = NULL;
+ ULONG newPath;
+ BOOLEAN foundPath = FALSE;
+ ULONG jnx = 0;
+ ULONG counter = 0;
+ BOOLEAN reset = FALSE;
+
+ for (inx = 0; inx < DsmList->Count; inx++) {
+
+ deviceInfo = DsmList->IdList[inx];
+
+ if (!(deviceInfo && DsmpIsDeviceInitialized(deviceInfo) && DsmpIsDeviceUsable(deviceInfo) && DsmpIsDeviceUsablePR(deviceInfo))) {
+
+ continue;
+ }
+
+
+ if (deviceInfo->FailGroup == groupEntry->PathToBeUsed) {
+
+ //
+ // We've reached the devInfo that corresponds to the path
+ // that we should be using. If this devInfo is not in the
+ // right state to be used, we need to find the first candidate
+ // starting from this one to satisfy the request.
+ // To play it safe, we may have already considered a previous
+ // devInfo to be a candidate, that now needs to be reset to
+ // the one that we now find.
+ //
+ reset = TRUE;
+ }
+
+#if DBG
+ if (deviceInfo->TargetPortGroup && !DsmpIsDeviceFailedState(deviceInfo->State)) {
+
+ if (deviceInfo->State != deviceInfo->ALUAState) {
+
+ DSM_ASSERT(groupEntry->LoadBalanceType == DSM_LB_ROUND_ROBIN_WITH_SUBSET &&
+ deviceInfo->State == DSM_DEV_ACTIVE_UNOPTIMIZED &&
+ deviceInfo->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED);
+ }
+ }
+#endif
+
+ if (deviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ if (!candidateDevice || reset) {
+
+ candidateDevice = deviceInfo;
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Candidate device %p.\n",
+ DsmList,
+ candidateDevice));
+
+ jnx = inx;
+ }
+
+ if (!groupEntry->PathToBeUsed || deviceInfo->FailGroup == groupEntry->PathToBeUsed) {
+
+ //
+ // The devInfo that corresponds to the path that we were
+ // supposed to use, is in a state that makes it usable.
+ // So we've found our devInfo.
+ //
+ InterlockedExchangePointer(&(groupEntry->PathToBeUsed), (PVOID)deviceInfo->FailGroup);
+ foundPath = TRUE;
+ candidateDevice = NULL;
+
+ break;
+ }
+ }
+ }
+
+ if (!foundPath) {
+
+ if (candidateDevice) {
+
+ InterlockedExchangePointer(&(groupEntry->PathToBeUsed), (PVOID)candidateDevice->FailGroup);
+ inx = jnx;
+ candidateDevice = NULL;
+
+ } else {
+
+ inx = 0;
+ }
+ }
+
+ failGroup = groupEntry->PathToBeUsed;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Path to be used is %p.\n",
+ DsmList,
+ groupEntry->PathToBeUsed));
+
+ //
+ // The current chosen path is given by failGroup. Find the next path
+ // that should be chosen in the RoundRobin policy. Start with the
+ // device at index inx + 1, and look for the one with Active state.
+ //
+ for (counter = 0, jnx = inx + 1;
+ counter < DsmList->Count && !newGroup;
+ counter++, jnx++) {
+
+ newPath = jnx % DsmList->Count;
+
+ deviceInfo = DsmList->IdList[newPath];
+
+ if (!(deviceInfo && DsmpIsDeviceInitialized(deviceInfo) && DsmpIsDeviceUsable(deviceInfo) && DsmpIsDeviceUsablePR(deviceInfo))) {
+
+ continue;
+ }
+
+
+ if (deviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ newGroup = deviceInfo->Group;
+ DSM_ASSERT(newGroup == groupEntry);
+
+ InterlockedExchangePointer(&(newGroup->PathToBeUsed), (PVOID)deviceInfo->FailGroup);
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): New Path is %p.\n",
+ DsmList,
+ newGroup->PathToBeUsed));
+
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case DSM_LB_DYN_LEAST_QUEUE_DEPTH: {
+
+ LONG leastQueueDepth = 0x7FFFFFFF;
+
+ for (inx = 0; inx < DsmList->Count; inx++) {
+
+ deviceInfo = DsmList->IdList[inx];
+
+ if (!(deviceInfo && DsmpIsDeviceInitialized(deviceInfo) && DsmpIsDeviceUsable(deviceInfo) && DsmpIsDeviceUsablePR(deviceInfo))) {
+
+ continue;
+ }
+
+
+ if (deviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED &&
+ deviceInfo->FailGroup->NumberOfRequestsInFlight < leastQueueDepth) {
+
+ leastQueueDepth = deviceInfo->FailGroup->NumberOfRequestsInFlight;
+ failGroup = deviceInfo->FailGroup;
+ }
+ }
+
+ if (failGroup) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Path to be used for LQD is %p.\n",
+ DsmList,
+ failGroup));
+
+ } else {
+
+ //
+ // For ALUA storage there are two cases where we are left with no
+ // TPG in the A/O state:
+ // 1) On storage that supports implicit transitions, a transition
+ // was initiated that left no TPG in the A/O state.
+ // 2) On storage that has explicit only transitions enabled, we tried
+ // making at least one path as A/O and failed. This can happen,
+ // for example, when STPG fails because this initiator is not
+ // registered or does not hold exclusive reservation over the
+ // target.
+ //
+ // For such storages, we should return some path instead of just
+ // failing the I/O. The path will likely be an A/U path until the
+ // storage does a transition to make a TPG A/O.
+ //
+ if (!DsmpIsSymmetricAccess((PDSM_DEVICE_INFO)DsmList->IdList[0])) {
+
+ //
+ // Use the same path as the one used for the previous request.
+ //
+ failGroup = groupEntry->PathToBeUsed;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpGetPath (DsmIds %p): Using same path (FOG %p) as previous request for LQD.\n",
+ DsmList,
+ failGroup));
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Failed to find a path for LQD.\n",
+ DsmList));
+ }
+ }
+
+ break;
+ }
+
+ case DSM_LB_LEAST_BLOCKS: {
+
+ ULONG bytes = 0;
+ PCDB cdb = NULL;
+ ULONG cdbLength = 0;
+ BOOLEAN isRead = FALSE;
+ BOOLEAN isWrite = FALSE;
+ PDSM_FAILOVER_GROUP lastPathUsed = groupEntry->PathToBeUsed;
+ ULONGLONG leastOutstandingIO = MAXULONGLONG;
+ ULONGLONG startLba = 0;
+
+ //
+ // Use the last path under the following conditions:
+ //
+ // 1. This is not a read/write request or
+ // 2. This is a read/write request and
+ // a. The request is sequential and
+ // b. The cache is not exhausted
+ //
+
+ if (Srb) {
+
+ cdb = SrbGetCdb(Srb);
+
+ if (cdb && DsmIsReadRequest(cdb->AsByte[0])) {
+
+ isRead = TRUE;
+ }
+
+ if (cdb && DsmIsWriteRequest(cdb->AsByte[0])) {
+
+ isWrite = TRUE;
+ }
+ }
+
+ if (isRead || isWrite) {
+
+ if (groupEntry->UseCacheForLeastBlocks) {
+
+ bytes = SrbGetDataTransferLength(Srb);
+
+ cdbLength = SrbGetCdbLength(Srb);
+
+ if (cdbLength == 16) {
+
+ REVERSE_BYTES_QUAD(&startLba, &cdb->CDB16.LogicalBlock);
+
+ } else {
+
+ REVERSE_BYTES(&startLba, &cdb->CDB10.LogicalBlockByte0);
+ }
+
+ //
+ // Check if:
+ // 1. The IO is sequential, AND
+ // 2. It is either:
+ // a. read request, OR
+ // b. write request and outstanding bytes will be within the cache limit
+ //
+ if ((lastPathUsed != NULL) &&
+ (startLba >= lastPathUsed->LastLba) &&
+ ((isRead) ||
+ (isWrite && lastPathUsed->OutstandingBytesOfIO + bytes <= groupEntry->CacheSizeForLeastBlocks))) {
+
+ failGroup = groupEntry->PathToBeUsed;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Sequential IO, so using same path %p for LeastBlocks.\n",
+ DsmList,
+ failGroup));
+ }
+ }
+
+ } else {
+ //
+ // The request is neither a read nor a write so use the same path.
+ //
+ failGroup = groupEntry->PathToBeUsed;
+ }
+
+ if (!failGroup) {
+
+ //
+ // Choose whichever Active/Optimized path has the least outstanding bytes.
+ //
+ for (inx = 0; inx < DsmList->Count; inx++) {
+
+ deviceInfo = DsmList->IdList[inx];
+
+ if (!(deviceInfo && DsmpIsDeviceInitialized(deviceInfo) && DsmpIsDeviceUsable(deviceInfo) && DsmpIsDeviceUsablePR(deviceInfo))) {
+
+ continue;
+ }
+
+
+ if (deviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED &&
+ deviceInfo->FailGroup->OutstandingBytesOfIO < leastOutstandingIO) {
+
+ leastOutstandingIO = deviceInfo->FailGroup->OutstandingBytesOfIO;
+ failGroup = deviceInfo->FailGroup;
+ }
+ }
+ }
+
+ if (failGroup) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Path to be used for LeastBlocks is %p.\n",
+ DsmList,
+ failGroup));
+
+ } else {
+
+ //
+ // For ALUA storage there are two cases where we are left with no
+ // TPG in the A/O state:
+ // 1) On storage that supports implicit transitions, a transition
+ // was initiated that left no TPG in the A/O state.
+ // 2) On storage that has explicit only transitions enabled, we tried
+ // making at least one path as A/O and failed. This can happen,
+ // for example, when STPG fails because this initiator is not
+ // registered or does not hold exclusive reservation over the
+ // target.
+ //
+ // For such storages, we should return some path instead of just
+ // failing the I/O. The path will likely be an A/U path until the
+ // storage does a transition to make a TPG A/O.
+ //
+ if (!DsmpIsSymmetricAccess((PDSM_DEVICE_INFO)DsmList->IdList[0])) {
+
+ //
+ // Use the same path as the one used for the previous request.
+ //
+ failGroup = groupEntry->PathToBeUsed;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpGetPath (DsmIds %p): Using same path (FOG %p) as previous request for LeastBlocks.\n",
+ DsmList,
+ failGroup));
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Failed to find a path for LeastBlocks.\n",
+ DsmList));
+ }
+ }
+
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Invalid LB Type %d set for group %p.\n",
+ DsmList,
+ groupEntry->LoadBalanceType,
+ groupEntry));
+
+ DSM_ASSERT(FALSE);
+
+ break;
+ }
+ }
+
+__Exit_DsmpGetPath:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpGetPath (DsmIds %p): Exiting function with failGroup %p.\n",
+ DsmList,
+ failGroup));
+
+ return failGroup;
+}
+
+
+PVOID
+DsmpGetPathIdFromPassThroughPath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmList,
+ _In_ PIRP Irp,
+ _Inout_ IN OUT NTSTATUS *Status
+ )
+/*++
+
+Routine Description:
+
+ This routine will pick the path that corresponds to the PathId
+ in the mpio pass through structure.
+
+ NOTE: Caller must ensure that the IRP is either MPTP or MPTPD.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ DsmList - List of DSM Ids sent by MPIO
+ Irp - The MPTP or MPTPD request
+ Status - Returned status
+
+Return Value:
+
+ The PathId to which the request should be sent
+--*/
+{
+ PDSM_FAILOVER_GROUP failGroup = NULL;
+ PDSM_GROUP_ENTRY groupEntry;
+ PDSM_DEVICE_INFO deviceInfo;
+ ULONG inx = 0;
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+ KIRQL irql;
+ PVOID newPath = NULL;
+ BOOLEAN found = FALSE;
+ BOOLEAN useScsiAddress = FALSE;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
+ UCHAR pathId = 0;
+ UCHAR targetId = 0;
+ UCHAR portNumber = 0;
+ ULONGLONG mpioPathId = 0;
+
+#if DBG
+ BOOLEAN useMpioPathId = FALSE;
+#endif
+
+ //
+ // Extract the parameters from the passthrough based on the bitness of the
+ // process (32 or 64) and the type of passthrough (legacy or extended).
+ //
+#if defined (_WIN64)
+ if (IoIs32bitProcess(Irp)) {
+
+ if (DsmpIsMPIOPassThroughEx(controlCode)) {
+ PMPIO_PASS_THROUGH_PATH32_EX mpioPassThroughPath32 = (PMPIO_PASS_THROUGH_PATH32_EX)(Irp->AssociatedIrp.SystemBuffer);
+ PSCSI_PASS_THROUGH32_EX passThrough32 = (PSCSI_PASS_THROUGH32_EX)((PUCHAR)mpioPassThroughPath32 + mpioPassThroughPath32->PassThroughOffset);
+
+ useScsiAddress = mpioPassThroughPath32->Flags & MPIO_IOCTL_FLAG_USE_SCSIADDRESS;
+ #if DBG
+ useMpioPathId = mpioPassThroughPath32->Flags & MPIO_IOCTL_FLAG_USE_PATHID;
+ #endif
+
+ if (useScsiAddress) {
+ PSTOR_ADDRESS address;
+ if (passThrough32->StorAddressOffset < sizeof(SCSI_PASS_THROUGH_EX) ||
+ passThrough32->StorAddressLength < sizeof(STOR_ADDRESS)) {
+ *Status = STATUS_INVALID_PARAMETER;
+ return NULL;
+ }
+ address = (PSTOR_ADDRESS)((PUCHAR)passThrough32 + passThrough32->StorAddressOffset);
+ if (address->Type != STOR_ADDRESS_TYPE_BTL8 ||
+ address->AddressLength < STOR_ADDR_BTL8_ADDRESS_LENGTH) {
+ *Status = STATUS_INVALID_PARAMETER;
+ return NULL;
+ }
+ pathId = ((PSTOR_ADDR_BTL8)address)->Path;
+ targetId = ((PSTOR_ADDR_BTL8)address)->Target;
+ portNumber = mpioPassThroughPath32->PortNumber;
+ } else {
+ mpioPathId = mpioPassThroughPath32->MpioPathId;
+ }
+
+ } else {
+ PMPIO_PASS_THROUGH_PATH32 mpioPassThroughPath32 = (PMPIO_PASS_THROUGH_PATH32)(Irp->AssociatedIrp.SystemBuffer);
+
+ useScsiAddress = mpioPassThroughPath32->Flags & MPIO_IOCTL_FLAG_USE_SCSIADDRESS;
+ #if DBG
+ useMpioPathId = mpioPassThroughPath32->Flags & MPIO_IOCTL_FLAG_USE_PATHID;
+ #endif
+
+ if (useScsiAddress) {
+ pathId = mpioPassThroughPath32->PassThrough.PathId;
+ targetId = mpioPassThroughPath32->PassThrough.TargetId;
+ portNumber = mpioPassThroughPath32->PortNumber;
+ } else {
+ mpioPathId = mpioPassThroughPath32->MpioPathId;
+ }
+ }
+ } else
+#endif
+ if (DsmpIsMPIOPassThroughEx(controlCode)) {
+ PMPIO_PASS_THROUGH_PATH_EX mpioPassThroughPath = (PMPIO_PASS_THROUGH_PATH_EX)(Irp->AssociatedIrp.SystemBuffer);
+ PSCSI_PASS_THROUGH_EX passThrough = (PSCSI_PASS_THROUGH_EX)((PUCHAR)mpioPassThroughPath + mpioPassThroughPath->PassThroughOffset);
+
+ useScsiAddress = mpioPassThroughPath->Flags & MPIO_IOCTL_FLAG_USE_SCSIADDRESS;
+ #if DBG
+ useMpioPathId = mpioPassThroughPath->Flags & MPIO_IOCTL_FLAG_USE_PATHID;
+ #endif
+
+ if (useScsiAddress) {
+ PSTOR_ADDRESS address;
+ if (passThrough->StorAddressOffset < sizeof(SCSI_PASS_THROUGH_EX) ||
+ passThrough->StorAddressLength < sizeof(STOR_ADDRESS)) {
+ *Status = STATUS_INVALID_PARAMETER;
+ return NULL;
+ }
+ address = (PSTOR_ADDRESS)((PUCHAR)passThrough + passThrough->StorAddressOffset);
+ if (address->Type != STOR_ADDRESS_TYPE_BTL8 ||
+ address->AddressLength < STOR_ADDR_BTL8_ADDRESS_LENGTH) {
+ *Status = STATUS_INVALID_PARAMETER;
+ return NULL;
+ }
+ pathId = ((PSTOR_ADDR_BTL8)address)->Path;
+ targetId = ((PSTOR_ADDR_BTL8)address)->Target;
+ portNumber = mpioPassThroughPath->PortNumber;
+ } else {
+ mpioPathId = mpioPassThroughPath->MpioPathId;
+ }
+ } else {
+ PMPIO_PASS_THROUGH_PATH mpioPassThroughPath = (PMPIO_PASS_THROUGH_PATH)(Irp->AssociatedIrp.SystemBuffer);
+
+ useScsiAddress = mpioPassThroughPath->Flags & MPIO_IOCTL_FLAG_USE_SCSIADDRESS;
+ #if DBG
+ useMpioPathId = mpioPassThroughPath->Flags & MPIO_IOCTL_FLAG_USE_PATHID;
+ #endif
+
+ if (useScsiAddress) {
+ pathId = mpioPassThroughPath->PassThrough.PathId;
+ targetId = mpioPassThroughPath->PassThrough.TargetId;
+ portNumber = mpioPassThroughPath->PortNumber;
+ } else {
+ mpioPathId = mpioPassThroughPath->MpioPathId;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpGetPathIdFromPassThroughPath (DsmIds %p): Entering function.\n",
+ DsmList));
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ deviceInfo = DsmList->IdList[0];
+ groupEntry = deviceInfo->Group;
+ DSM_ASSERT(groupEntry->GroupSig == DSM_GROUP_SIG);
+ //
+ // useMpioPathId is BOOLEAN (0 or 1) since MPIO_IOCTL_FLAG_USE_PATHID = 1
+ // But since MPIO_IOCTL_FLAG_USE_SCSIADDRESS = 0x2,
+ // useScsiAddress could have a value of 2 if set. Use logical NOT to make boolean before comparing below
+ //
+ DSM_ASSERT(useMpioPathId == !useScsiAddress);
+
+ for (inx = 0; inx < DSM_MAX_PATHS; inx++) {
+
+ deviceInfo = groupEntry->DeviceList[inx];
+
+ if (deviceInfo) {
+
+ failGroup = deviceInfo->FailGroup;
+
+ if (failGroup) {
+
+ if (useScsiAddress) {
+
+ if (portNumber == deviceInfo->ScsiAddress->PortNumber &&
+ pathId == deviceInfo->ScsiAddress->PathId &&
+ targetId == deviceInfo->ScsiAddress->TargetId) {
+
+ found = TRUE;
+ break;
+ }
+ } else {
+
+ NT_ASSERT(useMpioPathId);
+
+ if ((ULONGLONG)((ULONG_PTR)(failGroup->PathId)) == mpioPathId) {
+
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ if (found) {
+
+ newPath = failGroup->PathId;
+ status = STATUS_SUCCESS;
+
+ //
+ // This should not affect the next path chosen based on the
+ // current LB policy, so do NOT update groupEntry->PathToBeUsed
+ //
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpGetPathIdFromPassThroughPath (DsmIds %p): Failed to get corresponding path.\n",
+ DsmList));
+ }
+
+ if (Status) {
+
+ *Status = status;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpGetPathIdFromPassThroughPath (DsmIds %p): Exiting function with path %p and status %x.\n",
+ DsmList,
+ newPath,
+ status));
+
+ return newPath;
+}
+
+
+BOOLEAN
+DsmpShouldRetryTPGRequest(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ )
+/*++
+
+Routine Description:
+
+ This routine determines if a Report/Set TargetPortGroup request (sent either
+ as a passThrough or as an IRP_MJ_SCSI) needs to be retried.
+
+Arguments:
+
+ SenseData - Pointer to Sense Data information buffer.
+ SenseDataSize - Size of the passed in sense data buffer.
+
+Return Value:
+
+ TRUE if sense information indicates a retry-able error, else FALSE.
+
+--*/
+{
+ BOOLEAN retry = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryTPGRequest (SenseData %p): Entering function.\n",
+ SenseData));
+
+ //
+ // Two types of conditions need to be retried:
+ // 1. Asymmetric Access State Changed
+ // 2. Asymmetric Access State Transition
+ //
+
+ //
+ // Check if asymmetric access state changed
+ //
+ retry = DsmpShouldRetryPassThroughRequest(SenseData, SenseDataSize);
+ if (!retry) {
+
+ BOOLEAN validSense = FALSE;
+ UCHAR senseKey = 0;
+ UCHAR addSenseCode = 0;
+ UCHAR addSenseCodeQualifier = 0;
+
+ validSense = ScsiGetSenseKeyAndCodes(SenseData,
+ SenseDataSize,
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &addSenseCode,
+ &addSenseCodeQualifier);
+ if (validSense) {
+
+ if (senseKey == SCSI_SENSE_NOT_READY) {
+
+ switch (addSenseCode) {
+ case SCSI_ADSENSE_LUN_NOT_READY: {
+
+ //
+ // Check if asymmetric access state transitioning
+ //
+ if (addSenseCodeQualifier == SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_TRANSITION) {
+
+ retry = TRUE;
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: {
+
+ if (addSenseCodeQualifier == SCSI_SENSEQ_REPORTED_LUNS_DATA_CHANGED) {
+
+ retry = TRUE;
+ }
+ break;
+ }
+
+ default: {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryTPGRequest (SenseData %p): AddSenseCode %x. Not retrying.\n",
+ SenseData,
+ addSenseCode));
+
+ retry = FALSE;
+ break;
+ }
+ }
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryTPGRequest (SenseData %p): Sense data size %d not big enough.\n",
+ SenseData,
+ SenseDataSize));
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryTPGRequest (SenseData %p): Exiting function with retry %x.\n",
+ SenseData,
+ retry));
+
+ return retry;
+}
+
+
+BOOLEAN
+DsmpIsDeviceRemoved(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ )
+/*++
+
+Routine Description:
+
+ This routine evaluate Sense Data and determine if LUN is available or not.
+
+Arguments:
+
+ SenseData - Pointer to Sense Data information buffer.
+ SenseDataSize - Size of the passed in sense data buffer.
+
+Return Value:
+
+ TRUE if device is no longer available, else FALSE.
+
+--*/
+{
+ BOOLEAN validSense = FALSE;
+ UCHAR senseKey = 0;
+ UCHAR addSenseCode = 0;
+ UCHAR addSenseCodeQualifier = 0;
+ BOOLEAN bRemoved = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpIsDeviceRemoved (SenseData %p): Entering function.\n",
+ SenseData));
+
+ validSense = ScsiGetSenseKeyAndCodes(SenseData,
+ SenseDataSize,
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &addSenseCode,
+ &addSenseCodeQualifier);
+
+ if (validSense) {
+ //
+ // SPC 3 6.25 suggests response should follow Test Unit Ready responses
+ // For now, we accept Ileegal Request as an indication of device not in available
+ // state.
+ //
+ if (senseKey == SCSI_SENSE_ILLEGAL_REQUEST) {
+
+ ASSERT(addSenseCodeQualifier == 0); //LOGICAL UNIT NOT SUPPORTED
+
+ bRemoved = TRUE;
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpIsDeviceRemoved (SenseData %p): SenseKey %x AddSenseCode %x. Remove %x\n",
+ SenseData,
+ senseKey,
+ addSenseCode,
+ bRemoved));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpIsDeviceRemoved (SenseData %p): Exiting function. Removed %x.\n",
+ SenseData,
+ bRemoved));
+
+ return bRemoved;
+}
+
+
+BOOLEAN
+DsmpReservationCommand(
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb
+ )
+/*++
+
+Routine Description:
+
+ This routine examines the DeviceIoControlCode and Srb OpCode to determine
+ if this is PR request.
+
+Arguments:
+
+ Irp - The Irp containing Srb.
+ Srb - The current non-read/write Srb.
+
+Return Value:
+
+ TRUE - If it's a special-case command (some reservation-handling request).
+
+--*/
+{
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ UCHAR opCode = 0;
+ BOOLEAN isReservationCommand = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpReservationCommand (Irp %p): Entering function.\n",
+ Irp));
+
+ //
+ // Ensure it's a scsi request before checking the opcode.
+ //
+ if (irpStack->MajorFunction == IRP_MJ_SCSI) {
+
+ PCDB cdb = SrbGetCdb(Srb);
+ if (cdb != NULL) {
+ opCode = cdb->AsByte[0];
+
+ if (opCode == SCSIOP_PERSISTENT_RESERVE_IN || opCode == SCSIOP_PERSISTENT_RESERVE_OUT) {
+
+ //
+ // Set or release a reservation.
+ //
+ isReservationCommand = TRUE;
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpReservationCommand (Irp %p): Exiting function - IsReservationCmd %x.\n",
+ Irp,
+ isReservationCommand));
+
+ return isReservationCommand;
+}
+
+
+BOOLEAN
+DsmpMpioPassThroughPathCommand(
+ _In_ IN PIRP Irp
+ )
+/*++
+
+Routine Description:
+
+ This routine examines the DeviceIoControlCode to determine whether this is
+ either a mpio pass through or a mpio pass through direct. If so, it needs
+ to be handled via a specific path indicated by the caller.
+
+Arguments:
+
+ Irp - The Irp.
+
+Return Value:
+
+ TRUE - If it is either MPTP or MPTPD.
+ FALSE - Otherwise.
+
+--*/
+{
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG ioctlCode;
+ BOOLEAN isMPTPCommand = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpMpioPassThroughPathCommand (Irp %p): Entering function.\n",
+ Irp));
+
+ if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
+
+ //
+ // Check whether this is a MPTP, MPTPD, or an extended flavor.
+ //
+ ioctlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
+
+ if (ioctlCode == IOCTL_MPIO_PASS_THROUGH_PATH ||
+ ioctlCode == IOCTL_MPIO_PASS_THROUGH_PATH_DIRECT ||
+ ioctlCode == IOCTL_MPIO_PASS_THROUGH_PATH_EX ||
+ ioctlCode == IOCTL_MPIO_PASS_THROUGH_PATH_DIRECT_EX) {
+
+ isMPTPCommand = TRUE;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpMpioPassThroughPathCommand (Irp %p): Exiting function - IsMpioPassThruPathCmd %!bool!.\n",
+ Irp,
+ isMPTPCommand));
+
+ return isMPTPCommand;
+}
+
+
+VOID
+DsmpRequestComplete(
+ _In_ IN PVOID DsmId,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PVOID DsmContext
+ )
+/*++
+
+Routine Description:
+
+ This routine is called from mpio's completion routine when the Irp
+ has been completed by the port driver. Currently, it updates some counters
+ and free's the context back to the look-aside list.
+
+Arguments:
+
+ DsmIds - The collection of DSM IDs that pertain to the MPDISK.
+ Irp - Irp containing SRB.
+ Srb - Scsi request block
+ DsmContext - DSM context given to MPIO during initialization
+
+Return Value:
+
+ NONE
+
+--*/
+
+{
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ PDSM_CONTEXT dsmContext = (PDSM_CONTEXT)DsmContext;
+ UCHAR opCode = 0xFF;
+ ULONG dataTransferLength = 0;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PDSM_FAILOVER_GROUP failGroup = irpStack->Parameters.Others.Argument3;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpRequestComplete (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ DSM_ASSERT(DsmContext);
+
+ if (Srb) {
+ PCDB cdb = SrbGetCdb(Srb);
+ if (cdb) {
+ opCode = cdb->AsByte[0];
+ }
+ dataTransferLength = SrbGetDataTransferLength(Srb);
+ }
+
+ //
+ // Extract the interesting bits from the context struct.
+ //
+
+ if (failGroup) {
+
+ if (DsmpDecrementCounters(failGroup, Srb)) {
+
+ //
+ // If there are no requests on a path that is supposed to be removed, remove it now.
+ //
+ if (failGroup->State == DSM_FG_PENDING_REMOVE) {
+
+ KIRQL oldIrql;
+
+ NT_ASSERT(failGroup->Count == 0);
+
+ oldIrql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+ RemoveEntryList(&failGroup->ListEntry);
+ InterlockedDecrement((LONG volatile*)&dsmContext->NumberStaleFOGroups);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpRequestComplete (DevInfo %p): Removing FOGroup %p with path %p.\n",
+ DsmId,
+ failGroup,
+ failGroup->PathId));
+
+ DsmpFreePool(failGroup);
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), oldIrql);
+ }
+ }
+ }
+
+ //
+ // Note: We use the deviceInfo passed in since the one saved off in the
+ // context may be stale in the case of a retried I/O
+ //
+ if (deviceInfo) {
+
+ //
+ // If statistics gathering is enabled update the inflight request count
+ // for this device-path pairing.
+ //
+ if (!dsmContext->DisableStatsGathering) {
+
+ //
+ // Indicate one less request on this device.
+ // Update the path that on which the increment was done.
+ //
+ if (InterlockedCompareExchange((LONG volatile*)&deviceInfo->NumberOfRequestsInProgress, 0, 0) > 0) {
+ InterlockedDecrement(&(deviceInfo->NumberOfRequestsInProgress));
+ }
+ }
+
+ //
+ // If statistics gathering is enabled, we are interested in read/write requests
+ //
+ if (!dsmContext->DisableStatsGathering) {
+
+ //
+ // If it's a read or a write, update the stats.
+ // Use the path that was cached during dispatch.
+ //
+ if (DsmIsReadRequest(opCode)) {
+
+ if (deviceInfo->DeviceStats.NumberReads <= MAXULONG) {
+
+ InterlockedIncrement((LONG volatile*)&deviceInfo->DeviceStats.NumberReads);
+ }
+
+ if ((MAXULONGLONG - dataTransferLength) > deviceInfo->DeviceStats.BytesRead) {
+
+ deviceInfo->DeviceStats.BytesRead += dataTransferLength;
+
+ } else {
+
+ deviceInfo->DeviceStats.BytesRead = MAXULONGLONG;
+ }
+
+ } else if (DsmIsWriteRequest(opCode)) {
+
+ if (deviceInfo->DeviceStats.NumberWrites <= MAXULONG) {
+
+ InterlockedIncrement((LONG volatile*)&deviceInfo->DeviceStats.NumberWrites);
+ }
+
+ if ((MAXULONGLONG - dataTransferLength) > deviceInfo->DeviceStats.BytesWritten) {
+
+ deviceInfo->DeviceStats.BytesWritten += dataTransferLength;
+
+ } else {
+
+ deviceInfo->DeviceStats.BytesWritten = MAXULONGLONG;
+ }
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpRequestComplete (DevInfo %p): Exiting function.\n",
+ DsmId));
+
+ return;
+}
+
+
+NTSTATUS
+DsmpRegisterPersistentReservationKeys(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN BOOLEAN Register
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to build and send down the request to register
+ or unregister the persistent reservation keys to the device down
+ the path given by DeviceInfo.
+
+Arguments:
+
+ DeviceInfo - Device-path pair to use for sending down the request
+ Register - Flag to indicate whether to register or unregister the keys.
+
+Return Value:
+
+ STATUS_SUCCESS on success, else appropriate failure code.
+
+--*/
+{
+ PSCSI_PASS_THROUGH_WITH_BUFFERS passThrough = NULL;
+ PCDB cdb;
+ PPRO_PARAMETER_LIST parameters;
+ IO_STATUS_BLOCK ioStatus;
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG length;
+ PDSM_DEVICE_INFO deviceInfo = DeviceInfo;
+ PDSM_GROUP_ENTRY group;
+ ULONGLONG saKey;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): Entering function - Register = %x.\n",
+ deviceInfo,
+ Register));
+
+ group = DeviceInfo->Group;
+
+ NT_ASSERT(group && group->PRKeyValid);
+
+ if (DeviceInfo->State >= DSM_DEV_FAILED) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): Unusable - state %d.\n",
+ deviceInfo,
+ deviceInfo->State));
+
+ status = STATUS_UNSUCCESSFUL;
+ goto __Exit_DsmpRegisterPersistentReservationKeys;
+ }
+
+ //
+ // Build a pass through command to process Persistent Reserve Out
+ // for registering the device.
+ //
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ passThrough = DsmpAllocatePool(NonPagedPoolNx,
+ length,
+ DSM_TAG_PASS_THRU);
+ if (!passThrough) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): Failed to allocate memory for persistent reserve.\n",
+ deviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegisterPersistentReservationKeys;
+ }
+
+ REVERSE_BYTES_QUAD(&saKey, &group->PersistentReservationRegisteredKey);
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): Attempting PR-Out SA %u, Type %u, Scope %u, PR-Key %I64x.\n",
+ deviceInfo,
+ group->PRServiceAction,
+ group->PRType,
+ group->PRScope,
+ saKey));
+
+__RetryRequest:
+
+ //
+ // Build the cdb to reserve the device (Logical Unit). The type of reservation
+ // scope and service action is whatever cluster service provided at the time of
+ // sending down registration to this device before this particular path was available.
+ //
+ cdb = (PCDB) passThrough->ScsiPassThrough.Cdb;
+ cdb->PERSISTENT_RESERVE_OUT.OperationCode = SCSIOP_PERSISTENT_RESERVE_OUT;
+ cdb->PERSISTENT_RESERVE_OUT.ServiceAction = group->PRServiceAction;
+ cdb->PERSISTENT_RESERVE_OUT.Scope = group->PRScope;
+ cdb->PERSISTENT_RESERVE_OUT.Type = group->PRType;
+ cdb->PERSISTENT_RESERVE_OUT.ParameterListLength[1] = sizeof(PRO_PARAMETER_LIST);
+
+ passThrough->ScsiPassThrough.Length = sizeof(SCSI_PASS_THROUGH);
+ passThrough->ScsiPassThrough.CdbLength = 10;
+ passThrough->ScsiPassThrough.SenseInfoLength = SPTWB_SENSE_LENGTH;
+ passThrough->ScsiPassThrough.DataIn = 0;
+ passThrough->ScsiPassThrough.DataTransferLength = sizeof(PRO_PARAMETER_LIST);
+ passThrough->ScsiPassThrough.TimeOutValue = 20;
+ passThrough->ScsiPassThrough.SenseInfoOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, SenseInfoBuffer);
+ passThrough->ScsiPassThrough.DataBufferOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, DataBuffer);
+
+ parameters = (PPRO_PARAMETER_LIST)(passThrough->DataBuffer);
+
+ //
+ // Copy the persistent reservation key given by cluster service to
+ // Service Action Reservation Key. This key will be registered
+ // with the device.
+ //
+ // Set ServiceActionReservationKey to the well-known key if we are registering.
+ // Note that to unregister ServiceActionReservationKey needs to be set to 0.
+ //
+ if (Register) {
+
+ RtlCopyMemory(parameters->ServiceActionReservationKey, group->PersistentReservationRegisteredKey, 8);
+
+ } else {
+
+ RtlCopyMemory(parameters->ReservationKey, group->PersistentReservationRegisteredKey, 8);
+ RtlZeroMemory(parameters->ServiceActionReservationKey, 8);
+ }
+
+ DsmSendDeviceIoControlSynchronous(IOCTL_SCSI_PASS_THROUGH,
+ DeviceInfo->TargetObject,
+ passThrough,
+ passThrough,
+ length,
+ length,
+ FALSE,
+ &ioStatus);
+
+ status = ioStatus.Status;
+
+ if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_GOOD) && (NT_SUCCESS(ioStatus.Status))) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): Persistent Reserve (Register Key) succeeded using %p.\n",
+ deviceInfo,
+ DeviceInfo));
+
+ } else {
+
+ PUCHAR senseData;
+ UCHAR senseInfoLength;
+
+ senseData = (PUCHAR)(passThrough->SenseInfoBuffer);
+ senseInfoLength = passThrough->ScsiPassThrough.SenseInfoLength;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): DevInfo %p, Register keys (%d): NTStatus %x, ScsiStatus %x.\n",
+ deviceInfo,
+ DeviceInfo,
+ Register,
+ ioStatus.Status,
+ passThrough->ScsiPassThrough.ScsiStatus));
+
+ if (DsmpShouldRetryPassThroughRequest((PVOID)senseData, senseInfoLength)) {
+
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ RtlZeroMemory(passThrough, length);
+
+ goto __RetryRequest;
+
+ } else if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): Will change success to error status for register\n",
+ deviceInfo));
+
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ //
+ // Free the passthrough + data buffer.
+ //
+ DsmpFreePool(passThrough);
+
+__Exit_DsmpRegisterPersistentReservationKeys:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpRegisterPersistentReservationKeys (DevInfo %p): Exiting function with status %x.\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+
+BOOLEAN
+DsmpShouldRetryPassThroughRequest(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ )
+/*++
+
+Routine Description:
+
+ This routine determines if a passthrough request needs to be retried based on the
+ information in the passed in sense data.
+
+Arguments:
+
+ SenseData - Pointer to Sense Data information buffer.
+ SenseDataSize - Size of the passed in sense data buffer.
+
+Return Value:
+
+ TRUE if sense information indicates a retry-able error, else FALSE.
+
+--*/
+{
+ BOOLEAN validSense = FALSE;
+ UCHAR senseKey = 0;
+ UCHAR addSenseCode = 0;
+ BOOLEAN retry = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPassThroughRequest (SenseData %p): Entering function.\n",
+ SenseData));
+
+#if DBG
+ if (SenseDataSize > 0) {
+
+ ULONG inx;
+ PUCHAR senseInfo;
+
+
+ senseInfo = (PUCHAR) SenseData;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPassThroughRequest (SenseData %p): Sense info length %d. Sense Info : ",
+ SenseData,
+ SenseDataSize));
+
+ for (inx = 0; inx < SenseDataSize; inx++) {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "%x ",
+ senseInfo[inx]));
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "\n"));
+ }
+#endif
+
+ validSense = ScsiGetSenseKeyAndCodes(SenseData,
+ SenseDataSize,
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &addSenseCode,
+ NULL);
+ if (validSense) {
+ if (senseKey == SCSI_SENSE_UNIT_ATTENTION) {
+
+ switch (addSenseCode) {
+ case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED:
+ case SCSI_ADSENSE_BUS_RESET:
+ case SCSI_ADSENSE_PARAMETERS_CHANGED: {
+ retry = TRUE;
+ break;
+ }
+
+ default: {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPassThroughRequest (SenseData %p): AddSenseCode %x. Not retrying.\n",
+ SenseData,
+ addSenseCode));
+
+ retry = FALSE;
+ break;
+ }
+ }
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPassThroughRequest (SenseData %p): Sense data size %d not big enough.\n",
+ SenseData,
+ SenseDataSize));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPassThroughRequest (SenseData %p): Exiting function with retry %x.\n",
+ SenseData,
+ retry));
+
+ return retry;
+}
+
+
+BOOLEAN
+DsmpShouldRetryPersistentReserveCommand(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ )
+/*++
+
+Routine Description:
+
+ This routine determines if a a PR request needs to be retried based on the
+ information in the passed in sense data.
+
+Arguments:
+
+ SenseData - Pointer to Sense Data information buffer.
+ SenseDataSize - Size of the passed in sense data buffer.
+
+Return Value:
+
+ TRUE if sense information indicates a retry-able error, else FALSE.
+
+--*/
+{
+ BOOLEAN retry = FALSE;
+ BOOLEAN validSense = FALSE;
+ UCHAR senseKey = 0;
+ UCHAR addSenseCode = 0;
+ UCHAR addSenseCodeQualifier = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPersistentReserveCommand (SenseData %p): Entering function.\n",
+ SenseData));
+
+ retry = DsmpShouldRetryPassThroughRequest(SenseData, SenseDataSize);
+
+ if (!retry) {
+ validSense = ScsiGetSenseKeyAndCodes(SenseData,
+ SenseDataSize,
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &addSenseCode,
+ &addSenseCodeQualifier);
+ if (validSense) {
+
+ //
+ // If the TPG is in transitioning state, retry the request
+ //
+ if ((senseKey == SCSI_SENSE_UNIT_ATTENTION || senseKey == SCSI_SENSE_NOT_READY) &&
+ (addSenseCode == SCSI_ADSENSE_LUN_NOT_READY &&
+ addSenseCodeQualifier == SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_TRANSITION)) {
+
+ retry = TRUE;
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPersistentReserveCommand (SenseData %p): Sense data size %d not big enough.\n",
+ SenseData,
+ SenseDataSize));
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpShouldRetryPersistentReserveCommand (SenseData %p): Exiting function with retry %x.\n",
+ SenseData,
+ retry));
+
+ return retry;
+}
+
+
+VOID
+DsmpAllowStandbyPathsToRest(
+ _In_ PDSM_GROUP_ENTRY Group
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when a new path is available for a device
+ and has a desired state of ACTIVE_O. Since this will be an ACTIVE_O
+ path we see if there are any paths with a desired state of Standby
+ but that are currently active. These paths can be safely moved by
+ to standby.
+
+ This routine assumes that the lock is held
+
+Arguements:
+
+ Group is the multipath group
+
+Return Value:
+
+ None
+--*/
+{
+ PDSM_DEVICE_INFO existingDeviceInfo;
+ ULONG inx;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpAllowStandbyPathsToRest (Group %p): Entering function.\n",
+ Group));
+
+ for (inx = 0; inx < Group->NumberDevices; inx++) {
+
+ existingDeviceInfo = Group->DeviceList[inx];
+
+ if ((existingDeviceInfo->DesiredState == DSM_DEV_STANDBY) &&
+ (existingDeviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED)) {
+
+ existingDeviceInfo->State = DSM_DEV_STANDBY;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpAllowStandbyPathsToRest (Group %p): DevInfo %p changed to state %d at %d\n",
+ Group,
+ existingDeviceInfo,
+ existingDeviceInfo->State,
+ __LINE__));
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpAllowStandbyPathsToRest (Group %p): Exiting function.\n",
+ Group));
+ return;
+}
+
+
+PDSM_DEVICE_INFO
+DsmpGetAnyActivePath(
+ _In_ PDSM_GROUP_ENTRY Group,
+ _In_ BOOLEAN Exception,
+ _In_opt_ PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine will return an active path from the list
+
+ This routine assumes that the DSM lock is held
+
+Arguements:
+
+ Group is the multipath group
+ Exception - if TRUE, indicates that the returned devInfo must not be the same
+ as the one passed in.
+ DeviceInfo - the must-not-match devInfo. Valid parameter only if Exception is
+ TRUE.
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ active path or NULL
+
+--*/
+{
+ PDSM_DEVICE_INFO existingDeviceInfo;
+ PDSM_DEVICE_INFO candidateDevInfo = NULL;
+ ULONG inx;
+
+ UNREFERENCED_PARAMETER(SpecialHandlingFlag);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetAnyActivePath (Group %p): Entering function.\n",
+ Group));
+
+ for (inx = 0; inx < DSM_MAX_PATHS; inx++) {
+
+ existingDeviceInfo = Group->DeviceList[inx];
+
+ if (existingDeviceInfo &&
+ existingDeviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED &&
+ DsmpIsDeviceInitialized(existingDeviceInfo) &&
+ DsmpIsDeviceUsable(existingDeviceInfo) &&
+ DsmpIsDeviceUsablePR(existingDeviceInfo)) {
+
+
+ if (Exception && existingDeviceInfo == DeviceInfo) {
+ continue;
+ }
+
+ candidateDevInfo = existingDeviceInfo;
+ break;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetAnyActivePath (Group %p): Exiting function with DevInfo %p\n",
+ Group,
+ candidateDevInfo));
+
+ return candidateDevInfo;
+}
+
+
+PDSM_DEVICE_INFO
+DsmpGetActivePathToBeUsed(
+ _In_ PDSM_GROUP_ENTRY Group,
+ _In_ BOOLEAN Symmetric,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine will return an active path from the list that should
+ be the next one used by the DSM
+
+ This routine assumes that the DSM lock is held
+
+Arguements:
+
+ Group is the multipath group
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ active path or NULL
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetActivePathToBeUsed (Group %p): Entering function.\n",
+ Group));
+
+ deviceInfo = NULL;
+
+ switch (Group->LoadBalanceType) {
+
+ case DSM_LB_LEAST_BLOCKS:
+ case DSM_LB_DYN_LEAST_QUEUE_DEPTH: {
+
+ //
+ // Since we choose the path with the smallest queue or cumulative size in
+ // DsmpGetPath, we just pick any path now
+ //
+
+ // fall through
+ }
+
+ case DSM_LB_ROUND_ROBIN_WITH_SUBSET:
+ case DSM_LB_ROUND_ROBIN: {
+
+ //
+ // For RR and RRS we just pick any active path to start with
+ // and the DsmpGetPath will do the round robining
+ //
+ }
+
+ case DSM_LB_FAILOVER: {
+
+ deviceInfo = DsmpGetAnyActivePath(Group, FALSE, NULL, SpecialHandlingFlag);
+
+ break;
+ }
+
+ case DSM_LB_WEIGHTED_PATHS: {
+
+ PDSM_DEVICE_INFO workDeviceInfo;
+ ULONG weight = (ULONG) -1;
+ ULONG inx;
+
+ for (inx = 0; inx < Group->NumberDevices; inx++) {
+
+ workDeviceInfo = Group->DeviceList[inx];
+
+ if ((workDeviceInfo) &&
+ (DsmpIsDeviceInitialized(workDeviceInfo)) &&
+ (DsmpIsDeviceUsable(workDeviceInfo)) &&
+ (DsmpIsDeviceUsablePR(workDeviceInfo)) &&
+ (workDeviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED) &&
+ (workDeviceInfo->PathWeight < weight)) {
+
+ //
+ // We found a path that is active and is at
+ // the lowest weight. Remember it.
+ //
+ weight = workDeviceInfo->PathWeight;
+
+ deviceInfo = workDeviceInfo;
+ }
+ }
+
+ break;
+ }
+
+ default: {
+
+ break;
+ }
+ }
+
+ if (!deviceInfo && !Symmetric) {
+
+ //
+ // In the case of implicit transitions, it is possible that a TPG hasn't yet
+ // been made A/O. So instead of not setting any path, fall back to using some
+ // other path. IO sent down this path may fail, but will be retried in
+ // InterpretError(). Hopefully by then, at least one TPG will have transitioned
+ // to A/O state.
+ //
+ // The same argument holds true if the storage supports both implicit and
+ // explicit transitions, since it is possible that after we explicitly changed
+ // the TPG states, an implicit transition left us with no path in A/O state.
+ //
+ // In the case of explicit only transitions, we tried making at least one path
+ // as A/O and failed. This can happen, for example, when STPG fails because
+ // this initiator is not registered or does not hold exclusive reservation over
+ // the target. Instead of not using any path, we can consider a path in A/U state,
+ // A/U being just a functional path state.
+ //
+ BOOLEAN sendTPG = FALSE;
+
+ deviceInfo = DsmpFindStandbyPathToActivateALUA(Group, &sendTPG, SpecialHandlingFlag);
+
+ if ((deviceInfo != NULL) &&
+ ((deviceInfo->ALUASupport != DSM_DEVINFO_ALUA_EXPLICIT) ||
+ ((deviceInfo->ALUASupport == DSM_DEVINFO_ALUA_EXPLICIT) &&
+ (deviceInfo->State <= DSM_DEV_ACTIVE_UNOPTIMIZED)))) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpGetActivePathToBeUsed (Group %p): Using best alternative candidate device %p\n",
+ Group,
+ deviceInfo));
+ } else {
+
+ deviceInfo = NULL;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpGetActivePathToBeUsed (Group %p): No active/alternative path available for group\n",
+ Group));
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetActivePathToBeUsed (Group %p): Exiting function with devInfo %p.\n",
+ Group,
+ deviceInfo));
+
+ return deviceInfo;
+}
+
+
+PDSM_DEVICE_INFO
+DsmpFindStandbyPathToActivate(
+ _In_ PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine will find another path in the group that is active
+
+ This routine assumes that the DSM lock is held
+
+ This is used by devices that support symmetric LUA.
+
+Arguements:
+
+ Group is the multipath group
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Standby path or NULL if no standby path is available
+
+--*/
+{
+ PDSM_DEVICE_INFO existingDeviceInfo;
+ ULONG inx;
+ PDSM_DEVICE_INFO candidateDevInfo = NULL;
+
+ UNREFERENCED_PARAMETER(SpecialHandlingFlag);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindStandbyPathToActivate(Group %p): Entering function.\n",
+ Group));
+
+ for (inx = 0; inx < Group->NumberDevices; inx++) {
+
+ existingDeviceInfo = Group->DeviceList[inx];
+
+ if (existingDeviceInfo &&
+ existingDeviceInfo->State == DSM_DEV_STANDBY &&
+ DsmpIsDeviceInitialized(existingDeviceInfo) &&
+ DsmpIsDeviceUsable(existingDeviceInfo) &&
+ DsmpIsDeviceUsablePR(existingDeviceInfo)) {
+
+ //
+ // If we don't as yet have a candidate, pick the first available one.
+ // However, our preference is one that is through the preferred TPG.
+ //
+ if (!candidateDevInfo ||
+ existingDeviceInfo->TargetPortGroup && existingDeviceInfo->TargetPortGroup->Preferred) {
+
+ candidateDevInfo = existingDeviceInfo;
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindStandbyPathToActivate (Group %p): Exiting function with devInfo %p.\n",
+ Group,
+ candidateDevInfo));
+
+ return candidateDevInfo;
+}
+
+
+PDSM_DEVICE_INFO
+DsmpFindStandbyPathToActivateALUA(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PBOOLEAN SendTPG,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine will find another path in the group that is active
+
+ This is used by devices that don't support symmetric LUA.
+
+ N.B: This routine MUST be called with DsmContextLock held in either Shared or
+ Exclusive mode.
+
+Arguements:
+
+ Group is the multipath group
+ SendTPG - output parameter that indicates if TPG command need to be sent down.
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Standby path or NULL if no standby path is available
+
+--*/
+{
+ PDSM_DEVICE_INFO existingDeviceInfo;
+ ULONG inx;
+ PDSM_DEVICE_INFO candidateDevInfo = NULL;
+
+ UNREFERENCED_PARAMETER(SpecialHandlingFlag);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindStandbyPathToActivateALUA (Group %p): Entering function.\n",
+ Group));
+
+ for (inx = 0; inx < Group->NumberDevices; inx++) {
+
+ existingDeviceInfo = Group->DeviceList[inx];
+
+ //
+ // The candidate for making A/O obviously mustn't be in a failed state
+ // and should have a path assigned.
+ //
+ if (existingDeviceInfo &&
+ !DsmpIsDeviceFailedState(existingDeviceInfo->State) &&
+ DsmpIsDeviceInitialized(existingDeviceInfo) &&
+ DsmpIsDeviceUsable(existingDeviceInfo) &&
+ DsmpIsDeviceUsablePR(existingDeviceInfo)) {
+
+ //
+ // If we don't have any candidate currently, choose the very first
+ // one that is in a non-failure state, regardless of what state it
+ // may be in.
+ //
+ if (!candidateDevInfo) {
+
+ candidateDevInfo = existingDeviceInfo;
+ *SendTPG = TRUE;
+ }
+
+ //
+ // Might as well use one that the Admin desires for to be in A/O
+ //
+ if (existingDeviceInfo->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ //
+ // However, such a devInfo is not a better candidate if our candidate
+ // devInfo is also one that the Admin desires be in A/O, and it is
+ // through a preferred TPG.
+ //
+ if (!(existingDeviceInfo->DesiredState == candidateDevInfo->DesiredState &&
+ candidateDevInfo->TargetPortGroup->Preferred)) {
+
+ candidateDevInfo = existingDeviceInfo;
+ *SendTPG = TRUE;
+ }
+ }
+
+ //
+ // Check if the current one is at least better than the candidate.
+ //
+ if (DsmpIsBetterDeviceState(candidateDevInfo->State, existingDeviceInfo->State)) {
+
+ candidateDevInfo = existingDeviceInfo;
+ *SendTPG = TRUE;
+ }
+
+ //
+ // We found one that we may have just masked as non-A/O. This is the
+ // best option as we don't have to send down an STPG.
+ //
+ if (existingDeviceInfo->ALUAState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ candidateDevInfo = existingDeviceInfo;
+ *SendTPG = FALSE;
+ break;
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindStandbyPathToActivateALUA (Group %p): Exiting function with devInfo %p.\n",
+ Group,
+ candidateDevInfo));
+
+ return candidateDevInfo;
+}
+
+
+PDSM_DEVICE_INFO
+DsmpFindStandbyPathInAlternateTpgALUA(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine will find another path in the group that is not in the same
+ TPG as the passed in DeviceInfo.
+
+ This routine assumes that the DSM lock is held
+
+ This is used by devices that support ALUA.
+
+Arguements:
+
+ Group is the multipath group
+ DeviceInfo is the devInfo whose TPG must not be matched
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Standby path not in same TPG as passed in DeviceInfo
+ or NULL if no standby path is available
+
+--*/
+{
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup = DeviceInfo->TargetPortGroup;
+ PDSM_DEVICE_INFO existingDeviceInfo;
+ ULONG inx;
+ PDSM_DEVICE_INFO candidateDevInfo = NULL;
+
+ UNREFERENCED_PARAMETER(SpecialHandlingFlag);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindStandbyPathInAlternateTpgALUA (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ for (inx = 0; inx < Group->NumberDevices; inx++) {
+
+ existingDeviceInfo = Group->DeviceList[inx];
+
+ //
+ // We only care about deviceInfo if TPG is different
+ //
+ if (existingDeviceInfo && existingDeviceInfo->TargetPortGroup != targetPortGroup) {
+
+ //
+ // The candidate for making A/O obviously mustn't be in a failed state
+ // and must be initialized
+ //
+ if (!DsmpIsDeviceFailedState(existingDeviceInfo->State) &&
+ DsmpIsDeviceInitialized(existingDeviceInfo) &&
+ DsmpIsDeviceUsable(existingDeviceInfo) &&
+ DsmpIsDeviceUsablePR(existingDeviceInfo)) {
+
+ //
+ // If we don't have any candidate currently, choose the very first
+ // one that is in a non-failure state, regardless of what state it
+ // may be in.
+ //
+ if (!candidateDevInfo) {
+
+ candidateDevInfo = existingDeviceInfo;
+ continue;
+ }
+
+ //
+ // Check if the current one is at least better than the candidate.
+ //
+ if (DsmpIsBetterDeviceState(candidateDevInfo->State, existingDeviceInfo->State)) {
+
+ candidateDevInfo = existingDeviceInfo;
+ }
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFindStandbyPathInAlternateTpgALUA (DevInfo %p): Exiting function with devInfo %p.\n",
+ DeviceInfo,
+ candidateDevInfo));
+
+ return candidateDevInfo;
+}
+
+
+NTSTATUS
+DsmpSetLBForDsmPolicyAdjustment(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when a change is made to the DSM-wide default
+ load balance policy. It goes through each LUN representation (ie. Group
+ entry) and updates the appropriate ones (ie. ones for which the policy
+ was not chosen based on VID/PID or because of an explicit settings on
+ the LUN). It also then updates the path states in accordance with the
+ new LB policy.
+
+Arguements:
+
+ DsmContext is the DSM context
+ LoadBalanceType is the new load balance policy to be applied
+ PreferredPath is the preferred failback path to be used (applicable only
+ if LB policy is Failover)
+
+Return Value:
+
+ Success
+
+--*/
+
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL oldIrql;
+ PLIST_ENTRY entry;
+ ULONG groupIndex = 0;
+ ULONG devInfoIndex;
+ PDSM_GROUP_ENTRY group;
+ PDSM_DEVICE_INFO devInfo;
+ DSM_LOAD_BALANCE_TYPE newLoadBalancePolicy;
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetLBForDsmPolicyAdjustment (DsmContext %p): Entering function.\n",
+ DsmContext));
+
+ oldIrql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ for (entry = DsmContext->GroupList.Flink; entry != &(DsmContext->GroupList); entry = entry->Flink, groupIndex++) {
+
+ group = CONTAINING_RECORD(entry, DSM_GROUP_ENTRY, ListEntry);
+
+ newLoadBalancePolicy = LoadBalanceType;
+
+ //
+ // Only LUNs that don't have their policy explicitly set
+ // and ones that don't have it set based on VID/PID are
+ // of interest to us here.
+ //
+ if (group->LBPolicySelection == DSM_DEFAULT_LB_POLICY_ALUA_CAPABILITY ||
+ group->LBPolicySelection == DSM_DEFAULT_LB_POLICY_DSM_WIDE) {
+
+ //
+ // Also, if the caller is trying to clear the DSM-wide
+ // default policy, then we don't even care about those
+ // LUNs whose policies were not set using this value.
+ //
+ if (newLoadBalancePolicy < DSM_LB_FAILOVER &&
+ group->LBPolicySelection != DSM_DEFAULT_LB_POLICY_DSM_WIDE) {
+
+ continue;
+ }
+
+ //
+ // If the DSM-wide setting is being cleared, we need to fall back
+ // to using the default based on the array's ALUA capabilities.
+ //
+ if (newLoadBalancePolicy < DSM_LB_FAILOVER) {
+
+ newLoadBalancePolicy = DSM_LB_ROUND_ROBIN;
+ group->PreferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_ALUA_CAPABILITY;
+
+
+ } else {
+
+ //
+ // Since a new policy has been selected for the DSM-wide
+ // one, it needs to be applied to this LUN.
+ //
+ group->PreferredPath = (ULONGLONG)((ULONG_PTR)PreferredPath);
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_DSM_WIDE;
+ }
+
+ //
+ // If Round Robin is set and ALUA is enabled, we need to change the
+ // policy to Round Robin with Subset.
+ //
+ if (!DsmpIsSymmetricAccess(group->DeviceList[0]) && newLoadBalancePolicy == DSM_LB_ROUND_ROBIN) {
+
+ newLoadBalancePolicy = DSM_LB_ROUND_ROBIN_WITH_SUBSET;
+ }
+
+ //
+ // Finally set the new load balance policy.
+ //
+ group->LoadBalanceType = newLoadBalancePolicy;
+
+ //
+ // Path states need to be updated in accordance with the new policy.
+ //
+ for (devInfoIndex = 0; devInfoIndex < DSM_MAX_PATHS; devInfoIndex++) {
+
+ devInfo = group->DeviceList[devInfoIndex];
+ DsmpSetNewDefaultLBPolicy(DsmContext, devInfo, group->LoadBalanceType, SpecialHandlingFlag);
+ }
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), oldIrql);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetLBForDsmPolicyAdjustment (DsmContext %p): Exiting function with status %x\n",
+ DsmContext,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLBForVidPidPolicyAdjustment(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PWSTR TargetHardwareId,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when a change is made to the default load balance
+ policy for a VID/PID. It goes through each LUN representation (ie. Group
+ entry) and updates the appropriate ones (ie. ones for which the policy
+ was not because of an explicit settings on the LUN). It also then updates
+ the path states in accordance with the new LB policy.
+
+Arguements:
+
+ DsmContext is the DSM context
+ TargetHardwareId is the VID/PID whose matching LUNs policy need to be updated
+ LoadBalanceType is the new load balance policy to be applied
+ PreferredPath is the preferred failback path to be used (applicable only
+ if LB policy is Failover)
+
+Return Value:
+
+ Success
+
+--*/
+
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL oldIrql;
+ PLIST_ENTRY entry;
+ ULONG groupIndex = 0;
+ ULONG devInfoIndex;
+ PDSM_GROUP_ENTRY group;
+ PDSM_DEVICE_INFO devInfo;
+ DSM_LOAD_BALANCE_TYPE dsmLoadBalanceType;
+ ULONGLONG dsmPreferredPath;
+ BOOLEAN useDsmLBSettings = FALSE;
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetLBForVidPidPolicyAdjustment (%ws): Entering function.\n",
+ TargetHardwareId));
+
+ status = DsmpQueryDsmLBPolicyFromRegistry(&dsmLoadBalanceType, &dsmPreferredPath);
+
+ if (NT_SUCCESS(status)) {
+
+ useDsmLBSettings = TRUE;
+
+ } else {
+
+ if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ status = STATUS_SUCCESS;
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_WMI,
+ "DsmpSetLBForVidPidPolicyAdjustment (%ws): MSDSM-wide default LB policy not set.\n",
+ TargetHardwareId));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLBForVidPidPolicyAdjustment (%ws): Failed to query MSDMS-wide default LB setting. Status %x.\n",
+ TargetHardwareId,
+ status));
+ }
+ }
+
+ oldIrql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ for (entry = DsmContext->GroupList.Flink; entry != &(DsmContext->GroupList); entry = entry->Flink, groupIndex++) {
+
+ group = CONTAINING_RECORD(entry, DSM_GROUP_ENTRY, ListEntry);
+
+ //
+ // Only LUNs that don't have their policy explicitly set
+ // are of interest to us here.
+ //
+ if (group->LBPolicySelection < DSM_DEFAULT_LB_POLICY_LUN_EXPLICIT) {
+
+ //
+ // Figure out if this LUN matches the VID/PID of interest
+ // Device not of interest if it doesn't match the passed in target ID
+ //
+ if (wcscmp(group->HardwareId, TargetHardwareId) != 0) {
+
+ continue;
+ }
+
+ //
+ // Also, if the caller is trying to clear the VID/PID
+ // default policy, then we don't even care about those
+ // LUNs whose policies were not set using this value.
+ //
+ if (LoadBalanceType < DSM_LB_FAILOVER &&
+ group->LBPolicySelection != DSM_DEFAULT_LB_POLICY_VID_PID) {
+
+ continue;
+ }
+
+ //
+ // If the VID/PID setting is being cleared, we need to fall back
+ // to using the DSM-wide default policy if it has been set, else
+ // we need to use the default based on the array's ALUA capabilities.
+ //
+ if (LoadBalanceType < DSM_LB_FAILOVER) {
+
+ if (useDsmLBSettings) {
+
+ //
+ // Even if the MSDSM-wide policy is specified as RR, if the storage
+ // is ALUA, we can't have the policy as RR, so we'll change it to
+ // RRWS instead.
+ //
+ if (!DsmpIsSymmetricAccess(group->DeviceList[0]) && dsmLoadBalanceType == DSM_LB_ROUND_ROBIN) {
+
+ group->LoadBalanceType = DSM_LB_ROUND_ROBIN_WITH_SUBSET;
+
+ } else {
+
+ group->LoadBalanceType = dsmLoadBalanceType;
+ }
+
+ group->PreferredPath = (ULONGLONG)((ULONG_PTR)dsmPreferredPath);
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_DSM_WIDE;
+
+ } else {
+
+ //
+ // Default LB type:
+ // is Round Robin if ALUA is not supported, or if ALUA support is implicit but access is symmetric,
+ // else Round Robin With Subset (since in ALUA, all paths aren't in A/O).
+ //
+ if (DsmpIsSymmetricAccess(group->DeviceList[0])) {
+
+ group->LoadBalanceType = DSM_LB_ROUND_ROBIN;
+
+ } else {
+
+ group->LoadBalanceType = DSM_LB_ROUND_ROBIN_WITH_SUBSET;
+ }
+
+
+ group->PreferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_ALUA_CAPABILITY;
+ }
+
+ } else {
+
+ //
+ // Since a new policy has been selected for the DSM-wide
+ // one, it needs to be applied to this LUN.
+ //
+ // However, if the VID/PID policy is specified as RR but the storage
+ // is ALUA, we can't have the policy as RR, so we'll change it to
+ // RRWS instead.
+ //
+ if (!DsmpIsSymmetricAccess(group->DeviceList[0]) && LoadBalanceType == DSM_LB_ROUND_ROBIN) {
+
+ group->LoadBalanceType = DSM_LB_ROUND_ROBIN_WITH_SUBSET;
+
+ } else {
+
+ group->LoadBalanceType = LoadBalanceType;
+ }
+
+ group->PreferredPath = (ULONGLONG)((ULONG_PTR)PreferredPath);
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_VID_PID;
+ }
+
+ //
+ // Path states need to be updated in accordance with the new policy.
+ //
+ for (devInfoIndex = 0; devInfoIndex < DSM_MAX_PATHS; devInfoIndex++) {
+
+ devInfo = group->DeviceList[devInfoIndex];
+ DsmpSetNewDefaultLBPolicy(DsmContext, devInfo, group->LoadBalanceType, SpecialHandlingFlag);
+ }
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), oldIrql);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetLBForVidPidPolicyAdjustment (%ws): Exiting function with status %x\n",
+ TargetHardwareId,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetNewDefaultLBPolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_opt_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine is called to adjust the path state of an instance of a
+ LUN for which a new default load balance policy was applied
+ following an admin request for such a change.
+
+ This routine must be called with spinlock held.
+
+Arguements:
+
+ DsmContext is the DSM context
+ DeviceInfo is the device info on which the new path state needs to be set
+ LoadBalanceType is the load balance policy in accordance with which the path state needs to be adjusted
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetNewDefaultLBPolicy (DevInfo %p): Entering function\n",
+ DeviceInfo));
+
+ if (!DeviceInfo) {
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpSetNewDefaultLBPolicy;
+ }
+
+ if (!(DsmpIsDeviceInitialized(DeviceInfo) && DsmpIsDeviceUsable(DeviceInfo) && DsmpIsDeviceUsablePR(DeviceInfo)) ||
+ DsmpIsDeviceFailedState(DeviceInfo->State)) {
+
+ status = STATUS_UNSUCCESSFUL;
+ goto __Exit_DsmpSetNewDefaultLBPolicy;
+ }
+
+
+ group = DeviceInfo->Group;
+
+ if (!DsmpIsSymmetricAccess(DeviceInfo)) {
+
+ DsmpAdjustDeviceStatesALUA(group, NULL, SpecialHandlingFlag);
+
+ } else {
+
+ switch (LoadBalanceType) {
+
+ //
+ // For failover, it is important the right path is
+ // chosen, ie. preferred path needs to be taken into
+ // consideration.
+ //
+ case DSM_LB_FAILOVER: {
+
+ DsmpSetLBForPathArrival(DsmContext, DeviceInfo, SpecialHandlingFlag);
+
+ break;
+ }
+
+ //
+ // For all other policies, the state must be A/O.
+ //
+ default: {
+
+ DeviceInfo->PreviousState = DeviceInfo->State;
+ DeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ break;
+ }
+ }
+ }
+
+__Exit_DsmpSetNewDefaultLBPolicy:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetNewDefaultLBPolicy (DevInfo %p): Exiting function with status %x\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLBForPathArrival(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO NewDeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when a new path arrives for a multipath
+ group that doesn't support ALUA. The routine will set the path
+ to the appropriate state and fix up the other paths state if
+ they need to change.
+
+ This is used by devices NOT supporting ALUA.
+
+ This routine must be called with spinlock held.
+
+Arguements:
+
+ DsmContext is the DSM context
+ NewDeviceInfo is the device info for the newly arrived path
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+ PDSM_DEVICE_INFO deviceInfo;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (DevInfo %p): Entering function.\n",
+ NewDeviceInfo));
+
+ group = NewDeviceInfo->Group;
+
+ if (!(DsmpIsDeviceInitialized(NewDeviceInfo) && DsmpIsDeviceUsable(NewDeviceInfo) && DsmpIsDeviceUsablePR(NewDeviceInfo))) {
+
+ //
+ // Bad device instance. Nothing can be done about it.
+ //
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_UNDETERMINED;
+
+ NT_ASSERT(NewDeviceInfo->FailGroup == NULL);
+
+ goto __Exit_DsmpSetLBForPathArrival;
+ }
+
+
+ if (group->NumberDevices == 1) {
+
+ //
+ // if this is the only device for the group then we will always
+ // be active as every group must have at least one active path
+ //
+ if (NewDeviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ //
+ // All's good
+ //
+ goto __Exit_DsmpSetLBForPathArrival;
+
+ } else {
+
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (DevInfo %p): State changed to %d at %d\n",
+ NewDeviceInfo,
+ NewDeviceInfo->State,
+ __LINE__));
+
+ goto __Exit_DsmpSetLBForPathArrival;
+ }
+
+ switch(group->LoadBalanceType) {
+ case DSM_LB_FAILOVER: {
+
+ //
+ // Get the current active path.
+ //
+ deviceInfo = DsmpGetAnyActivePath(group, FALSE, NULL, SpecialHandlingFlag);
+
+ //
+ // If the newly arriving path is the preferred path, this now should
+ // become our active path.
+ //
+ if (group->PreferredPath == ((ULONGLONG)((ULONG_PTR)(NewDeviceInfo->FailGroup->PathId)))) {
+
+ //
+ // If current active path is not the preferred path, change its
+ // path state to standby.
+ //
+ if (deviceInfo && deviceInfo != NewDeviceInfo) {
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_STANDBY;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (Group %p): Preferred path back online. DevInfo %p changed to state %d\n",
+ group,
+ deviceInfo,
+ deviceInfo->State));
+ }
+
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ } else {
+
+ //
+ // In the case of failover, we must have only a single
+ // active path. If this newly added path is configured as
+ // the one that is desired to be active then we make this
+ // active and set the standby path that was active back to
+ // standby unless the preferred path is the currently active
+ // path. If the newly added path is supposed to be
+ // standby then we leave it as standby.
+ //
+ if (NewDeviceInfo->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ if (deviceInfo) {
+
+ //
+ // If the preferred path is currently active, don't change
+ // it regardless of this path wanting to be in active state.
+ //
+ if (group->PreferredPath == ((ULONGLONG)((ULONG_PTR)(deviceInfo->FailGroup->PathId)))) {
+
+ if (NewDeviceInfo != deviceInfo) {
+
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_STANDBY;
+ }
+
+ } else {
+
+ //
+ // Since the preferred path is not active, make this
+ // path active since it wants to be so. This means
+ // changing the current active path to standby.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_STANDBY;
+
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+ }
+ } else {
+
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+ }
+
+ } else {
+
+ if (deviceInfo) {
+
+ if (deviceInfo != NewDeviceInfo) {
+
+ //
+ // This newly arrived device doesn't want to be in
+ // A/O, and we already have an active path, so make
+ // it standby.
+ //
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_STANDBY;
+ }
+ } else {
+
+ //
+ // Since we currently don't have an active path, this one
+ // needs to be made active, regardless of its path it wishes
+ // to be in.
+ //
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case DSM_LB_ROUND_ROBIN_WITH_SUBSET: {
+
+ //
+ // In RRWS, a set of paths can be active and another set of
+ // paths can be standby. We set the new path to the desired
+ // state unless the desired state is standby, but there are
+ // no active paths. Also if the desired state is Active we
+ // need to check if there are any existing paths that are
+ // also active but have a desired state of standby. For
+ // those we can move them back to standby.
+ //
+ if (NewDeviceInfo->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ //
+ // We are the active path coming back. Find out who has
+ // been the active one and place him back to standby
+ //
+ DsmpAllowStandbyPathsToRest(group);
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (DevInfo %p): Changed to state %d at %d\n",
+ NewDeviceInfo,
+ NewDeviceInfo->State,
+ __LINE__));
+
+ } else {
+
+ //
+ // if there are no paths already active then we've got
+ // to make this one AO, otherwise we can be non-AO
+ //
+ deviceInfo = DsmpGetAnyActivePath(group, FALSE, NULL, SpecialHandlingFlag);
+
+ if (!deviceInfo || deviceInfo == NewDeviceInfo) {
+
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ } else {
+
+ NewDeviceInfo->State = DSM_DEV_STANDBY;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (%p): Changed to state %d at %d. Status %x\n",
+ NewDeviceInfo,
+ NewDeviceInfo->State,
+ __LINE__,
+ status));
+ }
+
+ status = STATUS_SUCCESS;
+
+ break;
+ }
+
+ case DSM_LB_LEAST_BLOCKS:
+ case DSM_LB_ROUND_ROBIN:
+ case DSM_LB_DYN_LEAST_QUEUE_DEPTH:
+ case DSM_LB_WEIGHTED_PATHS: {
+
+ //
+ // In RR, LWP, LB and LQD all paths are active so the new device
+ // becomes AO or AU.
+ //
+ if (NewDeviceInfo->State != DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (DevInfo %p): Changed to state %d at %d. Status %x\n",
+ NewDeviceInfo,
+ NewDeviceInfo->State,
+ __LINE__,
+ status));
+
+ status = STATUS_SUCCESS;
+
+ break;
+ }
+
+ default: {
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+__Exit_DsmpSetLBForPathArrival:
+
+ //
+ // Update the next path to be used for the group
+ //
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess(NewDeviceInfo),
+ SpecialHandlingFlag);
+ if (deviceInfo != NULL) {
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), (PVOID)deviceInfo->FailGroup);
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (DevInfo %p): Updating PathToBeUsed in %p to %p\n",
+ NewDeviceInfo,
+ group,
+ group->PathToBeUsed));
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (DevInfo %p): No FOG available for group %p\n",
+ NewDeviceInfo,
+ group));
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), NULL);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrival (DevInfo %p): Exiting function with status %x\n",
+ NewDeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLBForPathArrivalALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO NewDeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when a new path arrives for a multipath
+ group. The routine will set the path to the appropriate state and
+ fix up the other paths state if they need to change.
+
+ Spin lock must NOT be held by caller
+
+Arguements:
+
+ DsmContext is the DSM context
+ NewDeviceInfo is the device info for the newly arrived path
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+ PDSM_DEVICE_INFO deviceInfo = NULL;
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL irql = PASSIVE_LEVEL; // Initialize variable to prevent C4701 warnings;
+ BOOLEAN lockHeld = FALSE;
+ PDSM_DEVICE_INFO preferredActiveDeviceInfo = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrivalALUA (DevInfo %p): Entering function.\n",
+ NewDeviceInfo));
+
+ group = NewDeviceInfo->Group;
+
+ if (!(DsmpIsDeviceInitialized(NewDeviceInfo) && DsmpIsDeviceUsable(NewDeviceInfo) && DsmpIsDeviceUsablePR(NewDeviceInfo))) {
+
+ //
+ // Bad device instance. Nothing can be done about it.
+ //
+ NewDeviceInfo->PreviousState = NewDeviceInfo->State;
+ NewDeviceInfo->State = DSM_DEV_UNDETERMINED;
+
+ NT_ASSERT(NewDeviceInfo->FailGroup == NULL);
+
+ goto __Exit_DsmpSetLBForPathArrivalALUA;
+ }
+
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ lockHeld = TRUE;
+
+ if (group->NumberDevices == 1) {
+
+ //
+ // If this is the only device for the group then it should be the
+ // active instance as every group must have at least one active path.
+ //
+ if (NewDeviceInfo->State != DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ lockHeld = FALSE;
+
+ if (NewDeviceInfo->ALUASupport >= DSM_DEVINFO_ALUA_EXPLICIT) {
+
+ //
+ // If the device supports explicit transitions, set its state to A/O
+ //
+ status = DsmpSetDeviceALUAState(DsmContext, NewDeviceInfo, DSM_DEV_ACTIVE_OPTIMIZED);
+
+ } else {
+
+ //
+ // Since the device supports only implicit transitions, send down
+ // RTPG and hope that the controller has made this one the A/O path.
+ //
+ status = DsmpGetDeviceALUAState(DsmContext, NewDeviceInfo, NULL);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Remember that at this point it is possible that this path
+ // is still non-A/O. We'll need to handle this in DsmGetPath
+ // as a special case where we don't find an A/O path but the
+ // storage supports implicit-only transitions. At that time,
+ // we mustn't blindly return a NULL path back.
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrivalALUA (DevInfo %p): RTPG returned state = %x, ALUA state = %x.\n",
+ NewDeviceInfo,
+ NewDeviceInfo->State,
+ NewDeviceInfo->ALUAState));
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrivalALUA (DevInfo %p): State changed to %d at %d\n",
+ NewDeviceInfo,
+ NewDeviceInfo->State,
+ __LINE__));
+
+ } else {
+
+ deviceInfo = DsmpGetAnyActivePath(group, FALSE, NULL, SpecialHandlingFlag);
+
+ //
+ // Irrespecitve of the policy, we must have at least one A/O path.
+ //
+ // For RRWS and FOO, this path is of interest if it is desired to be in A/O.
+ //
+ // Also, for failover-only, this new path is of interest if it is
+ // the preferred path.
+ //
+ // This path is also of interest if it is not A/O and desired state has not
+ // explicitly been set to non-A/O and it has been exposed through the
+ // preferred TPG.
+ //
+ if ((!deviceInfo) ||
+ ((NewDeviceInfo->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) &&
+ (group->LoadBalanceType == DSM_LB_FAILOVER ||
+ group->LoadBalanceType == DSM_LB_ROUND_ROBIN_WITH_SUBSET)) ||
+ (group->PreferredPath == (ULONGLONG)((ULONG_PTR)(NewDeviceInfo->FailGroup->PathId)) &&
+ group->LoadBalanceType == DSM_LB_FAILOVER) ||
+ (NewDeviceInfo->State != DSM_DEV_ACTIVE_OPTIMIZED &&
+ NewDeviceInfo->DesiredState == DSM_DEV_UNDETERMINED &&
+ NewDeviceInfo->TargetPortGroup->Preferred)) {
+
+ //
+ // Since this path is supposed to be active, we make it
+ // active and then allow any paths that are supposed to
+ // be standby go back to being standby
+ //
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ lockHeld = FALSE;
+
+ //
+ // If explicit ALUA is supported, we need to send down STPG to make the change.
+ //
+ if (NewDeviceInfo->ALUASupport >= DSM_DEVINFO_ALUA_EXPLICIT) {
+
+ status = DsmpSetDeviceALUAState(DsmContext, NewDeviceInfo, DSM_DEV_ACTIVE_OPTIMIZED);
+
+ } else {
+
+ //
+ // If implicit ALUA, the controller may have made some
+ // changes to the TPG states. We just need to query it.
+ // We'll try and honor the Admin's request but can't
+ // guarantee it.
+ //
+ status = DsmpGetDeviceALUAState(DsmContext, NewDeviceInfo, NULL);
+ }
+
+ //
+ // We prefer this newly arrived devInfo to be A/O
+ //
+ preferredActiveDeviceInfo = NewDeviceInfo;
+ }
+ }
+
+ if (!lockHeld) {
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ lockHeld = TRUE;
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ DsmpAdjustDeviceStatesALUA(group, preferredActiveDeviceInfo, SpecialHandlingFlag);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrivalALUA (DevInfo %p): Trying to query ALUA state failed with %x\n",
+ NewDeviceInfo,
+ status));
+ }
+
+ status = STATUS_SUCCESS;
+
+__Exit_DsmpSetLBForPathArrivalALUA:
+
+ //
+ // Update the next path to be used for the group
+ //
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess(NewDeviceInfo),
+ SpecialHandlingFlag);
+ if (deviceInfo != NULL) {
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), (PVOID)deviceInfo->FailGroup);
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrivalALUA (DevInfo %p): Updating PathToBeUsed in %p to %p\n",
+ NewDeviceInfo,
+ group,
+ group->PathToBeUsed));
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrivalALUA (DevInfo %p): No active/alternative path available for group %p\n",
+ NewDeviceInfo,
+ group));
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), NULL);
+ }
+
+ if (lockHeld) {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathArrivalALUA (DevInfo %p): Exiting function with status %x\n",
+ NewDeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLBForPathRemoval(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO RemovedDeviceInfo,
+ _In_opt_ IN OPTIONAL PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when a path is removed from a multipath
+ group. The routine will set the path to the appropriate state and
+ fix up the other paths state if they need to change.
+
+ Note: This is used by devices NOT supporting ALUA.
+
+Arguements:
+
+ DsmContext is the DSM context
+
+ RemovedDeviceInfo is the device info for failing/going-away path
+
+ Group is an optional group override. That is, if Group is not NULL, this
+ function will run the load balance policy on the given Group and not
+ the Group from the RemovedDeviceInfo. This should only be used when
+ it's impossible to get a pointer to the RemovedDeviceInfo.
+
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+ PDSM_DEVICE_INFO deviceInfo;
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL irql;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p, Group %p): Entering function.\n",
+ RemovedDeviceInfo,
+ Group));
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ if (Group == NULL) {
+
+ if (!(DsmpIsDeviceFailedState(RemovedDeviceInfo->State))) {
+
+ RemovedDeviceInfo->LastKnownGoodState = RemovedDeviceInfo->State;
+ }
+
+ RemovedDeviceInfo->PreviousState = RemovedDeviceInfo->State;
+ RemovedDeviceInfo->State = DSM_DEV_FAILED;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p): changed to state %d at %d\n",
+ RemovedDeviceInfo,
+ RemovedDeviceInfo->State,
+ __LINE__));
+
+ group = RemovedDeviceInfo->Group;
+
+ } else {
+ //
+ // The caller has chosen to override the RemovedDeviceInfo->Group.
+ //
+ group = Group;
+ }
+
+ switch(group->LoadBalanceType) {
+ case DSM_LB_FAILOVER:
+ case DSM_LB_ROUND_ROBIN_WITH_SUBSET: {
+
+ //
+ // In the case of failover, we must have only a single
+ // active path. If the removed path was the active path we
+ // need to find another path to become active
+ //
+ // In RRWS, a set of paths can be active and another set of
+ // paths can be standby. If the removed path is an active
+ // path then we need to make sure there is another active
+ // path. If there is already another active path then there
+ // is nothing to do. If not then a path needs to be made
+ // active.
+ //
+ if (!DsmpGetAnyActivePath(group, FALSE, NULL, SpecialHandlingFlag)) {
+
+ deviceInfo = DsmpFindStandbyPathToActivate(group, SpecialHandlingFlag);
+ if (deviceInfo) {
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p): DevInfo %p changed to state %d at %d\n",
+ RemovedDeviceInfo,
+ deviceInfo,
+ deviceInfo->State,
+ __LINE__));
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p): LB Policy FO/RRWS and other paths active, no path made active at %d\n",
+ RemovedDeviceInfo,
+ __LINE__));
+ }
+
+ break;
+ }
+
+ case DSM_LB_LEAST_BLOCKS:
+ case DSM_LB_ROUND_ROBIN:
+ case DSM_LB_WEIGHTED_PATHS:
+ case DSM_LB_DYN_LEAST_QUEUE_DEPTH: {
+
+ //
+ // In RR, LQD, LB and LWP, all paths are active so we don't
+ // need to worry about activating a new path
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p): LB Policy RR, LWP or LQD, no path made active at %d\n",
+ RemovedDeviceInfo,
+ __LINE__));
+ break;
+ }
+
+ default: {
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ //
+ // Update the next path to be used for the group
+ //
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess(RemovedDeviceInfo),
+ SpecialHandlingFlag);
+ if (deviceInfo != NULL) {
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), deviceInfo->FailGroup);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p): Removal: Updating PathToBeUsed in %p to %p\n",
+ RemovedDeviceInfo,
+ group,
+ group->PathToBeUsed));
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p): After remove No FOG available for group %p\n",
+ RemovedDeviceInfo,
+ group));
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), NULL);
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemoval (DevInfo %p): Exiting function with status %x\n",
+ RemovedDeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLBForPathRemovalALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO RemovedDeviceInfo,
+ _In_opt_ IN OPTIONAL PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when a path is removed from a multipath
+ group. The routine will set the path to the appropriate state and
+ fix up the other paths state if they need to change.
+
+ Note: This should NOT be called with DsmContext Lock held
+ This is used for devices supporting ALUA.
+
+Arguements:
+
+ DsmContext is the DSM context
+
+ RemovedDeviceInfo is the device info for the failing/going-away path
+
+ Group is an optional group override. That is, if Group is not NULL, this
+ function will run the load balance policy on the given Group and not
+ the Group from the RemovedDeviceInfo. This should only be used when
+ it's impossible to get a pointer to the RemovedDeviceInfo.
+
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+ PDSM_DEVICE_INFO deviceInfo = NULL;
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL irql;
+ BOOLEAN lockHeld = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p, Group %p): Entering function.\n",
+ RemovedDeviceInfo,
+ Group));
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ lockHeld = TRUE;
+
+ if (Group == NULL) {
+
+ if (!(DsmpIsDeviceFailedState(RemovedDeviceInfo->State))) {
+
+ RemovedDeviceInfo->LastKnownGoodState = RemovedDeviceInfo->State;
+ }
+
+ RemovedDeviceInfo->PreviousState = RemovedDeviceInfo->State;
+ RemovedDeviceInfo->State = DSM_DEV_FAILED;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): changed to state %d at %d\n",
+ RemovedDeviceInfo,
+ RemovedDeviceInfo->State,
+ __LINE__));
+
+ group = RemovedDeviceInfo->Group;
+
+ } else {
+ //
+ // The caller has chosen to override the RemovedDeviceInfo->Group.
+ //
+ group = Group;
+ }
+
+ if (group->LoadBalanceType < DSM_LB_FAILOVER ||
+ group->LoadBalanceType > DSM_LB_LEAST_BLOCKS) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ //
+ // In the case of failover, we must have only a single
+ // active path. If the removed path was the active path we
+ // need to find another path to become active
+ //
+ // In rest of policies, set of paths can be active and another set of
+ // paths can be standby. If the removed path is an active
+ // path then we need to make sure there is another active
+ // path. If there is already another active path then there
+ // is nothing to do. If not then a path needs to be made
+ // active.
+ //
+ if (!DsmpGetAnyActivePath(group, FALSE, NULL, SpecialHandlingFlag)) {
+
+ BOOLEAN sendTPG = TRUE;
+
+ deviceInfo = DsmpFindStandbyPathToActivateALUA(group, &sendTPG, SpecialHandlingFlag);
+
+ if (deviceInfo) {
+
+ if (sendTPG) {
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ lockHeld = FALSE;
+
+ //
+ // If explicit transition supported, we need to send down STPG to make the change.
+ //
+ if (deviceInfo->ALUASupport >= DSM_DEVINFO_ALUA_EXPLICIT) {
+
+ status = DsmpSetDeviceALUAState(DsmContext, deviceInfo, DSM_DEV_ACTIVE_OPTIMIZED);
+
+ } else {
+
+ //
+ // If implicit ALUA, the controller may have made necessary
+ // changes to the TPG states. We just need to query it.
+ //
+ status = DsmpGetDeviceALUAState(DsmContext, deviceInfo, NULL);
+ }
+
+ if (!lockHeld) {
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ lockHeld = TRUE;
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ DsmpAdjustDeviceStatesALUA(group, deviceInfo, SpecialHandlingFlag);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): Trying to query for ALUA state failed with status %x\n",
+ RemovedDeviceInfo,
+ status));
+ }
+ } else {
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): Device %p changed to state %d at %d\n",
+ RemovedDeviceInfo,
+ deviceInfo,
+ deviceInfo->State,
+ __LINE__));
+ }
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): Other paths active, no path made active at %d\n",
+ RemovedDeviceInfo,
+ __LINE__));
+ }
+
+ status = STATUS_SUCCESS;
+ }
+
+ //
+ // Update the next path to be used for the group
+ //
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess(RemovedDeviceInfo),
+ SpecialHandlingFlag);
+ if (deviceInfo != NULL) {
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), deviceInfo->FailGroup);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): Removal: Updating PathToBeUsed in %p to %p\n",
+ RemovedDeviceInfo,
+ group,
+ group->PathToBeUsed));
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): No active/alternative path available for group %p\n",
+ RemovedDeviceInfo,
+ group));
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), NULL);
+ }
+
+ if (lockHeld) {
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): Exiting function with status %x.\n",
+ RemovedDeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLBForPathFailing(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDeviceInfo,
+ _In_ IN BOOLEAN MarkDevInfoFailed,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when an IO that was sent using this path fails with
+ a fatal error. The routine will set the path to the appropriate state and
+ fix up the other paths state if they need to change.
+
+ Note: This is used by devices NOT supporting ALUA.
+
+Arguements:
+
+ DsmContext is the DSM context
+
+ FailingDeviceInfo is the device info for the path on which IO failed
+
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Status
+
+--*/
+{
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailing (DevInfo %p): Entering function.\n",
+ FailingDeviceInfo));
+
+ //
+ // We need to do exactly what DsmpSetLBForPathRemoval() does, except
+ // that the devInfo may not really go away (may come back before a
+ // Pnp remove comes down for the real LUN)
+ //
+ if (MarkDevInfoFailed) {
+ status = DsmpSetLBForPathRemoval(DsmContext, FailingDeviceInfo, NULL, SpecialHandlingFlag);
+ } else {
+ status = DsmpSetLBForPathRemoval(DsmContext, FailingDeviceInfo, FailingDeviceInfo->Group, SpecialHandlingFlag);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailing (DevInfo %p): Exiting function with status %x\n",
+ FailingDeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLBForPathFailingALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDeviceInfo,
+ _In_ IN BOOLEAN MarkDevInfoFailed,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when an IO that was sent using this path fails with
+ a fatal error. The routine will set the path to the appropriate state and
+ send down a Set Target Port Groups command asynchronously to fix up the
+ other paths state if they need to change (actual work done in the completion
+ routine).
+
+ Note: This should NOT be called with DsmContext Lock held
+ This is used for devices supporting ALUA.
+
+Arguements:
+
+ DsmContext is the DSM context
+
+ FailingDeviceInfo is the device info for the failing/going-away path
+
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+ PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY failDevInfoListEntry = NULL;
+ PDSM_DEVICE_INFO deviceInfo = NULL;
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL irql;
+ PUCHAR targetPortGroupsInfo = NULL;
+ ULONG targetPortGroupsInfoLength;
+ PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR tpgDescriptor = NULL;
+ PDSM_COMPLETION_CONTEXT completionContext = NULL;
+ PVOID senseInfo = NULL;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ PDSM_TPG_COMPLETION_CONTEXT tpgCompletionContext = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Entering function.\n",
+ FailingDeviceInfo));
+
+ if (MarkDevInfoFailed) {
+ if (!(DsmpIsDeviceFailedState(FailingDeviceInfo->State))) {
+
+ FailingDeviceInfo->LastKnownGoodState = FailingDeviceInfo->State;
+ }
+
+ FailingDeviceInfo->PreviousState = FailingDeviceInfo->State;
+ FailingDeviceInfo->State = DSM_DEV_FAILED;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): changed to state %d at %d\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->State,
+ __LINE__));
+ }
+
+ group = FailingDeviceInfo->Group;
+
+ if (group->LoadBalanceType < DSM_LB_FAILOVER ||
+ group->LoadBalanceType > DSM_LB_LEAST_BLOCKS) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // Check if there are any active paths that can be used.
+ //
+ deviceInfo = DsmpGetAnyActivePath(group, FALSE, NULL, SpecialHandlingFlag);
+ if (!deviceInfo) {
+
+ //
+ // Check if an Set/Report TPG has already been sent for this failing devInfo
+ //
+ failDevInfoListEntry = DsmpFindFailPathDevInfoEntry(DsmContext, group, FailingDeviceInfo);
+
+ if (!failDevInfoListEntry) {
+
+ BOOLEAN sendTPG = TRUE;
+
+ deviceInfo = DsmpFindStandbyPathToActivateALUA(group, &sendTPG, SpecialHandlingFlag);
+
+ if (deviceInfo) {
+
+ if (sendTPG) {
+
+ tpgCompletionContext = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_TPG_COMPLETION_CONTEXT),
+ DSM_TAG_TPG_COMPLETION_CONTEXT);
+
+ if (tpgCompletionContext) {
+ UCHAR senseInfoLength = SENSE_BUFFER_SIZE_EX;
+
+ senseInfo = DsmpAllocatePool(NonPagedPoolNx,
+ senseInfoLength,
+ DSM_TAG_SCSI_SENSE_INFO);
+
+ if (senseInfo) {
+
+ srb = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(SCSI_REQUEST_BLOCK),
+ DSM_TAG_SCSI_REQUEST_BLOCK);
+
+ if (srb) {
+
+ srb->Length = SCSI_REQUEST_BLOCK_SIZE;
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+
+ completionContext = ExAllocateFromNPagedLookasideList(&DsmContext->CompletionContextList);
+ if (completionContext) {
+
+ //
+ // Update the target port group that needs to be made
+ // active/optimized. We will send down an STPG for
+ // storages that support both implicit and explicit.
+ // If the storage does NOT like our choice of A/O TPG,
+ // it will make an implicit transition. This is still
+ // a better option than solely relying on the storage's
+ // implicit transitions at this stage and ending up with
+ // no path in A/O state.
+ //
+ if (deviceInfo->ALUASupport >= DSM_DEVINFO_ALUA_EXPLICIT) {
+
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE +
+ sizeof(SPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR);
+
+ } else {
+
+ //
+ // Find an active/optimized target port group that should
+ // have been set by the controller
+ //
+ // Take care of worst case scenario, which is:
+ // 1. 4-byte header (for allocation length)
+ // 2. 32 8-byte descriptors (for TPGs)
+ // 3. Each descriptor containing 32 4-byte identifiers (for TPs in each TPG)
+ //
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE +
+ (DSM_MAX_PATHS * (sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) +
+ DSM_MAX_PATHS * sizeof(ULONG)));
+ }
+
+ targetPortGroupsInfo = DsmpAllocatePool(NonPagedPoolNx,
+ targetPortGroupsInfoLength,
+ DSM_TAG_TARGET_PORT_GROUPS);
+
+ if (targetPortGroupsInfo) {
+
+ failDevInfoListEntry = DsmpBuildFailPathDevInfoEntry(DsmContext,
+ group,
+ FailingDeviceInfo,
+ deviceInfo);
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ if (failDevInfoListEntry) {
+
+ tpgDescriptor = (PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR)(targetPortGroupsInfo + SPC3_TARGET_PORT_GROUPS_HEADER_SIZE);
+ tpgDescriptor->AsymmetricAccessState = DSM_DEV_ACTIVE_OPTIMIZED;
+ REVERSE_BYTES_SHORT(&tpgDescriptor->TPG_Identifier, &deviceInfo->TargetPortGroup->Identifier);
+
+ //
+ // Prevent the device info from being removed when a TPG is in-flight.
+ //
+ InterlockedIncrement(&FailingDeviceInfo->BlockRemove);
+
+ completionContext->DeviceInfo = FailingDeviceInfo;
+ completionContext->DsmContext = DsmContext;
+ completionContext->RequestUnique1 = deviceInfo;
+ completionContext->RequestUnique2 = FALSE;
+
+ tpgCompletionContext->CompletionContext = completionContext;
+ tpgCompletionContext->Srb = srb;
+ tpgCompletionContext->SenseInfoBuffer = senseInfo;
+ tpgCompletionContext->SenseInfoBufferLength = senseInfoLength;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Sending down TPG asynchronously for %p using devInfo %p (path %p).\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId,
+ deviceInfo,
+ deviceInfo->FailGroup->PathId));
+
+ if (deviceInfo->ALUASupport >= DSM_DEVINFO_ALUA_EXPLICIT) {
+
+ status = DsmpSetTargetPortGroupsAsync(deviceInfo,
+ DsmpPhase1ProcessPathFailingALUA,
+ tpgCompletionContext,
+ targetPortGroupsInfoLength,
+ targetPortGroupsInfo);
+ } else {
+
+ status = DsmpReportTargetPortGroupsAsync(deviceInfo,
+ DsmpPhase2ProcessPathFailingALUA,
+ tpgCompletionContext,
+ targetPortGroupsInfoLength,
+ targetPortGroupsInfo);
+ }
+
+ if (status != STATUS_PENDING) {
+
+ //
+ // Request not sent down successfully. Free the allocations.
+ //
+ DsmpFreePool(targetPortGroupsInfo);
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+ DsmpFreePool(srb);
+ DsmpFreePool(senseInfo);
+ DsmpFreePool(tpgCompletionContext);
+
+ //
+ // Allow the failing device to be removed.
+ //
+ InterlockedDecrement(&FailingDeviceInfo->BlockRemove);
+ }
+ } else {
+
+ //
+ // Fail to build DevInfo entry. Free the allocations.
+ //
+ DsmpFreePool(targetPortGroupsInfo);
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+ DsmpFreePool(srb);
+ DsmpFreePool(senseInfo);
+ DsmpFreePool(tpgCompletionContext);
+
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+ DsmpFreePool(srb);
+ DsmpFreePool(senseInfo);
+ DsmpFreePool(tpgCompletionContext);
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ NT_ASSERT(completionContext != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Failed to allocate completion context. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ DsmpFreePool(srb);
+ DsmpFreePool(senseInfo);
+ DsmpFreePool(tpgCompletionContext);
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ NT_ASSERT(srb != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Failed to allocate SRB. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ DsmpFreePool(senseInfo);
+ DsmpFreePool(tpgCompletionContext);
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ NT_ASSERT(senseInfo != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Failed to allocate senseInfo. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ DsmpFreePool(tpgCompletionContext);
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ NT_ASSERT(tpgCompletionContext != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Failed to allocate TPG completion context. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Found alternative devInfo %p for failing path %p without need for TPG\n",
+ FailingDeviceInfo,
+ deviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Couldn't find a standby path to activate for failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ deviceInfo = failDevInfoListEntry->TempDeviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): There is an RTPG/STPG already in progress for this path %p. Returning alternative %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId,
+ deviceInfo));
+ }
+ } else {
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Other paths active, no path made active at %d\n",
+ FailingDeviceInfo,
+ __LINE__));
+ }
+
+ status = STATUS_SUCCESS;
+ }
+
+ if (deviceInfo) {
+
+ //
+ // Update temporarily the next path to be used for the group as this devInfo
+ //
+ InterlockedExchangePointer(&(group->PathToBeUsed), deviceInfo->FailGroup);
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Updating PathToBeUsed in %p to %p\n",
+ FailingDeviceInfo,
+ group,
+ group->PathToBeUsed));
+
+ } else {
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), NULL);
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathRemovalALUA (DevInfo %p): No FOG available for group %p\n",
+ FailingDeviceInfo,
+ group));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetLBForPathFailingALUA (DevInfo %p): Exiting function with status %x.\n",
+ FailingDeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetPathForIoRetryALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDeviceInfo,
+ _In_ IN BOOLEAN TPGException,
+ _In_ IN BOOLEAN DeviceInfoException
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when an IO that was sent using this path fails with
+ a retry-able "ALUA" error. What this basically means is that most likely an
+ implicit transition has taken place and we need an RTPG to get the updated
+ path states. The routine will send down a Report Target Port Groups command
+ asynchronously to get the paths states (actual work done in the completion
+ routine).
+
+ Note: This should NOT be called with DsmContext Lock held
+ This is used for devices supporting ALUA.
+
+Arguements:
+
+ DsmContext is the DSM context
+
+ FailingDeviceInfo is the device info for the failing/going-away path
+
+ TPGException is a flag used to indicate if the selected path must be from a
+ TPG that is different from FailingDeviceInfo's. This is
+ special handling for UA with sense "TPG in SB/UA state"
+
+ DeviceInfoException is a flag used to indicate that the current FailindDeviceInfo
+ itself needs to be used again. This is special handling for
+ UA with sense "Asymmetric Access State Changed"
+
+ (NOTE: TPGException and DeviceInfoException are mutually exclusive, although
+ it is okay for both to be FALSE)
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PDSM_GROUP_ENTRY group;
+ PDSM_DEVICE_INFO deviceInfo = NULL;
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL irql;
+ PUCHAR targetPortGroupsInfo = NULL;
+ ULONG targetPortGroupsInfoLength;
+ PDSM_COMPLETION_CONTEXT completionContext = NULL;
+ PVOID senseInfo = NULL;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ PDSM_TPG_COMPLETION_CONTEXT tpgCompletionContext = NULL;
+ NTSTATUS throttleStatus = STATUS_UNSUCCESSFUL;
+ ULONG inflightRTPG;
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Entering function.\n",
+ FailingDeviceInfo));
+
+ group = FailingDeviceInfo->Group;
+
+
+ if (group->LoadBalanceType < DSM_LB_FAILOVER ||
+ group->LoadBalanceType > DSM_LB_LEAST_BLOCKS) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ //
+ // First check to see if we need to find a candidate from a different TPG.
+ //
+ if (TPGException) {
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // Find a candidate in a TPG that is different from this one
+ //
+ deviceInfo = DsmpFindStandbyPathInAlternateTpgALUA(group, FailingDeviceInfo, SpecialHandlingFlag);
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Need to try a different TPG deviceInfo %p.\n",
+ FailingDeviceInfo,
+ deviceInfo));
+
+ } else if (DeviceInfoException) {
+
+ //
+ // Retry on the same path
+ //
+ deviceInfo = FailingDeviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Will retry using same deviceInfo %p.\n",
+ FailingDeviceInfo,
+ deviceInfo));
+ }
+
+ //
+ // Check if an Report TPG has already been sent for this group
+ //
+ inflightRTPG = InterlockedCompareExchange((LONG volatile*)&group->InFlightRTPG, 0, 0);
+
+ //
+ // If there is no RTPG currently in flight, use the best candidate found
+ // above to send down the RTPG. If there is already an RTPG inflight,
+ // we're done. The result of the RTPG should fix the path states.
+ //
+ if (!inflightRTPG) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): No RTPG in flight. Try sending one down.\n",
+ FailingDeviceInfo));
+
+ //
+ // If we need a candidate device, first get the currently active one.
+ // If we can't find one that way, resort to finding the best alternative.
+ // Basic idea is find SOME path instead of failing IOs back to the application.
+ //
+ if (!deviceInfo) {
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // Find the best candidate - ie. either a currently A/O path or
+ // the best alternative path to be made A/O.
+ //
+ deviceInfo = DsmpGetAnyActivePath(group, TRUE, deviceInfo, SpecialHandlingFlag);
+ if (!deviceInfo) {
+
+ BOOLEAN sendTPG = TRUE;
+
+ deviceInfo = DsmpFindStandbyPathToActivateALUA(group, &sendTPG, SpecialHandlingFlag);
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): No active path. Best alternative %p.\n",
+ FailingDeviceInfo,
+ deviceInfo));
+ }
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+
+ if (deviceInfo) {
+
+ tpgCompletionContext = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_TPG_COMPLETION_CONTEXT),
+ DSM_TAG_TPG_COMPLETION_CONTEXT);
+
+ if (tpgCompletionContext) {
+ UCHAR senseInfoLength = SENSE_BUFFER_SIZE_EX;
+
+ senseInfo = DsmpAllocatePool(NonPagedPoolNx,
+ senseInfoLength,
+ DSM_TAG_SCSI_SENSE_INFO);
+
+ if (senseInfo) {
+
+ srb = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(SCSI_REQUEST_BLOCK),
+ DSM_TAG_SCSI_REQUEST_BLOCK);
+
+ if (srb) {
+
+ srb->Length = SCSI_REQUEST_BLOCK_SIZE;
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+
+ completionContext = ExAllocateFromNPagedLookasideList(&DsmContext->CompletionContextList);
+
+ if (completionContext) {
+
+ //
+ // Find an active/optimized target port group that should
+ // have been set by the controller
+ //
+ // Take care of worst case scenario, which is:
+ // 1. 4-byte header (for allocation length)
+ // 2. 32 8-byte descriptors (for TPGs)
+ // 3. Each descriptor containing 32 4-byte identifiers (for TPs in each TPG)
+ //
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE +
+ (DSM_MAX_PATHS * (sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) +
+ DSM_MAX_PATHS * sizeof(ULONG)));
+
+ targetPortGroupsInfo = DsmpAllocatePool(NonPagedPoolNx,
+ targetPortGroupsInfoLength,
+ DSM_TAG_TARGET_PORT_GROUPS);
+
+ if (targetPortGroupsInfo) {
+
+ completionContext->DeviceInfo = FailingDeviceInfo;
+ completionContext->DsmContext = DsmContext;
+ completionContext->RequestUnique1 = deviceInfo;
+ completionContext->RequestUnique2 = TRUE;
+
+ tpgCompletionContext->CompletionContext = completionContext;
+ tpgCompletionContext->Srb = srb;
+ tpgCompletionContext->SenseInfoBuffer = senseInfo;
+ tpgCompletionContext->SenseInfoBufferLength = senseInfoLength;
+
+ //
+ // Now we are all set to send the RTPG request.
+ // check and set InFlightRTPG to make sure this thread is the only one with
+ // the RTPG active for this group, since it is possible to have more than one
+ // threads reaching up to this point in parallel
+ //
+ inflightRTPG = InterlockedCompareExchange((LONG volatile*)&group->InFlightRTPG, 1, 0);
+ if (inflightRTPG) {
+
+ DsmpFreePool(targetPortGroupsInfo);
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+ DsmpFreePool(srb);
+ DsmpFreePool(senseInfo);
+ DsmpFreePool(tpgCompletionContext);
+ } else {
+
+ //
+ // Prevent the device info from being removed when a TPG is in-flight.
+ //
+ InterlockedIncrement(&FailingDeviceInfo->BlockRemove);
+
+ //
+ // First try and throttle the IO. The completion routine will
+ // take care of resuming the IO.
+ //
+ if (!InterlockedCompareExchange((LONG volatile*)&group->Throttled, 1, 0)) {
+
+ DsmNotification(((PDSM_CONTEXT)DsmContext)->MPIOContext,
+ ThrottleIO_V2,
+ deviceInfo,
+ FALSE,
+ &throttleStatus,
+ 0);
+
+ if (NT_SUCCESS(throttleStatus)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Successfully throttled IO. About to send RTPG. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ } else {
+
+ //
+ // Throttle can fail when the MPDisk is
+ // 1. Being removed. (or)
+ // 2. In any other state other than Normal or Degraded.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Throttle before RTPG failed. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ InterlockedDecrement((LONG volatile*)&FailingDeviceInfo->Group->Throttled);
+ }
+ } else {
+
+ //
+ // Currently we don't expect this to happen
+ //
+ NT_ASSERT(FALSE);
+ }
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Sending RTPG asynchronously. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ if (STATUS_PENDING != DsmpReportTargetPortGroupsAsync(deviceInfo,
+ DsmpPhase2ProcessPathFailingALUA,
+ tpgCompletionContext,
+ targetPortGroupsInfoLength,
+ targetPortGroupsInfo)) {
+
+
+
+ //
+ // Request not sent down successfully. Free the allocations.
+ //
+ DsmpFreePool(targetPortGroupsInfo);
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+ DsmpFreePool(srb);
+ DsmpFreePool(senseInfo);
+ DsmpFreePool(tpgCompletionContext);
+
+ //
+ // Allow the failing device to be removed.
+ //
+ InterlockedDecrement(&FailingDeviceInfo->BlockRemove);
+
+ //
+ // Resume IO if we throttled requests before calling DsmpReportTargetPortGroupsAsync
+ //
+ if (InterlockedCompareExchange((LONG volatile*)&group->Throttled, 0, 1)) {
+
+ NTSTATUS resumeStatus = STATUS_UNSUCCESSFUL;
+
+ DsmNotification(((PDSM_CONTEXT)deviceInfo->DsmContext)->MPIOContext,
+ ResumeIO_V2,
+ deviceInfo,
+ TRUE,
+ &resumeStatus,
+ 0);
+
+ if (!NT_SUCCESS(resumeStatus)) {
+
+ //
+ // Resume can fail when
+ // 1. The MPDisk is being removed (or)
+ // 2. The MPDisk is any other state other than throttled (or)
+ // 3. There is a problem dispatching throttled requests.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Resume IO failed.\n",
+ deviceInfo));
+ }
+
+ }
+
+ InterlockedDecrement((LONG volatile*)&group->InFlightRTPG);
+ }
+ }
+ } else {
+
+ NT_ASSERT(targetPortGroupsInfo != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Couldn't allocate TPG info buffer. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+ ExFreePool(srb);
+ ExFreePool(senseInfo);
+ ExFreePool(tpgCompletionContext);
+ }
+ } else {
+
+ NT_ASSERT(completionContext != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Couldn't allocate completion context. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ ExFreePool(srb);
+ ExFreePool(senseInfo);
+ ExFreePool(tpgCompletionContext);
+ }
+ } else {
+
+ NT_ASSERT(srb != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Couldn't allocate SRB. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ ExFreePool(senseInfo);
+ ExFreePool(tpgCompletionContext);
+ }
+ } else {
+
+ NT_ASSERT(senseInfo != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Couldn't allocate senseInfo. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+
+ ExFreePool(tpgCompletionContext);
+ }
+ } else {
+
+ NT_ASSERT(tpgCompletionContext != NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Couldn't allocate TPG completion context. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Couldn't find a path for RTPG. Failing path %p.\n",
+ FailingDeviceInfo,
+ FailingDeviceInfo->FailGroup->PathId));
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Other paths active, no path made active at %d\n",
+ FailingDeviceInfo,
+ __LINE__));
+ }
+
+ if(!deviceInfo) {
+
+ //
+ // It is possible that there are only two TPGs with one of them in U/A
+ // state and the other in transitioning state. In such a case, we would
+ // not find an alternative TPG deviceInfo. In addition, if there is an
+ // RTPG in flight, we won't go down the path of forcibly picking any
+ // deviceInfo. This is to cover that scenario, else we're left with no
+ // deviceInfo to do the retry and we'll end up setting the group's PTBU
+ // to NULL thus failing the retried request (if for eg. the LB is RRWS).
+ // Need to ensure that we handle this exception case.
+ //
+ if (inflightRTPG) {
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // Find the best candidate - ie. either a currently A/O path or
+ // the best alternative path to be made A/O.
+ //
+ deviceInfo = DsmpGetAnyActivePath(group, TRUE, deviceInfo, SpecialHandlingFlag);
+ if (!deviceInfo) {
+
+ BOOLEAN sendTPG = TRUE;
+
+ deviceInfo = DsmpFindStandbyPathToActivateALUA(group, &sendTPG, SpecialHandlingFlag);
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+ }
+
+ if(deviceInfo) {
+
+ //
+ // Update temporarily the next path to be used for the group as this devInfo
+ //
+ InterlockedExchangePointer(&(group->PathToBeUsed), deviceInfo->FailGroup);
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Updating PathToBeUsed in %p to %p\n",
+ FailingDeviceInfo,
+ group,
+ group->PathToBeUsed));
+
+ } else {
+ InterlockedExchangePointer(&(group->PathToBeUsed), NULL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): No FOG available for group %p\n",
+ FailingDeviceInfo,
+ group));
+ }
+
+ status = STATUS_SUCCESS;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetPathForIoRetryALUA (DevInfo %p): Exiting function with status %x.\n",
+ FailingDeviceInfo,
+ status));
+
+ return status;
+}
+
+
+PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY
+DsmpFindFailPathDevInfoEntry(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO FailingDevInfo
+ )
+/*++
+
+Routine Description:
+
+ This routine finds the entry that contains the alternate devInfo to use for
+ a failing one for the passed in devInfo.
+
+ N.B: This routine MUST be called with DsmContextLock held in either Shared or
+ Exclusive mode.
+
+Arguments:
+
+ Context is the DSM's context info.
+
+ Group is the group entry representing the device.
+
+ FailingDevInfo is the device info whose entry needs to be found.
+
+Return Value:
+
+ Pointer to the entry. NULL if it doesn't exist.
+
+--*/
+{
+ PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY failDevInfoListEntry = NULL;
+ PLIST_ENTRY entry = NULL;
+
+ UNREFERENCED_PARAMETER(Context);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpFindFailPathDevInfoEntry (DevInfo %p): Entering function.\n",
+ FailingDevInfo));
+
+ for (entry = Group->FailingDevInfoList.Flink;
+ entry != &Group->FailingDevInfoList;
+ entry = entry->Flink) {
+
+ failDevInfoListEntry = CONTAINING_RECORD(entry, DSM_FAIL_PATH_PROCESSING_LIST_ENTRY, ListEntry);
+ NT_ASSERT(failDevInfoListEntry);
+
+ if (failDevInfoListEntry) {
+
+ if (failDevInfoListEntry->FailingDeviceInfo == FailingDevInfo) {
+
+ break;
+
+ } else {
+
+ failDevInfoListEntry = NULL;
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpFindFailPathDevInfoEntry (DevInfo %p): Exiting function returning entry %p.\n",
+ FailingDevInfo,
+ failDevInfoListEntry));
+
+ return failDevInfoListEntry;
+}
+
+
+PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY
+DsmpBuildFailPathDevInfoEntry(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO FailingDevInfo,
+ _In_ IN PDSM_DEVICE_INFO AlternateDevInfo
+ )
+/*++
+
+Routine Description:
+
+ When InterpretError() is called with an IRP that has failed with a fatal error,
+ if the device is ALUA it is possible that STPG needs to be sent to update a new
+ devInfo as being Active/Optimized. However, for in-flight IOs that weren't
+ queued by MPIO, we still need to return a path that can be used.
+
+ This routine is builds an entry that contains the alternate devInfo to use for
+ a failing one.
+
+ NOTE: Calling function should be holding the spin lock.
+
+Arguments:
+
+ Context is the DSM's context info.
+
+ Group is the group entry representing the device.
+
+ FailingDevInfo is the device info that was used when the IRP failed.
+
+ AlternateDevInfo is the new one to temporarily use until its state can be properly set.
+
+Return Value:
+
+ Pointer to the newly built entry.
+ NULL if there were any errors building it.
+
+--*/
+{
+ PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY failDevInfoListEntry = NULL;
+
+ UNREFERENCED_PARAMETER(Context);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpBuildFailPathDevInfoEntry (DevInfo %p): Entering function.\n",
+ FailingDevInfo));
+
+ failDevInfoListEntry = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_FAIL_PATH_PROCESSING_LIST_ENTRY),
+ DSM_TAG_FAIL_DEVINFO_LIST_ENTRY);
+
+ if (failDevInfoListEntry) {
+
+ failDevInfoListEntry->FailingDeviceInfo = FailingDevInfo;
+ failDevInfoListEntry->TempDeviceInfo = AlternateDevInfo;
+ InsertTailList(&Group->FailingDevInfoList, &failDevInfoListEntry->ListEntry);
+ InterlockedIncrement((LONG volatile*)&Group->NumberFailingDevInfos);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpBuildFailPathDevInfoEntry (DevInfo %p): Failed to allocate memory for entry.\n",
+ FailingDevInfo));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpBuildFailPathDevInfoEntry (DevInfo %p): Exiting function returning entry %p.\n",
+ FailingDevInfo,
+ failDevInfoListEntry));
+
+ return failDevInfoListEntry;
+}
+
+
+NTSTATUS
+DsmpPhase1ProcessPathFailingALUA(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ This is the completion routine that is called when the STPG is sent down by
+ DsmpSetLBForPathFailingALUA.
+
+ The caller SHOULD NOT acquire the DSM Context lock before calling this routine.
+
+Arguements:
+
+ DeviceObject is the target device object to which Irp was sent
+
+ Irp is the scsi pass through request for STPG
+
+ Context is the completion context.
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
+ PDSM_TPG_COMPLETION_CONTEXT context = (PDSM_TPG_COMPLETION_CONTEXT)Context;
+ PSCSI_REQUEST_BLOCK srb = context->Srb;
+ PVOID senseData = context->SenseInfoBuffer;
+ UCHAR senseDataLength = context->SenseInfoBufferLength;
+ NTSTATUS status = Irp->IoStatus.Status;
+ ULONG targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE + sizeof(SPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR);
+ PUCHAR targetPortGroupsInfo;
+ PDSM_DEVICE_INFO deviceInfo = (PDSM_DEVICE_INFO)(context->CompletionContext->RequestUnique1);
+ PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY failDevInfoListEntry = NULL;
+ BOOLEAN releaseCompletionContextResources = TRUE;
+ KIRQL irql;
+ UCHAR scsiStatus = SrbGetScsiStatus(srb);
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpPhase1ProcessPathFailingALUA (DevInfo %p): Entering function.\n",
+ deviceInfo));
+
+#if DBG
+ KeQuerySystemTime(&context->CompletionContext->TickCount);
+#endif
+
+ if ((scsiStatus == SCSISTAT_GOOD) &&
+ (NT_SUCCESS(status))) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpPhase1ProcessPathFailingALUA (DevInfo %p): STPG succeeded.\n",
+ deviceInfo));
+
+ } else if (NT_SUCCESS(status) &&
+ scsiStatus == SCSISTAT_CHECK_CONDITION &&
+ DsmpShouldRetryTPGRequest(senseData, senseDataLength)) {
+
+ if ((context->NumberRetries)--) {
+
+ //
+ // Retry the request
+ //
+
+ NT_ASSERT(SrbGetDataBuffer(srb) == MmGetMdlVirtualAddress(Irp->MdlAddress));
+
+ //
+ // Reset byte count of transfer in SRB Extension.
+ //
+ SrbSetDataTransferLength(srb, Irp->MdlAddress->ByteCount);
+
+ //
+ // Zero SRB statuses.
+ //
+ srb->SrbStatus = 0;
+ SrbSetScsiStatus(srb, 0);
+
+ nextIrpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
+ nextIrpStack->MinorFunction = IRP_MN_SCSI_CLASS;
+
+ //
+ // Save SRB address in next stack for port driver.
+ //
+ nextIrpStack->Parameters.Scsi.Srb = srb;
+ IoSetCompletionRoutine(Irp, DsmpPhase1ProcessPathFailingALUA, Context, TRUE, TRUE, TRUE);
+
+ IoMarkIrpPending(Irp);
+
+ //
+ // Send the IRP asynchronously
+ //
+ DsmSendRequestEx(context->CompletionContext->DsmContext->MPIOContext,
+ deviceInfo->TargetObject,
+ Irp,
+ deviceInfo,
+ DSM_CALL_COMPLETION_ON_MPIO_ERROR);
+
+ //
+ // We know that the completion routine will always be called.
+ //
+ status = STATUS_PENDING;
+ goto __Exit_DsmpPhase1ProcessPathFailingALUA;
+ }
+ } else {
+
+ irql = ExAcquireSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock));
+
+ failDevInfoListEntry = DsmpFindFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ context->CompletionContext->DeviceInfo);
+
+ if (failDevInfoListEntry) {
+
+ DsmpRemoveFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ failDevInfoListEntry);
+ }
+
+ ExReleaseSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpPhase1ProcessPathFailingALUA (DevInfo %p): NTStatus 0%x, ScsiStatus 0x%x.\n",
+ deviceInfo,
+ status,
+ scsiStatus));
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // An explicit transition may cause changes to some other TPGs.
+ // So we need to query for the states of all the TPGs and update
+ // our internal list and its elements.
+ //
+
+ //
+ // Take care of worst case scenario, which is:
+ // 1. 4-byte header (for allocation length)
+ // 2. 32 8-byte descriptors (for TPGs)
+ // 3. Each descriptor containing 32 4-byte identifiers (for TPs in each TPG)
+ //
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE +
+ (DSM_MAX_PATHS * (sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) +
+ DSM_MAX_PATHS * sizeof(ULONG)));
+
+ targetPortGroupsInfo = DsmpAllocatePool(NonPagedPoolNx,
+ targetPortGroupsInfoLength,
+ DSM_TAG_TARGET_PORT_GROUPS);
+
+ if (targetPortGroupsInfo) {
+
+ if (STATUS_PENDING == DsmpReportTargetPortGroupsAsync(deviceInfo,
+ DsmpPhase2ProcessPathFailingALUA,
+ Context,
+ targetPortGroupsInfoLength,
+ targetPortGroupsInfo)) {
+
+ releaseCompletionContextResources = FALSE;
+
+ } else {
+
+ DsmpFreePool(targetPortGroupsInfo);
+ }
+ } else {
+
+ irql = ExAcquireSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock));
+
+ failDevInfoListEntry = DsmpFindFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ context->CompletionContext->DeviceInfo);
+
+ if (failDevInfoListEntry) {
+
+ DsmpRemoveFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ failDevInfoListEntry);
+ }
+
+ ExReleaseSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock), irql);
+ }
+ }
+
+ //
+ // Free the allocations.
+ //
+ IoFreeMdl(Irp->MdlAddress);
+ Irp->MdlAddress = NULL;
+
+ DsmpFreePool(Irp->UserBuffer);
+
+ IoFreeIrp(Irp);
+ Irp = (PIRP) NULL;
+
+ if (releaseCompletionContextResources) {
+
+ //
+ // Release our hold on the device info so that it can be removed.
+ //
+ InterlockedDecrement(&context->CompletionContext->DeviceInfo->BlockRemove);
+
+ ExFreeToNPagedLookasideList(&(context->CompletionContext->DsmContext)->CompletionContextList, context->CompletionContext);
+ DsmpFreePool(context->Srb);
+ DsmpFreePool(context->SenseInfoBuffer);
+#pragma warning(suppress:6001) // DevDiv 818965
+ DsmpFreePool(context);
+ }
+
+__Exit_DsmpPhase1ProcessPathFailingALUA:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpPhase1ProcessPathFailingALUA (DevInfo %p): Exiting function.\n",
+ deviceInfo));
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+
+NTSTATUS
+DsmpRemoveFailPathDevInfoEntry(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY FailPathDevInfoEntry
+ )
+/*++
+
+Routine Description:
+
+ This routine removes the entry pointed to from the passed in Group's list.
+
+ NOTE: Calling function should be holding the spin lock.
+
+Arguments:
+
+ Context is the DSM's context info.
+
+ Group is the group entry representing the device.
+
+ FailingPathDevInfoEntry is the entry that needs to be removed.
+
+Return Value:
+
+ STATUS_SUCCESS if successful, else appropriate NT error code.
+
+--*/
+{
+ PLIST_ENTRY entry = &FailPathDevInfoEntry->ListEntry;
+ PDSM_DEVICE_INFO deviceInfo = FailPathDevInfoEntry->FailingDeviceInfo;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ UNREFERENCED_PARAMETER(Context);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpRemoveFailPathDevInfoEntry (DevInfo %p): Entering function.\n",
+ deviceInfo));
+
+ RemoveEntryList(entry);
+ DsmpFreePool(FailPathDevInfoEntry);
+ InterlockedDecrement((LONG volatile*)&Group->NumberFailingDevInfos);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpRemoveFailPathDevInfoEntry (DevInfo %p): Exiting function with status %x.\n",
+ deviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpPhase2ProcessPathFailingALUA(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ This is the completion routine that is called when the RTPG is sent down by
+ DsmpPhase1ProcessPathFailingALUA.
+
+ The caller SHOULD NOT acquire the DSM Context lock before calling this routine.
+
+Arguements:
+
+ DeviceObject is the target device object to which Irp was sent
+
+ Irp is the scsi pass through request for RTPG
+
+ Context is the completion context.
+
+Return Value:
+
+ Status
+
+--*/
+{
+ PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
+ PDSM_TPG_COMPLETION_CONTEXT context = (PDSM_TPG_COMPLETION_CONTEXT)Context;
+ PSCSI_REQUEST_BLOCK srb = context->Srb;
+ PVOID senseData = context->SenseInfoBuffer;
+ UCHAR senseDataLength = context->SenseInfoBufferLength;
+ NTSTATUS status = Irp->IoStatus.Status;
+ PUCHAR header;
+ ULONG returnedDataLength = 0;
+ PUCHAR targetPortGroupsInfo = NULL;
+ ULONG targetPortGroupsInfoLength = 0;
+ PDSM_DEVICE_INFO deviceInfo = (PDSM_DEVICE_INFO)(context->CompletionContext->RequestUnique1);
+ BOOLEAN decrementRTPGcount = (BOOLEAN)(context->CompletionContext->RequestUnique2);
+ KIRQL irql;
+ ULONG index;
+ PDSM_DEVICE_INFO devInfo;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup = NULL;
+ PDSM_GROUP_ENTRY group = deviceInfo->Group;
+ PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY failDevInfoListEntry = NULL;
+ UCHAR scsiStatus = SrbGetScsiStatus(srb);
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpPhase2ProcessPathFailingALUA (DevInfo %p): Entering function.\n",
+ deviceInfo));
+
+#if DBG
+ KeQuerySystemTime(&(context->CompletionContext)->TickCount);
+#endif
+
+ if ((status == STATUS_BUFFER_OVERFLOW) ||
+ (NT_SUCCESS(status) &&
+ (scsiStatus == SCSISTAT_GOOD))) {
+
+ header = (PUCHAR)((PUCHAR)SrbGetDataBuffer(srb));
+ GetUlongFrom4ByteArray(header, returnedDataLength);
+
+ status = STATUS_SUCCESS;
+ if (returnedDataLength > SrbGetDataTransferLength(srb)) {
+
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ }
+
+ if ((scsiStatus == SCSISTAT_GOOD) &&
+ (NT_SUCCESS(status))) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmpPhase2ProcessPathFailingALUA (DevInfo %p): RTPG using path %p succeeded.\n",
+ deviceInfo,
+ deviceInfo->FailGroup->PathId));
+
+ header = (PUCHAR)((PUCHAR)SrbGetDataBuffer(srb));
+ GetUlongFrom4ByteArray(header, returnedDataLength);
+
+ //
+ // Allocate a buffer to hold the TPG info.
+ //
+ targetPortGroupsInfo = DsmpAllocatePool(NonPagedPoolNx,
+ SPC3_TARGET_PORT_GROUPS_HEADER_SIZE + returnedDataLength,
+ DSM_TAG_TARGET_PORT_GROUPS);
+
+ if (targetPortGroupsInfo) {
+
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE + returnedDataLength;
+
+ //
+ // Copy it over.
+ //
+ RtlCopyMemory(targetPortGroupsInfo,
+ header,
+ targetPortGroupsInfoLength);
+
+ } else {
+
+ irql = ExAcquireSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock));
+
+ failDevInfoListEntry = DsmpFindFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ context->CompletionContext->DeviceInfo);
+
+ if (failDevInfoListEntry) {
+
+ DsmpRemoveFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ failDevInfoListEntry);
+ }
+
+ ExReleaseSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpPhase2ProcessPathFailingALUA (DevInfo %p): Failed to allocate mem for TPG.\n",
+ deviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ } else if (NT_SUCCESS(status) &&
+ scsiStatus == SCSISTAT_CHECK_CONDITION &&
+ DsmpShouldRetryTPGRequest(senseData, senseDataLength)) {
+
+ if ((context->NumberRetries)--) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpPhase2ProcessPathFailingALUA (DevInfo %p): Retrying check condition using path %p. Retries remaining %u.\n",
+ deviceInfo,
+ deviceInfo->FailGroup->PathId,
+ context->NumberRetries));
+
+ //
+ // Retry the request
+ //
+
+ NT_ASSERT(SrbGetDataBuffer(srb) == MmGetMdlVirtualAddress(Irp->MdlAddress));
+
+ //
+ // Reset byte count of transfer in SRB Extension to true length.
+ //
+ SrbSetDataTransferLength(srb, targetPortGroupsInfoLength);
+
+ //
+ // Zero SRB statuses.
+ //
+ srb->SrbStatus = 0;
+ SrbSetScsiStatus(srb, 0);
+
+ nextIrpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
+ nextIrpStack->MinorFunction = IRP_MN_SCSI_CLASS;
+
+ //
+ // Save SRB address in next stack for port driver.
+ //
+ nextIrpStack->Parameters.Scsi.Srb = srb;
+ IoSetCompletionRoutine(Irp, DsmpPhase2ProcessPathFailingALUA, Context, TRUE, TRUE, TRUE);
+
+ IoMarkIrpPending(Irp);
+
+ //
+ // Send the IRP asynchronously
+ //
+ DsmSendRequestEx(context->CompletionContext->DsmContext->MPIOContext,
+ deviceInfo->TargetObject,
+ Irp,
+ deviceInfo,
+ DSM_CALL_COMPLETION_ON_MPIO_ERROR);
+
+ //
+ // We know that the completion routine will always be called.
+ //
+ status = STATUS_PENDING;
+ goto __Exit_DsmpPhase2ProcessPathFailingALUA;
+ }
+ } else {
+
+ irql = ExAcquireSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock));
+
+ failDevInfoListEntry = DsmpFindFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ context->CompletionContext->DeviceInfo);
+
+ if (failDevInfoListEntry) {
+
+ DsmpRemoveFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ failDevInfoListEntry);
+ }
+
+ ExReleaseSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpPhase2ProcessPathFailingALUA (DevInfo %p): NTStatus 0%x, ScsiStatus 0x%x.\n",
+ deviceInfo,
+ status,
+ SrbGetScsiStatus(srb)));
+
+ // Failed to get TPG Info.
+ // Here it is possible status is success, but scsiStatus is not.
+ // If so, set status to unsuccessful.
+
+ if (NT_SUCCESS(status)) {
+ status = STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ irql = ExAcquireSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock));
+
+ //
+ // Parse the TPG information and update the device path states
+ //
+ status = DsmpParseTargetPortGroupsInformation(context->CompletionContext->DsmContext,
+ deviceInfo->Group,
+ targetPortGroupsInfo,
+ targetPortGroupsInfoLength);
+
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ targetPortGroup = deviceInfo->Group->TargetPortGroupList[index];
+
+ if (targetPortGroup) {
+
+ DsmpUpdateTargetPortGroupDevicesStates(targetPortGroup, targetPortGroup->AsymmetricAccessState);
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ PDSM_DEVICE_INFO tempDevice = NULL;
+
+ //
+ // Update all the devInfo states. If the device is AO but
+ // not this device, make it fake AU.
+ // If device not in AO, make sure that it matches the TPG
+ // state.
+ // Ensure that:
+ // 1. All devices match their ALUA state.
+ // 2. For RRWS, if a device's desired state is non-A/O, but ALUA state is A/O, mask it.
+ // 3. For FOO there must be only one A/O device. Preferably the preferred path.
+ //
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ devInfo = group->DeviceList[index];
+
+ if (devInfo) {
+
+ if (devInfo->ALUAState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ //
+ // In implicit transitions, there is no guarantee that
+ // the TPG of chosen "deviceInfo" is in A/O state. So
+ // to play it safe, we hang on to the very first devInfo
+ // whose TPG is in A/O state.
+ //
+ if (!tempDevice &&
+ !DsmpIsDeviceFailedState(devInfo->State)) {
+
+ devInfo->PreviousState = devInfo->State;
+ devInfo->State = devInfo->ALUAState;
+
+ tempDevice = devInfo;
+ }
+
+ if (devInfo != deviceInfo) {
+
+ if (!DsmpIsDeviceFailedState(devInfo->State)) {
+
+ //
+ // For FOO, only one path can be in A/O.
+ // For RRWS, mask an A/O path if that isn't the desired state.
+ //
+ if ((group->LoadBalanceType == DSM_LB_FAILOVER) ||
+ (group->LoadBalanceType == DSM_LB_ROUND_ROBIN_WITH_SUBSET &&
+ devInfo->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED &&
+ devInfo->DesiredState != DSM_DEV_UNDETERMINED)) {
+
+ //
+ // For implicit transitions, we may have saved off an A/O path.
+ // Don't undo that.
+ //
+ if (tempDevice != devInfo) {
+
+ devInfo->PreviousState = devInfo->State;
+ devInfo->State = DSM_DEV_ACTIVE_UNOPTIMIZED;
+ }
+
+ } else {
+
+ devInfo->PreviousState = devInfo->State;
+ devInfo->State = devInfo->ALUAState;
+ }
+ }
+ } else {
+
+ devInfo->PreviousState = devInfo->State;
+
+ //
+ // For FOO, only one path can be in A/O state.
+ // The TPG of the selected "deviceInfo" is in A/O, so this
+ // can now very well be made the candidate. However, since
+ // it is possible that we saved off another candidate, we
+ // now need to replace that with this.
+ //
+ if (!DsmpIsDeviceFailedState(devInfo->State) &&
+ devInfo->Group->LoadBalanceType == DSM_LB_FAILOVER &&
+ tempDevice) {
+
+ tempDevice->State = DSM_DEV_ACTIVE_UNOPTIMIZED;
+ }
+
+ devInfo->State = devInfo->ALUAState;
+
+ tempDevice = devInfo;
+ }
+ } else {
+
+ if (!DsmpIsDeviceFailedState(devInfo->State)) {
+
+ devInfo->PreviousState = devInfo->State;
+ devInfo->State = devInfo->ALUAState;
+ }
+ }
+ }
+ }
+ }
+
+ failDevInfoListEntry = DsmpFindFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ context->CompletionContext->DeviceInfo);
+
+ if (failDevInfoListEntry) {
+
+ DsmpRemoveFailPathDevInfoEntry(context->CompletionContext->DsmContext,
+ context->CompletionContext->DeviceInfo->Group,
+ failDevInfoListEntry);
+ }
+
+ ExReleaseSpinLockExclusive(&(context->CompletionContext->DsmContext->DsmContextLock), irql);
+ }
+
+
+ //
+ // Resume IO if we throttled requests.
+ //
+ if (InterlockedCompareExchange((LONG volatile*)&group->Throttled, 0, 1)) {
+
+ NTSTATUS resumeStatus = STATUS_UNSUCCESSFUL;
+
+ DsmNotification(((PDSM_CONTEXT)deviceInfo->DsmContext)->MPIOContext,
+ ResumeIO_V2,
+ deviceInfo,
+ TRUE,
+ &resumeStatus,
+ 0);
+
+ if (!NT_SUCCESS(resumeStatus)) {
+
+ //
+ // Resume can fail when
+ // 1. The MPDisk is being removed (or)
+ // 2. The MPDisk is any other state other than throttled (or)
+ // 3. There is a problem dispatching throttled requests.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmpPhase2ProcessPathFailingALUA (DevObj %p): Resume IO failed.\n",
+ DeviceObject));
+ }
+
+ }
+
+ if (decrementRTPGcount) {
+
+ //
+ // Resetting InFlightRTPG after resume so that we don't get into situation where
+ // new DsmpSetPathForIoRetryALUA caller thread finds that InFlightRTPG is not set but Throttled is set
+ //
+ ULONG count = InterlockedCompareExchange((LONG volatile*)&group->InFlightRTPG, 0, 1);
+
+ //
+ // If decrementRTPGCount flag is set, there must be atleast one RTPG in flight.
+ //
+ NT_ASSERT(count);
+
+ UNREFERENCED_PARAMETER(count);
+
+ }
+
+ //
+ // Free the allocations.
+ //
+ if (targetPortGroupsInfo) {
+
+ DsmpFreePool(targetPortGroupsInfo);
+ }
+
+ //
+ // Release our hold on the device info so that it can be removed.
+ //
+ InterlockedDecrement(&context->CompletionContext->DeviceInfo->BlockRemove);
+
+ IoFreeMdl(Irp->MdlAddress);
+ Irp->MdlAddress = NULL;
+
+ DsmpFreePool(Irp->UserBuffer);
+
+ IoFreeIrp(Irp);
+ Irp = (PIRP) NULL;
+
+ ExFreeToNPagedLookasideList(&(context->CompletionContext->DsmContext)->CompletionContextList, context->CompletionContext);
+ DsmpFreePool(srb);
+ DsmpFreePool(senseData);
+#pragma warning(suppress:6001) // DevDiv 818965
+ DsmpFreePool(context);
+
+__Exit_DsmpPhase2ProcessPathFailingALUA:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpPhase2ProcessPathFailingALUA (DevInfo %p): Exiting function.\n",
+ deviceInfo));
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+
+NTSTATUS
+DsmpPersistentReserveOut(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ )
+/*++
+
+Routine Description:
+
+ This routine will handle determine which devices to send the request to based
+ on the service action of the PR-out command.
+ On REGISTER/REGISTER_AND_IGNORE_EXISTING, it will send the command down all
+ paths. If any path succeeds, the PR key will be stored. If failure down any
+ path, return failure.
+ On REGISTER/REGISTER_AND_IGNORE_EXISTING with key == 0 (ie. UNREGISTER),
+ the request is sent down every path. Failure is returned if request fails down
+ any path (but error is ignored if path happens to be one where prior
+ REGISTER/REGISTER_AND_IGNORE_EXISTING had failed in the first place). The
+ stored PR key is cleared irrespective of success/failure being returned.
+ On RESERVE/RELEASE, the command is sent down one path. If it fails, another
+ path is tried. Failure is returned only if none succeed.
+ On CLEAR, command is sent down one path. If it fails, another path is tried.
+ Failure is returned only if none succeed. The stored PR key is cleared
+ irrespective of success/failure being returned.
+ On PREEMPT, command is sent down one path. If it fails, another path is
+ tried. Failure is returned only if none succeed.
+ On PREEMPT_AND_ABORT, command is sent down one path. Failed request is not
+ retried.
+
+ NOTE: If a path shows up later, REGISTER_AND_IGNORE_EXISTING request is
+ built by the IsPathActive routine using the saved PR key and sent down new path.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ DsmIds - The collection of DSM IDs that pertain to the MPDISK.
+ Irp - Irp containing SRB.
+ Srb - Scsi request block
+ Event - The event to
+
+Return Value:
+
+ NTSTATUS of the operation.
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo;
+ PDSM_DEVICE_INFO servicingDeviceInfo = NULL;
+ PDSM_GROUP_ENTRY group;
+ LONG i;
+ ULONG count;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PDSM_COMPLETION_CONTEXT completionContext;
+ PCDB cdb = SrbGetCdb(Srb);
+ UCHAR serviceAction;
+ NTSTATUS returnStatus = STATUS_SUCCESS;
+ BOOLEAN sendDownAll = FALSE;
+ BOOLEAN savePRKeyIfAnySucceed = FALSE;
+ BOOLEAN retryOnAnother = FALSE;
+ BOOLEAN passOnlyIfAllSucceed = FALSE;
+ BOOLEAN ignoreIfPreviousFailed = FALSE;
+ BOOLEAN clearPRKey = FALSE;
+ KEVENT event;
+ PPRO_PARAMETER_LIST prOutParam = Irp->AssociatedIrp.SystemBuffer;
+ PUCHAR index = NULL;
+ UCHAR prKey[8] = {0};
+ PSTORAGE_REQUEST_BLOCK_HEADER srbCopy = NULL;
+ PIO_STACK_LOCATION irpStack;
+ PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
+ BOOLEAN statusUpdated = FALSE;
+ ULONGLONG currentTickCount;
+ ULONGLONG finalTickCount;
+ ULONG tickLength = KeQueryTimeIncrement();
+ PVOID senseInfoBuffer = NULL;
+ UCHAR senseInfoBufferLength = 0;
+ BOOLEAN srbCopySucceeded = FALSE;
+ UCHAR prType;
+ UCHAR prScope;
+ ULONGLONG saKey;
+ ULONGLONG resKey;
+ ULONG SpecialHandlingFlag = 0;
+
+ UNREFERENCED_PARAMETER(Event);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // Cache away a copy of the SRB
+ //
+ srbCopy = SrbAllocateCopy(Srb, NonPagedPoolNx, DSM_TAG_SCSI_REQUEST_BLOCK);
+ if (srbCopy == NULL) {
+ returnStatus = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpPersistentReserveOut;
+ }
+
+ deviceInfo = DsmIds->IdList[0];
+ group = deviceInfo->Group;
+
+ prType = cdb->PERSISTENT_RESERVE_OUT.Type;
+ prScope = cdb->PERSISTENT_RESERVE_OUT.Scope;
+ serviceAction = cdb->PERSISTENT_RESERVE_OUT.ServiceAction;
+
+ NT_ASSERT(serviceAction >= RESERVATION_ACTION_REGISTER && serviceAction <= RESERVATION_ACTION_REGISTER_IGNORE_EXISTING);
+
+ index = prOutParam->ServiceActionReservationKey;
+ RtlCopyMemory(&prKey, index, 8);
+ REVERSE_BYTES_QUAD(&saKey, &prOutParam->ServiceActionReservationKey);
+
+ REVERSE_BYTES_QUAD(&resKey, &prOutParam->ReservationKey);
+
+ switch (serviceAction) {
+ case RESERVATION_ACTION_REGISTER:
+ case RESERVATION_ACTION_REGISTER_IGNORE_EXISTING: {
+
+ //
+ // The command must be sent down all paths.
+ //
+ sendDownAll = TRUE;
+
+ //
+ // Return failure if it fails down even one of the paths.
+ //
+ passOnlyIfAllSucceed = TRUE;
+
+ if (DsmpIsPersistentReservationKeyZeroKey(ARRAY_SIZE(prKey), prKey)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): NULL PR key, service action %u\n",
+ DsmIds,
+ serviceAction));
+
+ //
+ // If unregister fails, don't report it back as error if the
+ // previous register/register_and_ignore_existing down that
+ // path had failed too.
+ //
+ ignoreIfPreviousFailed = TRUE;
+
+ //
+ // Clear the group PR key irrespective of the status that is
+ // going to be returned to clusdisk.
+ //
+ clearPRKey = TRUE;
+
+ } else {
+
+ //
+ // If register/register_and_ignore_existing succeed down any of
+ // the paths, save off the PR key for the group entry.
+ //
+ savePRKeyIfAnySucceed = TRUE;
+ }
+
+ break;
+ }
+
+ case RESERVATION_ACTION_RESERVE:
+ case RESERVATION_ACTION_RELEASE:
+ case RESERVATION_ACTION_PREEMPT:
+ case RESERVATION_ACTION_PREEMPT_ABORT:
+ case RESERVATION_ACTION_CLEAR: {
+
+ if (serviceAction != RESERVATION_ACTION_PREEMPT_ABORT) {
+
+ //
+ // Apart from preempt_abort, all the others must be retried
+ // (down another path) if they fail down the chosen path.
+ //
+ retryOnAnother = TRUE;
+ }
+
+ if (serviceAction == RESERVATION_ACTION_CLEAR) {
+
+ //
+ // Clear the stored PR key for the group entry irrespective of
+ // the status that is going to be returned back.
+ //
+ clearPRKey = TRUE;
+ }
+
+ break;
+ }
+
+ default: {
+
+ returnStatus = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): Invalid service action %u.\n",
+ DsmIds,
+ serviceAction));
+
+ goto __Exit_DsmpPersistentReserveOut;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): Srb %p, Service Action %u, Type %u, Scope %u, \
+ \n\t\t\t\tservice action reservation key %I64x, reservation key %I64x.\n",
+ DsmIds,
+ Srb,
+ serviceAction,
+ prType,
+ prScope,
+ saKey,
+ resKey));
+
+ //
+ // Allocate a context for the completion routine.
+ //
+ completionContext = ExAllocateFromNPagedLookasideList(&DsmContext->CompletionContextList);
+ if (!completionContext) {
+
+ returnStatus = STATUS_INSUFFICIENT_RESOURCES;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u - Failed to allocate completion context.\n",
+ DsmIds,
+ serviceAction));
+
+ goto __Exit_DsmpPersistentReserveOut;
+ }
+
+ KeInitializeEvent(&event, NotificationEvent, FALSE);
+
+ //
+ // Indicate the target for this request.
+ //
+ completionContext->DsmContext = DsmContext;
+ completionContext->RequestUnique1 = (PVOID)&event;
+ completionContext->RequestUnique2 = cdb->PERSISTENT_RESERVE_OUT.OperationCode;
+
+ count = group->NumberDevices;
+
+ for (i = count - 1; i >= 0; i--) {
+
+ //
+ // A PR command may fail with a "retry-able" UA when reservation is
+ // released or preempted (on every I_T_L nexus except the one on which
+ // it was released/preempted). In such a case we should retry the PR
+ // command on the same path.
+ //
+ KeQueryTickCount((PLARGE_INTEGER)&currentTickCount);
+ finalTickCount = currentTickCount + (DSM_SECONDS_TO_TICKS(group->MaxPRRetryTimeDuringStateTransition) / tickLength);
+
+ if (!sendDownAll && !retryOnAnother) {
+
+ //
+ // If the request doesn't need to be retried (down another path) on
+ // failure, better choose the path that has maximum chances of
+ // success.
+ //
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess((PDSM_DEVICE_INFO)DsmIds->IdList[0]),
+ SpecialHandlingFlag);
+
+ if (!deviceInfo) {
+
+ returnStatus = STATUS_UNSUCCESSFUL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u - No active/alternative path for device %p.\n",
+ DsmIds,
+ serviceAction,
+ group));
+
+ break;
+ }
+
+ } else {
+
+ deviceInfo = group->DeviceList[i];
+
+ //
+ // Ignore "bad" paths for now. If the path becomes "good" again,
+ // IsPathActive() will send down the register.
+ // Also, don't consider newly arrived paths for which the group has
+ // a reservation but register has not yet been sent down. This rule
+ // applies only to requests that are not Register.
+ //
+ if ((DsmpIsDeviceFailedState(deviceInfo->State) || !DsmpIsDeviceInitialized(deviceInfo)) ||
+ (!DsmpIsDeviceUsablePR(deviceInfo) && !sendDownAll)) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): Ignoring bad instance - state %x, init %x, key reg %x (key valid %x).\n",
+ DsmIds,
+ deviceInfo->State,
+ deviceInfo->Initialized,
+ deviceInfo->PRKeyRegistered,
+ deviceInfo->Group->PRKeyValid));
+
+ deviceInfo = NULL;
+ }
+
+ }
+
+ if (!deviceInfo) {
+
+ //
+ // Maybe a remove came through and caused a collapse of the device
+ // list, thus making this entry empty.
+ //
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u - Couldn't find path for device %p.\n",
+ DsmIds,
+ serviceAction,
+ group));
+
+ continue;
+ }
+
+__DsmpPersistentReserveOut_RetryRequest:
+
+ IoMarkIrpPending(Irp);
+
+ completionContext->DeviceInfo = deviceInfo;
+
+ //
+ // Set-up a completion routine.
+ //
+ IoSetCompletionRoutine(Irp,
+ DsmpPersistentReserveCompletion,
+ completionContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ //
+ // Always send the original request down a new path
+ //
+ irpStack = IoGetNextIrpStackLocation(Irp);
+ srbCopySucceeded = SrbCopySrb(Srb, SrbGetSrbLength(Srb), srbCopy);
+ NT_ASSERT(srbCopySucceeded == TRUE);
+ irpStack->Parameters.Scsi.Srb = Srb;
+
+ //
+ // Clear the sense buffer if it exists
+ //
+ senseInfoBuffer = SrbGetSenseInfoBuffer(Srb);
+ senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
+ if (senseInfoBuffer) {
+ RtlZeroMemory(senseInfoBuffer, senseInfoBufferLength);
+ }
+
+ servicingDeviceInfo = deviceInfo;
+
+ //
+ // Issue the request and wait.
+ //
+ status = DsmSendRequest(DsmContext->MPIOContext,
+ deviceInfo->TargetObject,
+ Irp,
+ deviceInfo);
+
+ if (status == STATUS_PENDING) {
+
+ KeWaitForSingleObject(&event,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ status = Irp->IoStatus.Status;
+ }
+
+ if (NT_SUCCESS(status) || status == STATUS_BUFFER_OVERFLOW) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u sent down successfully on %p.\n",
+ DsmIds,
+ serviceAction,
+ deviceInfo->FailGroup->PathId));
+
+ if (!passOnlyIfAllSucceed) {
+
+ //
+ // Success down any one path means success
+ //
+ returnStatus = status;
+ }
+
+ if (savePRKeyIfAnySucceed) {
+
+ RtlCopyMemory(&group->PersistentReservationRegisteredKey, &prKey, 8);
+
+ group->PRServiceAction = serviceAction;
+ group->PRType = prType;
+ group->PRScope = prScope;
+ group->PRKeyValid = TRUE;
+ deviceInfo->PRKeyRegistered = TRUE;
+ }
+
+ if (!sendDownAll) {
+
+ //
+ // Need for retrying on another path only necessary in the case
+ // of request failing down the chosen path. Since the request
+ // succeeded down this path, we are done.
+ //
+ break;
+ }
+ } else {
+
+ BOOLEAN recordFailure;
+
+ //
+ // Check to see if the request failed because of a "transient error",
+ // like reservations released for example. If so, this is NOT an actual
+ // error and the request must be retried. Multiple retries may be required
+ // if for example the UA indicates that the TPGs are in transitioning state.
+ //
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID &&
+ Srb->SrbStatus & SRB_STATUS_ERROR &&
+ SrbGetScsiStatus(Srb) == SCSISTAT_CHECK_CONDITION) {
+
+ KeQueryTickCount((PLARGE_INTEGER)&currentTickCount);
+
+ senseInfoBuffer = SrbGetSenseInfoBuffer(Srb);
+ senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
+
+ if (DsmpShouldRetryPersistentReserveCommand(senseInfoBuffer, senseInfoBufferLength) &&
+ currentTickCount < finalTickCount) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u returned UA with error %x. Retrying same path %p.\n",
+ DsmIds,
+ serviceAction,
+ status,
+ deviceInfo->FailGroup->PathId));
+
+ KeResetEvent(&event);
+ Irp->IoStatus.Status = 0;
+
+ goto __DsmpPersistentReserveOut_RetryRequest;
+ }
+ }
+
+ //
+ // The return status is STATUS_SUCCESS by default. This means that if the
+ // request failed on the first path and was retried down every other path
+ // but fails down all of them, the return status is never updated.
+ // So cache the first failure status to cover the above scenario.
+ //
+ if (!statusUpdated) {
+
+ returnStatus = status;
+ statusUpdated = TRUE;
+ }
+
+ recordFailure = TRUE;
+ if (ignoreIfPreviousFailed && !deviceInfo->PRKeyRegistered) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u - Ignoring status %x for path %p.\n",
+ DsmIds,
+ serviceAction,
+ status,
+ deviceInfo->FailGroup->PathId));
+
+ //
+ // Okay to ignore this failure if the previous failed.
+ //
+ recordFailure = FALSE;
+ }
+
+ if (passOnlyIfAllSucceed && recordFailure) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u - Saving status %x for return.\n",
+ DsmIds,
+ serviceAction,
+ status));
+
+ //
+ // Save the failure status to return back.
+ //
+ returnStatus = status;
+ }
+
+ //
+ // If the request is not to be sent down all paths, and also
+ // a retry (along a different path) on failure is not required,
+ // we're done - just return this failure.
+ //
+ if (!(sendDownAll || retryOnAnother)) {
+
+ returnStatus = status;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u sent down %p failed with %x. Breaking out.\n",
+ DsmIds,
+ serviceAction,
+ deviceInfo->FailGroup->PathId,
+ status));
+
+ break;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT %u sent down %p failed with %x. Sending down another path.\n",
+ DsmIds,
+ serviceAction,
+ deviceInfo->FailGroup->PathId,
+ status));
+ }
+ }
+
+ //
+ // If we are here, it is either because the request needs to be sent down
+ // all paths, or because the request failed down the chosen path and needs
+ // to be retried down a new path.
+ //
+ KeResetEvent(&event);
+ Irp->IoStatus.Status = 0;
+ }
+
+ if (clearPRKey) {
+
+ for (i = 0; (ULONG)i < group->NumberDevices; i++) {
+
+ deviceInfo = group->DeviceList[i];
+
+ if (deviceInfo) {
+
+ deviceInfo->RegisterServiced = FALSE;
+ deviceInfo->PRKeyRegistered = FALSE;
+ }
+ }
+ group->PersistentReservationRegisteredKey[0] = group->PersistentReservationRegisteredKey[1] =
+ group->PersistentReservationRegisteredKey[2] = group->PersistentReservationRegisteredKey[3] =
+ group->PersistentReservationRegisteredKey[4] = group->PersistentReservationRegisteredKey[5] =
+ group->PersistentReservationRegisteredKey[6] = group->PersistentReservationRegisteredKey[7] = 0;
+
+ group->PRKeyValid = FALSE;
+ group->ReservationList = 0;
+ }
+
+ if (savePRKeyIfAnySucceed && group->PRKeyValid) {
+
+ ULONG ordinal;
+
+ for (i = 0; (ULONG)i < group->NumberDevices; i++) {
+
+ deviceInfo = group->DeviceList[i];
+
+ if (deviceInfo) {
+
+ deviceInfo->RegisterServiced = TRUE;
+ ordinal = (1 << i);
+ group->ReservationList |= ordinal;
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): PR_OUT for %u completed with status %x.\n",
+ DsmIds,
+ serviceAction,
+ returnStatus));
+
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+
+__Exit_DsmpPersistentReserveOut:
+
+ if (srbCopy != NULL) {
+ DsmpFreePool(srbCopy);
+ }
+
+ currentIrpStack->Parameters.Others.Argument3 = servicingDeviceInfo;
+ Irp->IoStatus.Status = returnStatus;
+ if ((!NT_SUCCESS(returnStatus)) &&
+ (SrbGetSrbStatus(Srb) == SRB_STATUS_SUCCESS)) {
+ SrbSetSrbStatus(Srb, DsmpNtStatusToSrbStatus(returnStatus));
+ }
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveOut (DsmIds %p): Exiting function returning IRP status %x.\n",
+ DsmIds,
+ returnStatus));
+
+ return returnStatus;
+}
+
+
+
+NTSTATUS
+DsmpPersistentReserveIn(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ )
+/*++
+
+Routine Description:
+
+ This routine will handle determine which devices to send the request to based
+ on the service action of the PR-in command.
+ On READ KEYS, it will send down one path. In case of failure, other paths will
+ be tried until one succeeds. Failure is returned only if it fails down all paths.
+ On READ_RESERVATION/REPORT_CAPABILITIES, command is sent down one path. Failed
+ request is not retried.
+
+Arguments:
+
+ DsmContext - DSM context given to MPIO during initialization
+ DsmIds - The collection of DSM IDs that pertain to the MPDISK.
+ Irp - Irp containing SRB.
+ Srb - Scsi request block
+ Event - The event to
+
+Return Value:
+
+ NTSTATUS of the operation.
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo;
+ PDSM_DEVICE_INFO servicingDeviceInfo = NULL;
+ PDSM_GROUP_ENTRY group;
+ LONG i;
+ ULONG count;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PDSM_COMPLETION_CONTEXT completionContext;
+ PCDB cdb = SrbGetCdb(Srb);
+ UCHAR serviceAction;
+ BOOLEAN retryOnAnother = FALSE;
+ KEVENT event;
+ PSTORAGE_REQUEST_BLOCK_HEADER srbCopy = NULL;
+ PIO_STACK_LOCATION irpStack;
+ PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONGLONG currentTickCount;
+ ULONGLONG finalTickCount;
+ ULONG tickLength = KeQueryTimeIncrement();
+ PVOID senseInfoBuffer = NULL;
+ UCHAR senseInfoBufferLength = 0;
+ BOOLEAN srbCopySucceeded = FALSE;
+ ULONG SpecialHandlingFlag = 0;
+
+ UNREFERENCED_PARAMETER(Event);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // Cache away a copy of the SRB
+ //
+ srbCopy = SrbAllocateCopy(Srb, NonPagedPoolNx, DSM_TAG_SCSI_REQUEST_BLOCK);
+ if (srbCopy == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpPersistentReserveIn;
+ }
+
+ deviceInfo = DsmIds->IdList[0];
+ group = deviceInfo->Group;
+
+ serviceAction = cdb->PERSISTENT_RESERVE_IN.ServiceAction;
+
+ switch (serviceAction) {
+ case RESERVATION_ACTION_READ_RESERVATIONS:
+ case RESERVATION_ACTION_READ_KEYS: {
+
+ //
+ // If there is a failure on the chosen path, retry on another path.
+ //
+ retryOnAnother = TRUE;
+ break;
+ }
+
+ case SPC3_RESERVATION_ACTION_REPORT_CAPABILITIES: {
+
+ break;
+ }
+
+ default: {
+
+ NT_ASSERT(FALSE);
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpPersistentReserveIn;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): Srb %p. Service Action %u.\n",
+ DsmIds,
+ Srb,
+ serviceAction));
+
+ //
+ // Allocate a context for the completion routine.
+ //
+ completionContext = ExAllocateFromNPagedLookasideList(&DsmContext->CompletionContextList);
+ if (!completionContext) {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN %u - Failed to allocate completion context.\n",
+ DsmIds,
+ serviceAction));
+
+ goto __Exit_DsmpPersistentReserveIn;
+ }
+
+ KeInitializeEvent(&event, NotificationEvent, FALSE);
+
+ //
+ // Indicate the target for this request.
+ //
+ completionContext->DsmContext = DsmContext;
+ completionContext->RequestUnique1 = (PVOID)&event;
+ completionContext->RequestUnique2 = cdb->PERSISTENT_RESERVE_IN.OperationCode;
+
+ count = group->NumberDevices;
+
+ for (i = count - 1; i >= 0; i--) {
+
+ //
+ // A PR command may fail with a "retry-able" UA when reservation is
+ // released or preempted (on every I_T_L nexus except the one on which
+ // it was released/preempted). In such a case we should retry the PR
+ // command on the same path.
+ //
+ KeQueryTickCount((PLARGE_INTEGER)&currentTickCount);
+ finalTickCount = currentTickCount + (DSM_SECONDS_TO_TICKS(group->MaxPRRetryTimeDuringStateTransition) / tickLength);
+
+ if (!retryOnAnother) {
+
+ //
+ // If the request doesn't need to be retried (down another path) on
+ // failure, better choose the path that has maximum chances of
+ // success.
+ //
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess((PDSM_DEVICE_INFO)DsmIds->IdList[0]),
+ SpecialHandlingFlag);
+
+ if (!deviceInfo) {
+
+ status = STATUS_UNSUCCESSFUL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN %u - No active/alternative path for device %p.\n",
+ DsmIds,
+ serviceAction,
+ group));
+
+ break;
+ }
+
+ } else {
+
+ deviceInfo = group->DeviceList[i];
+
+ if (DsmpIsDeviceFailedState(deviceInfo->State) || !DsmpIsDeviceInitialized(deviceInfo)) {
+
+ //
+ // Ignore "bad" paths for now.
+ //
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): Ignoring bad instance - state %x, init %x.\n",
+ DsmIds,
+ deviceInfo->State,
+ deviceInfo->Initialized));
+
+ deviceInfo = NULL;
+ }
+ }
+
+ if (!deviceInfo) {
+
+ //
+ // Maybe a remove came through and caused a collapse of the device
+ // list, thus making this entry empty.
+ //
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN %u - Couldn't find path for device %p.\n",
+ DsmIds,
+ serviceAction,
+ group));
+
+ continue;
+ }
+
+__DsmpPersistentReserveIn_RetryRequest:
+
+ IoMarkIrpPending(Irp);
+
+ completionContext->DeviceInfo = deviceInfo;
+
+ //
+ // Set-up a completion routine.
+ //
+ IoSetCompletionRoutine(Irp,
+ DsmpPersistentReserveCompletion,
+ completionContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ //
+ // Always send the original request down a new path
+ //
+ irpStack = IoGetNextIrpStackLocation(Irp);
+ srbCopySucceeded = SrbCopySrb(Srb, SrbGetSrbLength(Srb), srbCopy);
+ NT_ASSERT(srbCopySucceeded == TRUE);
+ irpStack->Parameters.Scsi.Srb = Srb;
+
+ //
+ // Clear the sense buffer if it exists
+ //
+ senseInfoBuffer = SrbGetSenseInfoBuffer(Srb);
+ senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
+ if (senseInfoBuffer) {
+ RtlZeroMemory(senseInfoBuffer, senseInfoBufferLength);
+ }
+
+ servicingDeviceInfo = deviceInfo;
+
+ //
+ // Issue the request and wait.
+ //
+ status = DsmSendRequest(DsmContext->MPIOContext,
+ deviceInfo->TargetObject,
+ Irp,
+ deviceInfo);
+
+ if (status == STATUS_PENDING) {
+
+ KeWaitForSingleObject(&event,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ status = Irp->IoStatus.Status;
+ }
+
+ if (NT_SUCCESS(status) || status == STATUS_BUFFER_OVERFLOW) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN for %u sent down successfully on %p.\n",
+ DsmIds,
+ serviceAction,
+ deviceInfo->FailGroup->PathId));
+#if DBG
+ if (serviceAction == RESERVATION_ACTION_READ_KEYS) {
+
+ PPRI_REGISTRATION_LIST prInRegistrationList = Irp->AssociatedIrp.SystemBuffer;
+ ULONG numberOfKeys;
+ ULONG keyIndex;
+ ULONGLONG prKey;
+
+ REVERSE_BYTES(&numberOfKeys, &prInRegistrationList->AdditionalLength);
+ numberOfKeys /= 8;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): %u registrations keys present:\n",
+ DsmIds,
+ numberOfKeys));
+
+ for (keyIndex = 0; keyIndex < numberOfKeys; keyIndex++) {
+
+ REVERSE_BYTES_QUAD(&prKey, &(prInRegistrationList->ReservationKeyList[keyIndex]));
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): Registration Key %u: %I64x\n",
+ DsmIds,
+ keyIndex,
+ prKey));
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "\n"));
+
+ } else if (serviceAction == RESERVATION_ACTION_READ_RESERVATIONS) {
+
+ PPRI_RESERVATION_LIST prInReservationList = Irp->AssociatedIrp.SystemBuffer;
+ ULONG numberOfDescriptors;
+ PPRI_RESERVATION_DESCRIPTOR prInReservationDescriptor = prInReservationList->Reservations;
+ ULONGLONG prKey = 0;
+
+ REVERSE_BYTES(&numberOfDescriptors, &prInReservationList->AdditionalLength);
+ numberOfDescriptors /= sizeof(PRI_RESERVATION_DESCRIPTOR);
+ NT_ASSERT(numberOfDescriptors <= 1);
+
+ if (numberOfDescriptors == 1) {
+ REVERSE_BYTES_QUAD(&prKey, &prInReservationDescriptor->ReservationKey);
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): %u Reservation Key: %I64x\n",
+ DsmIds,
+ numberOfDescriptors,
+ prKey));
+ }
+#endif
+ //
+ // Done.
+ //
+ break;
+
+ } else {
+
+ //
+ // Check to see if the request failed because of a "transient error",
+ // like reservations released for example. If so, this is NOT an actual
+ // error and the request must be retried. Multiple retries may be required
+ // if for example the UA indicates that the TPGs are in transitioning state.
+ //
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID &&
+ Srb->SrbStatus & SRB_STATUS_ERROR &&
+ SrbGetScsiStatus(Srb) == SCSISTAT_CHECK_CONDITION) {
+
+ KeQueryTickCount((PLARGE_INTEGER)&currentTickCount);
+
+ senseInfoBuffer = SrbGetSenseInfoBuffer(Srb);
+ senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
+
+ if (group->PRKeyValid &&
+ DsmpShouldRetryPersistentReserveCommand(senseInfoBuffer, senseInfoBufferLength) &&
+ currentTickCount < finalTickCount) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN %u returned UA with error %x. Retrying same path %p.\n",
+ DsmIds,
+ serviceAction,
+ status,
+ deviceInfo->FailGroup->PathId));
+
+ KeResetEvent(&event);
+ Irp->IoStatus.Status = 0;
+
+ goto __DsmpPersistentReserveIn_RetryRequest;
+ }
+ }
+
+ //
+ // If a retry (along a different path) on failure is not required,
+ // we're done - just return this failure.
+ //
+ if (!retryOnAnother) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN for %u down %p failed with %x. Breaking out.\n",
+ DsmIds,
+ serviceAction,
+ deviceInfo->FailGroup->PathId,
+ status));
+
+ break;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN for %u down %p failed with %x. Sending down another path.\n",
+ DsmIds,
+ serviceAction,
+ deviceInfo->FailGroup->PathId,
+ status));
+
+ //
+ // If we are here, it is because the request failed down the chosen path
+ // and needs to be retried down a new path.
+ //
+ KeResetEvent(&event);
+ Irp->IoStatus.Status = 0;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): PR_IN for %u completed with status %x.\n",
+ DsmIds,
+ serviceAction,
+ status));
+
+ ExFreeToNPagedLookasideList(&DsmContext->CompletionContextList, completionContext);
+
+__Exit_DsmpPersistentReserveIn:
+
+ if (srbCopy != NULL) {
+ DsmpFreePool(srbCopy);
+ }
+
+ currentIrpStack->Parameters.Others.Argument3 = servicingDeviceInfo;
+ Irp->IoStatus.Status = status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveIn (DsmIds %p): Exiting function returning IRP status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpPersistentReserveCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ General-purpose completion routine for PR in and out commands sent synchronously.
+
+Arguments:
+
+ DeviceObject - Target of the request.
+ Irp - Command being sent.
+ Context - The event on which the caller is waiting.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+
+{
+ PDSM_COMPLETION_CONTEXT context = Context;
+ PKEVENT event;
+
+ // It is required to specify a DSM completion context
+ // when setting DsmpPersistentReserveCompletion as completion routine.
+ _Analysis_assume_(context != NULL);
+
+ event = (PKEVENT)(context->RequestUnique1);
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpPersistentReserveCompletion: DevInfo %p, IRP %p, Context %p\n",
+ context->DeviceInfo,
+ Irp,
+ Context));
+
+ if (Irp->PendingReturned) {
+
+ IoMarkIrpPending(Irp);
+ }
+
+ KeSetEvent(event, 0, FALSE);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
diff --git a/tests/projects/wdk/kmdf/msdsm/dsmtrace.mof b/tests/projects/wdk/kmdf/msdsm/dsmtrace.mof
new file mode 100644
index 000000000..d9e8cf4cc
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/dsmtrace.mof
@@ -0,0 +1,111 @@
+#pragma classflags("forceupdate")
+#pragma namespace("\\\\.\\root\\WMI")
+//
+// Copyright (C) 2004 Microsoft Corporation
+//
+// WPP Generated File
+//
+
+//ModuleName = wppCtlGuid (Init called in Function DriverEntry)
+[Dynamic,
+ Description("MSDSM Driver Tracing Provider"),
+ guid("{DEDADFF5-F99F-4600-B8C9-2D4D9B806B5B}"),
+ locale("MS\\0x409")]
+class MSDSMGuid : EventTrace
+{
+ [Description ("Enable Flags"),
+ ValueDescriptions{
+ "TRACE_FLAG_GENERAL Flag",
+ "TRACE_FLAG_PNP Flag",
+ "TRACE_FLAG_POWER Flag",
+ "TRACE_FLAG_RW Flag",
+ "TRACE_FLAG_IOCTL Flag",
+ "TRACE_FLAG_QUEUE Flag",
+ "TRACE_FLAG_WMI Flag",
+ "TRACE_FLAG_TIMER Flag",
+ "TRACE_FLAG_INIT Flag",
+ "TRACE_FLAG_LOCK Flag",
+ "TRACE_FLAG_DEBUG1 Flag",
+ "TRACE_FLAG_DEBUG2 Flag",
+ "TRACE_FLAG_MCN Flag",
+ "TRACE_FLAG_ISR Flag",
+ "TRACE_FLAG_ENUM Flag"},
+ DefineValues{
+ "TRACE_FLAG_GENERAL",
+ "TRACE_FLAG_PNP",
+ "TRACE_FLAG_POWER",
+ "TRACE_FLAG_RW",
+ "TRACE_FLAG_IOCTL",
+ "TRACE_FLAG_QUEUE",
+ "TRACE_FLAG_WMI",
+ "TRACE_FLAG_TIMER",
+ "TRACE_FLAG_INIT",
+ "TRACE_FLAG_LOCK",
+ "TRACE_FLAG_DEBUG1",
+ "TRACE_FLAG_DEBUG2",
+ "TRACE_FLAG_MCN",
+ "TRACE_FLAG_ISR",
+ "TRACE_FLAG_ENUM"},
+ Values{
+ "TRACE_FLAG_GENERAL",
+ "TRACE_FLAG_PNP",
+ "TRACE_FLAG_POWER",
+ "TRACE_FLAG_RW",
+ "TRACE_FLAG_IOCTL",
+ "TRACE_FLAG_QUEUE",
+ "TRACE_FLAG_WMI",
+ "TRACE_FLAG_TIMER",
+ "TRACE_FLAG_INIT",
+ "TRACE_FLAG_LOCK",
+ "TRACE_FLAG_DEBUG1",
+ "TRACE_FLAG_DEBUG2",
+ "TRACE_FLAG_MCN",
+ "TRACE_FLAG_ISR",
+ "TRACE_FLAG_ENUM"},
+ ValueMap{
+ "0x00000001",
+ "0x00000002",
+ "0x00000004",
+ "0x00000008",
+ "0x00000010",
+ "0x00000020",
+ "0x00000040",
+ "0x00000080",
+ "0x00000100",
+ "0x00000200",
+ "0x00000400",
+ "0x00000800",
+ "0x00001000",
+ "0x00002000",
+ "0x00004000"}
+ ]
+ uint32 Flags;
+ [Description ("Levels"),
+ ValueDescriptions{
+ "Abnormal exit or termination",
+ "Severe errors that need logging",
+ "Warnings such as allocation failure",
+ "Includes non-error cases",
+ "Detailed traces from intermediate steps" },
+ DefineValues{
+ "TRACE_LEVEL_FATAL",
+ "TRACE_LEVEL_ERROR",
+ "TRACE_LEVEL_WARNING"
+ "TRACE_LEVEL_INFORMATION",
+ "TRACE_LEVEL_VERBOSE" },
+ Values{
+ "Fatal",
+ "Error",
+ "Warning",
+ "Information",
+ "Verbose" },
+ ValueMap{
+ "0x1",
+ "0x2",
+ "0x3",
+ "0x4",
+ "0x5" },
+ ValueType("index")
+ ]
+ uint32 Level;
+};
diff --git a/tests/projects/wdk/kmdf/msdsm/intrface.c b/tests/projects/wdk/kmdf/msdsm/intrface.c
new file mode 100644
index 000000000..eeebf7f93
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/intrface.c
@@ -0,0 +1,5198 @@
+/*++
+
+Copyright (C) 2004-2010 Microsoft Corporation
+
+Module Name:
+
+ intrface.c
+
+Abstract:
+
+ This driver is the Microsoft Device Specific Module (DSM)
+ devices that conform with SPC-3 specs.
+ It exports behaviors that mpio.sys will use to determine how to
+ multipath these devices.
+
+ This file contains DriverEntry and all the functions that are
+ exported to MPIO.
+
+ This DSM is targetted towards Windows 2008 and above.
+
+Environment:
+
+ kernel mode only
+
+--*/
+
+#include "precomp.h"
+
+#ifdef DEBUG_USE_WPP
+#include "intrface.tmh"
+#endif
+
+#pragma warning (disable:4305)
+
+
+//
+// Flag to indicate whether to NT_ASSERT or ignore a particular condition.
+//
+BOOLEAN DoAssert = TRUE;
+
+//
+// OS Version Info
+// MSDSM is targetted towards Windows Server 2008 and above.
+//
+BOOLEAN gServer2008AndAbove = FALSE;
+
+//
+// Global to cache MPIO's Control Object.
+//
+PDEVICE_OBJECT gMPIOControlObject = NULL;
+
+//
+// Flag to indicate if the MPIO control object was referenced.
+//
+BOOLEAN gMPIOControlObjectRefd = FALSE;
+
+//
+// Global to cache the Driver Object.
+//
+PDRIVER_OBJECT gDsmDriverObject = NULL;
+
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(INIT, DriverEntry)
+#endif
+
+//
+// The code.
+//
+NTSTATUS
+DriverEntry(
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when the driver is loaded.
+
+Arguments:
+
+ DriverObject - Supplies the driver object.
+ RegistryPath - Supplies the registry path.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ PDSM_CONTEXT dsmContext = NULL;
+ PFILE_OBJECT fileObject;
+ WCHAR dosDeviceName[64] = DSM_MPIO_CONTROL_OBJECT_SYMLINK;
+ UNICODE_STRING mpUnicodeName;
+ NTSTATUS status = STATUS_SUCCESS;
+ MPIO_VERSION_INFO versionInfo = {0};
+ DSM_TYPE dsmMode = DsmType3;
+ DSM_MPIO_CONTEXT mpctlContext;
+ IO_STATUS_BLOCK ioStatus;
+
+
+ //
+ // Initialize the tracing subsystem.
+ // Any failure is handled by ETW itself.
+ //
+ WPP_INIT_TRACING(DriverObject, RegistryPath);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Entering function.\n",
+ DriverObject));
+
+ gDsmDriverObject = DriverObject;
+
+ //
+ // Determine the OS version.
+ //
+ gServer2008AndAbove = RtlIsNtDdiVersionAvailable(NTDDI_VISTA);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Server2008AndAbove is %!bool!.\n",
+ DriverObject,
+ gServer2008AndAbove));
+
+ //
+ // MSDSM is supported only on Server 2008 and above.
+ //
+ if (!gServer2008AndAbove) {
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DriverEntry;
+ }
+
+ //
+ // Build the mpio symbolic link name.
+ //
+ RtlInitUnicodeString(&mpUnicodeName, dosDeviceName);
+
+ //
+ // Get a pointer to mpio's deviceObject.
+ //
+ status = IoGetDeviceObjectPointer(&mpUnicodeName,
+ FILE_READ_ATTRIBUTES,
+ &fileObject,
+ &gMPIOControlObject);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_FATAL,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Failed to communicate with MPIO control object. Status %x.\n",
+ DriverObject,
+ status));
+
+ goto __Exit_DriverEntry;
+ }
+
+ ObReferenceObject(gMPIOControlObject);
+ gMPIOControlObjectRefd = TRUE;
+ ObDereferenceObject(fileObject);
+
+ status = DsmGetVersion(&versionInfo, sizeof(MPIO_VERSION_INFO));
+
+ if (!NT_SUCCESS(status)) {
+
+ //
+ // If we can't get the version, that means we aren't using a compatible
+ // version of MPIO drivers and so should not continue.
+ //
+ TracePrint((TRACE_LEVEL_FATAL,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): MPIO version unknown - DSM exiting.\n",
+ DriverObject));
+
+ status = STATUS_UNSUCCESSFUL;
+ goto __Exit_DriverEntry;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): MPIO version %d.%d.%d.%d.\n",
+ DriverObject,
+ versionInfo.MajorVersion,
+ versionInfo.MinorVersion,
+ versionInfo.ProductBuild,
+ versionInfo.QfeNumber));
+
+ RtlZeroMemory(&gDsmInitData, sizeof(DSM_INIT_DATA));
+
+ //
+ // Must be newer than 1.0.7.0 to support DSM type 2 upwards.
+ //
+ if ((versionInfo.MajorVersion > 1) ||
+ (versionInfo.MinorVersion >= 1) ||
+ (versionInfo.ProductBuild > 7) ||
+ (versionInfo.QfeNumber >= 1)) {
+
+ //
+ // Must be newer than 1.18 to support DSM's versioning
+ //
+ if (versionInfo.MajorVersion > 1 ||
+ versionInfo.MinorVersion > 17) {
+
+ dsmMode = DsmType6;
+
+ {
+ RTL_OSVERSIONINFOW osVersion = {0};
+
+ osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
+ RtlGetVersion(&osVersion);
+
+ gDsmInitData.DsmVersion.MajorVersion = osVersion.dwMajorVersion;
+ gDsmInitData.DsmVersion.MinorVersion = osVersion.dwMinorVersion;
+ gDsmInitData.DsmVersion.ProductBuild = osVersion.dwBuildNumber;
+ gDsmInitData.DsmVersion.QfeNumber = 0;
+ }
+ }
+ } else {
+
+ //
+ // We cannot use this DSM with older versions of the MPIO drivers.
+ //
+ TracePrint((TRACE_LEVEL_FATAL,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): MPIO version not supported - DSM exiting.\n",
+ DriverObject));
+
+ status = STATUS_UNSUCCESSFUL;
+ goto __Exit_DriverEntry;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Setting DSM type to %d.\n",
+ DriverObject,
+ dsmMode));
+
+ //
+ // Build the init data structure.
+ //
+ dsmContext = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_CONTEXT),
+ DSM_TAG_DSM_CONTEXT);
+ if (!dsmContext) {
+
+ TracePrint((TRACE_LEVEL_FATAL,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Failed to allocate memory for DSM Context.\n",
+ DriverObject));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DriverEntry;
+ }
+
+ //
+ // Set-up the init data
+ //
+ gDsmInitData.DsmContext = (PVOID) dsmContext;
+ gDsmInitData.InitDataSize = sizeof(DSM_INIT_DATA);
+
+ gDsmInitData.DsmInquireDriver = DsmInquire;
+ gDsmInitData.DsmCompareDevices = DsmCompareDevices;
+ gDsmInitData.DsmGetControllerInfo = DsmGetControllerInfo;
+ gDsmInitData.DsmSetDeviceInfo = DsmSetDeviceInfo;
+ gDsmInitData.DsmIsPathActive = DsmIsPathActive;
+ gDsmInitData.DsmPathVerify = DsmPathVerify;
+ gDsmInitData.DsmInvalidatePath = DsmInvalidatePath;
+ gDsmInitData.DsmMoveDevice = DsmMoveDevice;
+ gDsmInitData.DsmRemovePending = DsmRemovePending;
+ gDsmInitData.DsmRemoveDevice = DsmRemoveDevice;
+ gDsmInitData.DsmRemovePath = DsmRemovePath;
+ gDsmInitData.DsmSrbDeviceControl = DsmSrbDeviceControl;
+ gDsmInitData.DsmLBGetPath = DsmLBGetPath;
+ gDsmInitData.DsmInterpretErrorEx = DsmInterpretError;
+ gDsmInitData.DsmUnload = DsmUnload;
+ gDsmInitData.DsmSetCompletion = DsmSetCompletion;
+ gDsmInitData.DsmCategorizeRequest = DsmCategorizeRequest;
+ gDsmInitData.DsmBroadcastSrb = DsmBroadcastRequest;
+ gDsmInitData.DsmIsAddressTypeSupported = DsmIsAddressTypeSupported;
+ gDsmInitData.DsmDeviceNotUsed = DsmDeviceNotUsed;
+
+ //
+ // Since MSDSM is for SPC-3 compliant devices, MPIO should be able to build
+ // a serial number for the device.
+ //
+ gDsmInitData.DsmDeviceSerialNumber = NULL;
+
+ //
+ // Notifies MPIO of the appropriate Type support
+ //
+ gDsmInitData.DsmType = dsmMode;
+
+ gDsmInitData.DriverObject = DriverObject;
+
+
+ //
+ // Set-up the WMI Info.
+ //
+ DsmpWmiInitialize(&gDsmInitData.DsmWmiInfo, RegistryPath);
+ DsmpDsmWmiInitialize(&gDsmInitData.DsmWmiGlobalInfo, RegistryPath);
+
+ RtlInitUnicodeString(&gDsmInitData.DisplayName, DSM_FRIENDLY_NAME);
+
+ //
+ // Initialize some of the fields in DSM Context structure.
+ //
+ KeInitializeSpinLock(&dsmContext->SupportedDevicesListLock);
+ InitializeListHead(&dsmContext->GroupList);
+ InitializeListHead(&dsmContext->DeviceList);
+ InitializeListHead(&dsmContext->FailGroupList);
+ InitializeListHead(&dsmContext->ControllerList);
+ InitializeListHead(&dsmContext->StaleFailGroupList);
+
+ //
+ // Build the list context structures used for completion processing.
+ //
+ ExInitializeNPagedLookasideList(&dsmContext->CompletionContextList,
+ NULL,
+ NULL,
+ POOL_NX_ALLOCATION,
+ sizeof(DSM_COMPLETION_CONTEXT),
+ DSM_TAG_GENERIC,
+ 0);
+
+ RtlZeroMemory(&mpctlContext, sizeof(DSM_MPIO_CONTEXT));
+
+ //
+ // Send the IOCTL to mpio.sys to register ourselves.
+ //
+ DsmSendDeviceIoControlSynchronous(IOCTL_MPDSM_REGISTER,
+ gMPIOControlObject,
+ &gDsmInitData,
+ &mpctlContext,
+ sizeof(DSM_INIT_DATA),
+ sizeof(DSM_MPIO_CONTEXT),
+ TRUE,
+ &ioStatus);
+
+ status = ioStatus.Status;
+
+ if (NT_SUCCESS(status)) {
+
+ dsmContext->MPIOContext = mpctlContext.MPIOContext;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Registered with MPIO.\n",
+ DriverObject));
+
+ DriverObject->DriverUnload = DsmDriverUnload;
+
+ //
+ // Query the registry for disabling/enabling statistics gathering
+ //
+ if (STATUS_OBJECT_NAME_NOT_FOUND == DsmpGetStatsGatheringChoice(dsmContext, (PULONG)&dsmContext->DisableStatsGathering)) {
+
+ //
+ // If the value does not exist, write the default to registry.
+ //
+ DsmpSetStatsGatheringChoice(dsmContext, (ULONG)dsmContext->DisableStatsGathering);
+ }
+ }
+
+__Exit_DriverEntry:
+
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Exiting function successfully.\n",
+ DriverObject));
+ } else {
+
+ //
+ // Since the DSM is going to be unloaded but without DriverUnload being
+ // called, we need to perform cleanup here.
+ //
+ if (dsmContext != NULL) {
+ DsmpFreeDSMResources(dsmContext);
+ dsmContext = NULL;
+ }
+
+ if (gMPIOControlObjectRefd) {
+
+ //
+ // Drop the reference on MPIO's control object.
+ //
+ ObDereferenceObject(gMPIOControlObject);
+ gMPIOControlObjectRefd = FALSE;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DriverEntry (DrvObj %p): Exiting function with status %x.\n",
+ DriverObject,
+ status));
+
+ //
+ // Stop the tracing subsystem.
+ // NOTE: once we unregister ETW, no more TracePrint can be done, so we
+ // must ensure that ETW unregister is the last thing that happens.
+ //
+ WPP_CLEANUP(gDsmDriverObject);
+ }
+
+ return status;
+}
+
+
+VOID
+DsmDriverUnload(
+ _In_ IN PDRIVER_OBJECT DriverObject
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when the driver is unloaded.
+
+Arguments:
+
+ DriverObject - Supplies the driver object.
+
+Return Value:
+
+ Nothing
+
+--*/
+{
+ DSM_DEREGISTER_DATA deregisterData;
+ IO_STATUS_BLOCK ioStatus;
+
+ deregisterData.DeregisterDataSize = sizeof(DSM_DEREGISTER_DATA);
+ deregisterData.DriverObject = DriverObject;
+ deregisterData.DsmContext = gDsmInitData.DsmContext;
+ deregisterData.MpioContext = ((PDSM_CONTEXT)(gDsmInitData.DsmContext))->MPIOContext;
+ //
+ // Send the IOCTL to mpio.sys to de-register ourselves.
+ //
+ DsmSendDeviceIoControlSynchronous(IOCTL_MPDSM_DEREGISTER,
+ gMPIOControlObject,
+ &deregisterData,
+ NULL,
+ sizeof(DSM_DEREGISTER_DATA),
+ 0,
+ TRUE,
+ &ioStatus);
+
+ NT_ASSERT(NT_SUCCESS(ioStatus.Status));
+
+
+
+ return;
+}
+
+
+NTSTATUS
+DsmInquire(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDEVICE_OBJECT TargetDevice,
+ _In_ IN PDEVICE_OBJECT PortObject,
+ _In_ IN PSTORAGE_DEVICE_DESCRIPTOR Descriptor,
+ _In_ IN PSTORAGE_DEVICE_ID_DESCRIPTOR DeviceIdList,
+ _Out_ OUT PVOID *DsmIdentifier
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to determine if TargetDevice belongs to
+ the DSM. If this is a supported device DsmIdentifier will be
+ updated with 'deviceInfo'.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ TargetDevice - DeviceObject for the child device.
+ PortObject - The Port driver FDO on which TargetDevice resides.
+ Descriptor - Pointer to the device descriptor corresponding to TargetDevice.
+ Rehash of inquiry data, plus serial number information
+ (if applicable).
+ DeviceIdList - VPD Page 0x83 information.
+ DsmIdentifier - Pointer to be filled in by the DSM on success.
+
+Return Value:
+
+ STATUS_NOT_SUPPORTED - if not on the SupportList.
+ STATUS_INSUFFICIENT_RESOURCES - No mem.
+ STATUS_SUCCESS
+--*/
+{
+ PDSM_CONTEXT dsmContext = DsmContext;
+ PDSM_DEVICE_INFO deviceInfo = NULL;
+ PDSM_GROUP_ENTRY group;
+ BOOLEAN newGroup;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroupEntry = NULL;
+ PDSM_TARGET_PORT_LIST_ENTRY targetPortEntry = NULL;
+ PSTR serialNumber = NULL;
+ SIZE_T serialNumberLength = 0;
+ NTSTATUS status;
+ ULONG allocationLength;
+ BOOLEAN serialNumberAllocated = FALSE;
+ KIRQL irql = PASSIVE_LEVEL; // Initialize variable to prevent C4701 error
+ BOOLEAN supported = FALSE;
+ BOOLEAN spinlockHeld = FALSE;
+ UCHAR vendorId[9] = {0};
+ UCHAR productId[17] = {0};
+ INQUIRYDATA inquiryData;
+ UCHAR alua = DSM_DEVINFO_ALUA_NOT_SUPPORTED;
+ ULONG index;
+ PDSM_IDS controllerObjects = NULL;
+ PDEVICE_OBJECT controllerDeviceObject;
+ PLIST_ENTRY entry = NULL;
+ PSTORAGE_DESCRIPTOR_HEADER controllerIdHeader = NULL;
+ PULONG relativeTargetPortId = NULL;
+ PUSHORT targetPortGroupId = NULL;
+ PUCHAR targetPortGroupsInfo = NULL;
+ ULONG targetPortGroupsInfoLength = 0;
+ PSTR controllerSerialNumber;
+ BOOLEAN match = FALSE;
+ BOOLEAN doneUpdating = FALSE;
+ PDSM_CONTROLLER_LIST_ENTRY controllerEntry = NULL;
+ PDSM_TARGET_PORT_DEVICELIST_ENTRY tp_device = NULL;
+ PWSTR hardwareId = NULL;
+ PWCHAR deviceName = NULL;
+ ULONG tempResult = 0;
+ ULONG maxPRRetryTimeDuringStateTransition = DSM_MAX_PR_UNIT_ATTENTION_RETRY_TIME;
+ BOOLEAN useCacheForLeastBlocks = FALSE;
+ ULONGLONG cacheSizeForLeastBlocks = 0;
+ BOOLEAN fakeControllerEntryExists = FALSE;
+ STORAGE_IDENTIFIER_CODE_SET serialNumberCodeSet = StorageIdCodeSetReserved;
+
+#if DBG
+ BOOLEAN multiport;
+#endif
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Entering function.\n",
+ TargetDevice));
+
+ //
+ // 1. Get standard inquiry for the device. Check if SPC-3 compliant.
+ // If not compliant, check SupportedDeviceList.
+ // 2. Create device serial number.
+ // 3. Create a partially populated deviceInfo.
+ // DeviceDescriptor.
+ // SCSI address.
+ // Save off serial number.
+ // ALUA, port FDO, etc.
+ // 4. Create device name.
+ // 5. If ALUA support, send down Report Target Port Groups.
+ // 6. Find the group. If none, build one.
+ // 7. If new group, build target port groups and target ports info.
+ // Else, update target port groups and target ports info.
+ // 8. If both implicit as well as explicit transitions allowed, disable implicit.
+ // 9. Get list of controllers objects and get VPD 0x83 for each (only if no
+ // match for existing ones).
+ // Match returned ids of type 0x5 with what was returned in Report Target Port Groups.
+ // If no type 0x5 identifier, use SCSI address.
+ // Create controller list (delete stale entries).
+ //
+
+
+ //
+ // Query the registry to find out what devices are being supported
+ // on this machine.
+ //
+ DsmpGetDeviceList(dsmContext);
+
+ status = DsmpGetStandardInquiryData(TargetDevice, &inquiryData);
+
+ if (NT_SUCCESS(status)) {
+
+ supported = DsmpCheckScsiCompliance(TargetDevice,
+ &inquiryData,
+ Descriptor,
+ DeviceIdList);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to get inquiry data with status %x.\n",
+ TargetDevice,
+ status));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ //
+ // Since the device isn't SPC-3 compliant, check if the device is on the
+ // SupportedDeviceList.
+ //
+ if (!supported) {
+
+
+ if (!supported) {
+
+ //
+ // Get the inquiry data embedded in the device descriptor.
+ //
+ RtlStringCchCopyA((LPSTR)vendorId,
+ sizeof(vendorId) / sizeof(vendorId[0]),
+ (LPCSTR)(&inquiryData.VendorId));
+
+ RtlStringCchCopyA((LPSTR)productId,
+ sizeof(productId) / sizeof(productId[0]),
+ (LPCSTR)(&inquiryData.ProductId));
+
+ supported = DsmpDeviceSupported(dsmContext,
+ (PCSZ)vendorId,
+ (PCSZ)productId);
+ }
+
+ if (!supported) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Unsupported Device.\n",
+ TargetDevice));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+ }
+
+ //
+ // Find out if device can be accessed via mulitple ports. This info is
+ // important since it will determine whether or not to send down a
+ // ReportTargetPortGroups command.
+ //
+#if DBG
+ multiport = (inquiryData.MultiPort & 0x10) ? TRUE : FALSE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Is %ws multiported.\n",
+ TargetDevice,
+ multiport ? L"" : L"not"));
+#endif
+
+ //
+ // Query the assymmetric states transition method
+ //
+ switch ((inquiryData.Reserved >> 0x4) & 0x3) {
+ case 1: alua = DSM_DEVINFO_ALUA_IMPLICIT;
+ break;
+
+ case 2: alua = DSM_DEVINFO_ALUA_EXPLICIT;
+ break;
+
+ case 3: alua = DSM_DEVINFO_ALUA_IMPLICIT | DSM_DEVINFO_ALUA_EXPLICIT;
+ break;
+
+ default: alua = DSM_DEVINFO_ALUA_NOT_SUPPORTED;
+ break;
+ }
+
+ //
+ // Get some information about this device. The preferred info is
+ // from the Device ID Page.
+ //
+ if (DeviceIdList) {
+
+ //
+ // This will parse out the 'best' identifier and return
+ // a NULL-terminated ascii string.
+ //
+ serialNumber = (PSTR)DsmpParseDeviceID(DeviceIdList,
+ DSM_DEVID_SERIAL_NUMBER,
+ NULL,
+ &serialNumberCodeSet,
+ FALSE);
+
+ if (!serialNumber) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): NULL serial number.\n",
+ TargetDevice));
+
+ //
+ // Either an allocation failed, or the DeviceIdList is malformed.
+ //
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ //
+ // Indicate that the serialnumber buffer is allocated.
+ //
+ serialNumberAllocated = TRUE;
+ serialNumberLength = strlen((const char*)serialNumber);
+
+ } else {
+
+ //
+ // Get the serial number of this device. Use the serial number
+ // page (0x80). Ensure that the device's serial number is
+ // present. If not, can't claim support for this drive.
+ //
+
+ if (!Descriptor ||
+ (Descriptor->SerialNumberOffset == MAXULONG) ||
+ (Descriptor->SerialNumberOffset == 0)) {
+
+ //
+ // The port driver currently doesn't get the VPD page 0x80,
+ // if the device doesn't support GET_SUPPORTED_PAGES. Check to
+ // see whether there actually is a serial number.
+ //
+ serialNumber = DsmpGetSerialNumber(TargetDevice);
+
+ if (!serialNumber) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): serialNumber = NULL.\n",
+ TargetDevice));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+
+ } else {
+ serialNumberAllocated = TRUE;
+ serialNumberLength = strlen((const char*)serialNumber);
+ }
+ }
+ }
+
+ //
+ // Allocate for the device. This is also used as DsmId.
+ //
+ allocationLength = sizeof(DSM_DEVICE_INFO);
+
+ //
+ // As DSM_DEVICE_INFO has storage for the descriptor, add only
+ // the additional stuff that's at the end.
+ //
+ if (Descriptor) {
+ status = RtlULongSub(Descriptor->Size, sizeof(STORAGE_DEVICE_DESCRIPTOR), &tempResult);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Arithmetic underflow - status %x.\n",
+ TargetDevice,
+ status));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+ }
+
+ status = RtlULongAdd(allocationLength, tempResult, &allocationLength);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Arithmetic overflow - status %x.\n",
+ TargetDevice,
+ status));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ deviceInfo = DsmpAllocatePool(NonPagedPoolNx,
+ allocationLength,
+ DSM_TAG_DEV_INFO);
+ if (!deviceInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to allocate Device Info.\n",
+ TargetDevice));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ deviceInfo->State = deviceInfo->PreviousState = deviceInfo->TempPreviousStateForLB = deviceInfo->ALUAState = deviceInfo->LastKnownGoodState = DSM_DEV_NOT_USED_STATE;
+ deviceInfo->DesiredState = DSM_DEV_UNDETERMINED;
+ //
+ // Copy over the StorageDescriptor.
+ //
+ if (Descriptor) {
+ RtlCopyMemory(&deviceInfo->Descriptor,
+ Descriptor,
+ Descriptor->Size);
+ }
+
+ //
+ // Get the scsi address for this device. Note that on success, DsmGetScsiAddress()
+ // will allocate memory which we are responsible for freeing.
+ //
+ status = DsmGetScsiAddress(TargetDevice,
+ &deviceInfo->ScsiAddress);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Error %x while getting scsi address.\n",
+ TargetDevice,
+ status));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ //
+ // Capture the serial number allocated flag.
+ //
+ deviceInfo->SerialNumberAllocated = serialNumberAllocated;
+
+ //
+ // Set the serial number.
+ //
+ if (!serialNumberAllocated) {
+
+ PSTORAGE_DEVICE_DESCRIPTOR descriptor;
+
+ //
+ // serialNumber is not pointing to the buffer passed by MPIO. Update
+ // it to point to the Device Descriptor allocated by the DSM.
+ //
+ descriptor = &(deviceInfo->Descriptor);
+
+ NT_ASSERT(descriptor->SerialNumberOffset != 0 && descriptor->SerialNumberOffset != MAXULONG);
+
+ serialNumber = (PCHAR)descriptor + descriptor->SerialNumberOffset;
+ serialNumberLength = strlen((const char*)serialNumber);
+ }
+
+ if (alua == (DSM_DEVINFO_ALUA_IMPLICIT | DSM_DEVINFO_ALUA_EXPLICIT)) {
+
+ BOOLEAN disableImplicit = FALSE;
+
+ status = DsmpDisableImplicitStateTransition(TargetDevice, &disableImplicit);
+
+ if (NT_SUCCESS(status)) {
+
+ if (disableImplicit) {
+
+ alua &= ~DSM_DEVINFO_ALUA_IMPLICIT;
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Disabled implicit ALUA state transition.\n",
+ TargetDevice));
+
+ //
+ // Record that the storage actually supported implicit also, but we
+ // turned it OFF.
+ //
+ deviceInfo->ImplicitDisabled = TRUE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Storage support both transitions but does NOT allow disabling Implicit.\n",
+ TargetDevice));
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to disable implicit ALUA state transitions - status %x.\n",
+ TargetDevice,
+ status));
+ }
+ }
+
+ deviceInfo->SerialNumber = serialNumber;
+
+ //
+ // Save the Physical Device Object (PDO) of the device.
+ // Used to verify that no two devices have the same PDO.
+ //
+ deviceInfo->PortPdo = TargetDevice;
+
+ //
+ // Save the FDO of the adapter. Used for handling reserve\release
+ //
+ deviceInfo->PortFdo = PortObject;
+
+ //
+ // Set the signature.
+ //
+ deviceInfo->DeviceSig = DSM_DEVICE_SIG;
+
+ deviceInfo->DsmContext = DsmContext;
+
+ deviceInfo->ALUASupport = alua;
+
+ //
+ // Build the name (using serialnumber) that will be used as registry key
+ // to store Load Balance settings for this device.
+ //
+ deviceName = DsmpBuildDeviceName(deviceInfo, serialNumber, serialNumberLength);
+
+ if (!deviceName) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to allocate device name for %p.\n",
+ TargetDevice,
+ deviceInfo));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+
+ //
+ // Send down ReportTargetPortGroups command and keep the info handy.
+ //
+ if (alua != DSM_DEVINFO_ALUA_NOT_SUPPORTED) {
+
+ status = DsmpReportTargetPortGroups(TargetDevice,
+ &targetPortGroupsInfo,
+ &targetPortGroupsInfoLength);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to report target port groups for %p. Status %x.\n",
+ TargetDevice,
+ deviceInfo,
+ status));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ //
+ // We've just sent down an RTPG (relatively expensive operation), and it
+ // succeeded, so sending down one more as part part of the initialization
+ // in PathVerify() since it is going to be called almost immediately.
+ //
+ deviceInfo->IgnorePathVerify = TRUE;
+ }
+
+ //
+ // Query the registry for max time to retry failed PR requests
+ //
+ DsmpGetMaxPRRetryTime(DsmContext, &maxPRRetryTimeDuringStateTransition);
+
+ //
+ // Query the registry to see if the user has overridden the default
+ // Least Blocks settings.
+ //
+ status = DsmpQueryCacheInformationFromRegistry(DsmContext,
+ &useCacheForLeastBlocks,
+ &cacheSizeForLeastBlocks);
+
+ if (!NT_SUCCESS(status)) {
+ //
+ // Couldn't get the settings from the registry so fall back on the
+ // default for Least Blocks.
+ //
+ useCacheForLeastBlocks = TRUE;
+ cacheSizeForLeastBlocks = DSM_LEAST_BLOCKS_DEFAULT_THRESHOLD;
+ }
+
+ //
+ // Build LUN's hardware id. Needs to be called at PASSIVE_LEVEL, so
+ // do it before grabbing the lock. The hardware id of the group is
+ // later set under the protection of the lock.
+ //
+ hardwareId = DsmpBuildHardwareId(deviceInfo);
+
+ irql = ExAcquireSpinLockExclusive(&(((PDSM_CONTEXT)DsmContext)->DsmContextLock));
+ spinlockHeld = TRUE;
+
+ status = STATUS_SUCCESS;
+
+ //
+ // See if there is an existing Multi-path group to which this belongs.
+ // (same serial number).
+ //
+ group = DsmpFindDevice(DsmContext, deviceInfo, FALSE);
+ if (!group) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): First device %p in the group.\n",
+ TargetDevice,
+ deviceInfo));
+
+ newGroup = TRUE;
+
+ //
+ // This device doesn't belong to any group yet. So Build a multi-path
+ // group entry. This'll represents all paths to a particular device.
+ //
+ group = DsmpBuildGroupEntry(DsmContext, deviceInfo);
+ if (group) {
+
+ //
+ // Set the registry key name for the new group
+ //
+ group->RegistryKeyName = deviceName;
+ deviceName = NULL;
+
+ //
+ // Cache the LUN's hardware id
+ //
+ if (!hardwareId) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to build a hardwareId for %p.\n",
+ TargetDevice,
+ deviceInfo));
+ }
+
+ group->HardwareId = hardwareId;
+ hardwareId = NULL;
+
+ group->UseCacheForLeastBlocks = useCacheForLeastBlocks;
+ group->CacheSizeForLeastBlocks = cacheSizeForLeastBlocks;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to allocate Group Entry for %p.\n",
+ TargetDevice,
+ deviceInfo));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Found group %p for device %p.\n",
+ TargetDevice,
+ group,
+ deviceInfo));
+
+ newGroup = FALSE;
+
+ if (!group->HardwareId) {
+
+ //
+ // If we weren't successful in previously building the hardware id for this LUN,
+ // retry doing it again now.
+ //
+ hardwareId = DsmpBuildHardwareId(deviceInfo);
+ if (!hardwareId) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to build a hardwareId for %p.\n",
+ TargetDevice,
+ deviceInfo));
+ }
+
+ group->HardwareId = hardwareId;
+ hardwareId = NULL;
+ }
+
+ //
+ // Sanity check that we haven't been presented with device instances
+ // with different ALUA support. So compare with the first device instance.
+ //
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ if (group->DeviceList[index]) {
+
+ break;
+ }
+ }
+
+ if (index < DSM_MAX_PATHS) {
+
+ //
+ // Only acceptable conditions are:
+ // 1. both have same support,
+ // 2. one has explicit, while other has both explicit-and-implicit (this
+ // is a potential valid case because DsmpDisableImplicitStateTransition
+ // may have failed).
+ //
+ if (!((deviceInfo->ALUASupport == group->DeviceList[index]->ALUASupport) ||
+ ((deviceInfo->ALUASupport == DSM_DEVINFO_ALUA_EXPLICIT && deviceInfo->ImplicitDisabled) &&
+ (group->DeviceList[index]->ALUASupport == (DSM_DEVINFO_ALUA_IMPLICIT | DSM_DEVINFO_ALUA_EXPLICIT))) ||
+ ((group->DeviceList[index]->ALUASupport == DSM_DEVINFO_ALUA_EXPLICIT && group->DeviceList[index]->ImplicitDisabled) &&
+ (deviceInfo->ALUASupport == (DSM_DEVINFO_ALUA_IMPLICIT | DSM_DEVINFO_ALUA_EXPLICIT))))) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Mismatch in device instances' ALUA support %d vs %d.\n",
+ TargetDevice,
+ deviceInfo->ALUASupport,
+ group->DeviceList[index]->ALUASupport));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(group);
+
+ group->MaxPRRetryTimeDuringStateTransition = maxPRRetryTimeDuringStateTransition;
+
+ if (alua == DSM_DEVINFO_ALUA_NOT_SUPPORTED) {
+
+ //
+ // Since the device doesn't support ALUA, it is automatically
+ // symmetric LU access.
+ //
+ group->Symmetric = TRUE;
+
+ if (newGroup) {
+
+ //
+ // This is the first in the group, so make it the active device.
+ // The actual active/passive devices will be set-up when
+ // LB policies are set by the user.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ } else {
+
+ //
+ // Already something active, this will be the fail-over device
+ // until the load-balance groups are set-up.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_STANDBY;
+ }
+
+ } else {
+
+ if (DeviceIdList == NULL) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): No Device ID List.\n",
+ TargetDevice));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ if (alua == DSM_DEVINFO_ALUA_IMPLICIT) {
+
+ //
+ // Assume that the LU access is symmetric. When parsing the TPG
+ // info, if we find that not all TPGs are in the same LU access
+ // state, then we know that this the access is asymmetric.
+ //
+ group->Symmetric = TRUE;
+ }
+
+ //
+ // Build TPG and TP info
+ //
+ status = DsmpParseTargetPortGroupsInformation(DsmContext,
+ group,
+ targetPortGroupsInfo,
+ targetPortGroupsInfoLength);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to build TPG information - status %x.\n",
+ TargetDevice,
+ status));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup;
+
+ targetPortGroup = group->TargetPortGroupList[index];
+
+ if (targetPortGroup) {
+
+ DsmpUpdateTargetPortGroupDevicesStates(targetPortGroup, targetPortGroup->AsymmetricAccessState);
+ }
+ }
+
+ //
+ // Find the target port through which this devInfo was exposed.
+ //
+ relativeTargetPortId = (PULONG)DsmpParseDeviceID(DeviceIdList,
+ DSM_DEVID_RELATIVE_TARGET_PORT,
+ NULL,
+ NULL,
+ FALSE);
+ NT_ASSERT(relativeTargetPortId);
+
+ if (!relativeTargetPortId) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Couldn't retrieve relative TP id.\n",
+ TargetDevice));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ //
+ // Find the target port group
+ //
+ targetPortGroupId = (PUSHORT)DsmpParseDeviceID(DeviceIdList,
+ DSM_DEVID_TARGET_PORT_GROUP,
+ NULL,
+ NULL,
+ FALSE);
+ NT_ASSERT(targetPortGroupId);
+
+ if (targetPortGroupId) {
+
+ //
+ // Find the target port group entry
+ //
+ targetPortGroupEntry = DsmpFindTargetPortGroup(DsmContext,
+ group,
+ targetPortGroupId);
+
+ NT_ASSERT(targetPortGroupEntry);
+
+ if (targetPortGroupEntry) {
+
+ //
+ // Look through the target port group to find the target port
+ //
+ targetPortEntry = DsmpFindTargetPort(DsmContext,
+ targetPortGroupEntry,
+ relativeTargetPortId);
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Couldn't find TPG Id %x's entry.\n",
+ TargetDevice,
+ *targetPortGroupId));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ NT_ASSERT(targetPortEntry);
+
+ if (!targetPortEntry) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Couldn't find relative TP %x's entry.\n",
+ TargetDevice,
+ *relativeTargetPortId));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ //
+ // Update the devInfo with the target port and target port group
+ // info
+ //
+ deviceInfo->TargetPortGroup = targetPortGroupEntry;
+ deviceInfo->TargetPort = targetPortEntry;
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = deviceInfo->ALUAState = deviceInfo->TargetPortGroup->AsymmetricAccessState;
+
+ tp_device = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_TARGET_PORT_DEVICELIST_ENTRY),
+ DSM_TAG_TP_DEVICE_LIST_ENTRY);
+
+ if (!tp_device) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Insufficient resources allocating TP device list entry.\n",
+ TargetDevice));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+
+ //
+ // Add the device to the list of devices that are exposed via this target port.
+ //
+ tp_device->DeviceInfo = deviceInfo;
+ InterlockedIncrement((LONG volatile*)&targetPortEntry->Count);
+ InsertTailList(&targetPortEntry->TP_DeviceList, &tp_device->ListEntry);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to retrieve TPG Id.\n",
+ TargetDevice));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Add the deviceInfo to the list. DO NOT modify the status
+ // variable if this function returns SUCCESS.
+ //
+ status = DsmpAddDeviceEntry(DsmContext,
+ group,
+ deviceInfo);
+ if (NT_SUCCESS(status)) {
+
+ *DsmIdentifier = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Added device %p to group %p.\n",
+ TargetDevice,
+ *DsmIdentifier,
+ group));
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to add device %p to group %p - status %x.\n",
+ TargetDevice,
+ deviceInfo,
+ group,
+ status));
+
+ //
+ // We weren't able to add this deviceInfo to the list so we must
+ // remove its entry on the target port list before the deviceInfo
+ // is freed.
+ //
+ DsmpRemoveDeviceFromTargetPortList(deviceInfo);
+
+ if (newGroup) {
+
+ DsmpRemoveGroupEntry(DsmContext, group, FALSE);
+
+ DsmpFreePool(group);
+ group = NULL;
+ }
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __Exit_DsmInquire;
+ }
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+ spinlockHeld = FALSE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Device %p added. State %d, Desired State %d\n",
+ TargetDevice,
+ deviceInfo,
+ deviceInfo->State,
+ deviceInfo->DesiredState));
+
+ //
+ // Update the global list of controller objects
+ //
+ controllerObjects = DsmGetAssociatedDevice(dsmContext->MPIOContext,
+ PortObject,
+ 0x0C);
+ if (controllerObjects) {
+
+ //
+ // This loop needs its own status variable so that it does not
+ // inadvertently overwrite a STATUS_SUCCESS from the code above.
+ //
+ NTSTATUS matchStatus = STATUS_SUCCESS;
+ PSCSI_ADDRESS controllerScsiAddress = NULL;
+
+ //
+ // Walk through the list and get VPD 0x83 data and associate the devInfo
+ // with the controller object.
+ //
+ for (index = 0; index < controllerObjects->Count; index++) {
+
+ STORAGE_IDENTIFIER_CODE_SET codeSet = StorageIdCodeSetReserved;
+
+ //
+ // Free the previously allocated SCSI address, if any.
+ //
+ if (controllerScsiAddress) {
+ DsmpFreePool(controllerScsiAddress);
+ controllerScsiAddress = NULL;
+ }
+
+ controllerDeviceObject = (PDEVICE_OBJECT)controllerObjects->IdList[index];
+ NT_ASSERT(controllerDeviceObject);
+
+ if (!controllerDeviceObject) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Controller list %p's index %x is NULL.\n",
+ TargetDevice,
+ controllerObjects,
+ index));
+
+ continue;
+ }
+
+ matchStatus = DsmpGetDeviceIdList(controllerDeviceObject, &controllerIdHeader);
+ NT_ASSERT(NT_SUCCESS(matchStatus));
+
+ if (!NT_SUCCESS(matchStatus)) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to get DeviceId list for controller %p - status %x.\n",
+ TargetDevice,
+ controllerDeviceObject,
+ matchStatus));
+
+ continue;
+ }
+
+ controllerSerialNumber = DsmpParseDeviceID((PSTORAGE_DEVICE_ID_DESCRIPTOR)controllerIdHeader,
+ DSM_DEVID_SERIAL_NUMBER,
+ NULL,
+ &codeSet,
+ FALSE);
+ NT_ASSERT(controllerSerialNumber);
+ DsmpFreePool(controllerIdHeader);
+
+ if (!controllerSerialNumber) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to parse serial number for controller %p.\n",
+ TargetDevice,
+ controllerDeviceObject));
+
+ continue;
+ }
+
+ //
+ // Note that on success, DsmGetScsiAddress() will allocate memory
+ // which we are responsible for freeing.
+ //
+ matchStatus = DsmGetScsiAddress(controllerDeviceObject, &controllerScsiAddress);
+ NT_ASSERT(NT_SUCCESS(matchStatus));
+
+ if (!NT_SUCCESS(matchStatus)) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to get controller %p's scsi address - status %x.\n",
+ TargetDevice,
+ controllerDeviceObject,
+ matchStatus));
+
+ continue;
+ }
+
+ controllerEntry = DsmpFindControllerEntry(DsmContext,
+ PortObject,
+ controllerScsiAddress,
+ controllerSerialNumber,
+ strlen(controllerSerialNumber),
+ codeSet,
+ TRUE);
+
+ if (!controllerEntry) {
+
+ controllerEntry = DsmpBuildControllerEntry(DsmContext,
+ controllerDeviceObject,
+ PortObject,
+ controllerScsiAddress,
+ controllerSerialNumber,
+ codeSet,
+ TRUE);
+
+ if (!controllerEntry) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to build an entry for controller %p.\n",
+ TargetDevice,
+ controllerDeviceObject));
+
+ continue;
+ }
+
+ InsertHeadList(&dsmContext->ControllerList, &controllerEntry->ListEntry);
+ InterlockedIncrement((LONG volatile*)&dsmContext->NumberControllers);
+ }
+
+ controllerEntry->DeviceObject = controllerDeviceObject;
+
+ //
+ // Parse the DeviceIdList for all the 0x5 type identifiers
+ // and for each, compare the target port groups and target ports to match
+ // the device to its controller.
+ //
+ if (!match) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Failed to match devInfo %p with controller %p's Ids.\n",
+ TargetDevice,
+ deviceInfo,
+ controllerDeviceObject));
+
+ match = DsmpIsDeviceBelongsToController(DsmContext,
+ deviceInfo,
+ controllerEntry);
+ }
+
+ if (match && !doneUpdating) {
+
+ InterlockedIncrement((LONG volatile*)&(controllerEntry->RefCount));
+ deviceInfo->Controller = controllerEntry;
+ doneUpdating = TRUE;
+ }
+ }
+
+ //
+ // Free the last SCSI address allocated in the loop, if any.
+ //
+ if (controllerScsiAddress) {
+ DsmpFreePool(controllerScsiAddress);
+ controllerScsiAddress = NULL;
+ }
+ }
+
+ //
+ // If there was no controller to associate this device with, use a fake one.
+ // Note that we only really care about matching on the Port and Target
+ // portions of the SCSI address.
+ //
+ if (!deviceInfo->Controller) {
+
+ for (entry = dsmContext->ControllerList.Flink;
+ entry != &dsmContext->ControllerList;
+ entry = entry->Flink) {
+
+ controllerEntry = CONTAINING_RECORD(entry, DSM_CONTROLLER_LIST_ENTRY, ListEntry);
+
+ if ((controllerEntry->IsFakeController) &&
+ (controllerEntry->ScsiAddress->PortNumber == deviceInfo->ScsiAddress->PortNumber) &&
+ (controllerEntry->ScsiAddress->TargetId == deviceInfo->ScsiAddress->TargetId)) {
+
+ fakeControllerEntryExists = TRUE;
+ break;
+ }
+ }
+
+ //
+ // If no fake one exists as yet for this port FDO, create one now.
+ //
+ if (!fakeControllerEntryExists) {
+
+ CHAR fakeControllerSerialNumber[] = "FakeController";
+ SCSI_ADDRESS fakeControllerScsiAddress = {0};
+ fakeControllerScsiAddress.PortNumber = deviceInfo->ScsiAddress->PortNumber;
+ fakeControllerScsiAddress.TargetId = deviceInfo->ScsiAddress->TargetId;
+
+ controllerEntry = DsmpBuildControllerEntry(DsmContext,
+ NULL,
+ PortObject,
+ &fakeControllerScsiAddress,
+ fakeControllerSerialNumber,
+ StorageIdCodeSetBinary,
+ TRUE);
+
+ if (controllerEntry) {
+
+ InsertHeadList(&dsmContext->ControllerList, &controllerEntry->ListEntry);
+ InterlockedIncrement((LONG volatile*)&dsmContext->NumberControllers);
+ controllerEntry->IsFakeController = TRUE;
+ }
+ }
+
+ if (controllerEntry) {
+ InterlockedIncrement((LONG volatile*)&(controllerEntry->RefCount));
+ }
+
+ deviceInfo->Controller = controllerEntry;
+ }
+
+__Exit_DsmInquire:
+
+ if (spinlockHeld) {
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(*DsmIdentifier);
+
+ } else {
+
+ //
+ // If there was any sort of ERROR, the deviceInfo will NOT be put on
+ // MSDSM's internal list that is accessible to other threads. Thus,
+ // we are safe to free the memory below and we do not require any
+ // synchronization mechanism to do so.
+ //
+
+ //
+ // Check to see whether the serial number buffer was allocated, or just
+ // an offset into the Descriptor.
+ //
+ if (serialNumberAllocated) {
+
+ //
+ // Need to free this before returning.
+ //
+ DsmpFreePool(serialNumber);
+ }
+
+ if (deviceInfo) {
+
+ if (deviceInfo->ScsiAddress) {
+ DsmpFreePool(deviceInfo->ScsiAddress);
+ }
+
+ DsmpFreePool(deviceInfo);
+ }
+ }
+
+ //
+ // If deviceName is not NULL then it hasn't been assigned to any GROUP.
+ // Free the allocated memory.
+ //
+ if (deviceName) {
+ DsmpFreePool(deviceName);
+ }
+
+ //
+ // If hardwareId is not NULL then it hasn't been assigned to any GROUP.
+ // Free the allocated memory.
+ //
+ if (hardwareId) {
+ DsmpFreePool(hardwareId);
+ }
+
+ if (targetPortGroupsInfo) {
+ DsmpFreePool(targetPortGroupsInfo);
+ }
+
+ if (relativeTargetPortId) {
+ DsmpFreePool(relativeTargetPortId);
+ }
+
+ if (targetPortGroupId) {
+ DsmpFreePool(targetPortGroupId);
+ }
+
+ if (controllerObjects) {
+ DsmpFreePool(controllerObjects);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmInquire (DevObj %p): Exiting function with status %x.\n",
+ TargetDevice,
+ status));
+
+ return status;
+}
+
+
+BOOLEAN
+DsmCompareDevices(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId1,
+ _In_ IN PVOID DsmId2
+ )
+/*++
+
+Routine Description:
+
+ This routine is called to determine if the device ids represent
+ the same underlying physical device.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ DsmId1/2 - Identifers returned from DMS_INQUIRE_DRIVER.
+
+Return Value:
+
+ TRUE if DsmIds correspond to the same underlying device.
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo0 = DsmId1;
+ PDSM_DEVICE_INFO deviceInfo1 = DsmId2;
+ PSTR serialNumber0;
+ PSTR serialNumber1;
+ SIZE_T length;
+ BOOLEAN match = FALSE;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmCompareDevices (DevInfo %p): Entering function - comparing with %p.\n",
+ deviceInfo0,
+ deviceInfo1));
+
+ //
+ // Get the two serial numbers. They were either embedded in
+ // the STORAGE_DEVICE_DESCRIPTOR or built by directly issuing
+ // the VPD request.
+ //
+ serialNumber0 = deviceInfo0->SerialNumber;
+ serialNumber1 = deviceInfo1->SerialNumber;
+
+ if (serialNumber0 && serialNumber1) {
+
+ //
+ // Get the length of the base-device Serial Number.
+ //
+ length = strlen((const char*)serialNumber0);
+
+ //
+ // If the lengths match, compare the contents.
+ //
+ if (length == strlen((const char*)serialNumber1)) {
+
+ if (RtlEqualMemory(serialNumber0, serialNumber1, length)) {
+ match = TRUE;
+ }
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmCompareDevices (DevInfo %p): Serialnumber not assigned for %p and\\or %p.\n",
+ DsmId1,
+ deviceInfo0,
+ deviceInfo1));
+ }
+
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmCompareDevices (DevInfo %p): Exiting function with match = %!bool!.\n",
+ DsmId1,
+ match));
+
+ return match;
+}
+
+
+NTSTATUS
+DsmGetControllerInfo(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN ULONG Flags,
+ _Inout_ IN OUT PCONTROLLER_INFO *ControllerInfo
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to get information about the controller that
+ the device corresponding to DsmId in on. Currently this DSM controls
+ hardware that doesn't expose controllers directly. Therefore State
+ is always NO_CNTRL. This information is used mainly by whatever
+ WMI admin utilities want it.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+
+ DsmId - Value returned from DMSInquireDriver.
+
+ Flags - Bitfield of modifiers. If ALLOCATE is not set, ControllerInfo
+ will have a valid buffer for the DSM to operate on.
+
+ ControllerInfo - Pointer for the DSM to place the allocated controller
+ info pertaining to DsmId
+
+Return Value:
+
+ STATUS_INSUFFICIENT_RESOURCES if memory allocation fails.
+
+ STATUS_SUCCESS on success
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ PDSM_CONTROLLER_LIST_ENTRY controllerEntry = deviceInfo->Controller;
+ PCONTROLLER_INFO controllerInfo = NULL;
+ LARGE_INTEGER time;
+ ULONG controllerId = 0;
+ NTSTATUS status = STATUS_SUCCESS;
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmGetControllerInfo (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ //
+ // Check to see whether a controller id has already been made-up.
+ //
+ if (!controllerEntry) {
+
+ //
+ // Since this device is in an enclosure that doesn't have controllers,
+ // e.g. JBOD, make one up.
+ //
+ KeQuerySystemTime(&time);
+
+ //
+ // Use only the lower 32-bits.
+ //
+ controllerId = time.LowPart;
+ }
+
+ //
+ // Check the Flags
+ //
+ if (Flags & DSM_CNTRL_FLAGS_ALLOCATE) {
+
+ //
+ // This is the first call. Need to allocate the controller structure.
+ //
+ controllerInfo = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(CONTROLLER_INFO),
+ DSM_TAG_CTRL_INFO);
+ if (!controllerInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmGetControllerInfo (DevInfo %p): Failed to allocate memory for Controller Info\n",
+ DsmId));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmGetControllerInfo;
+ }
+
+ if (!controllerEntry) {
+
+ //
+ // Indicate that there are no specific controllers.
+ //
+ controllerInfo->State = DSM_CONTROLLER_NO_CNTRL;
+
+ //
+ // Set the identifier to the value generated earlier.
+ // Indicate that it's Binary, not ASCII.
+ //
+ controllerInfo->Identifier.Type = StorageIdCodeSetBinary;
+ controllerInfo->Identifier.Length = 8;
+
+ RtlCopyMemory(controllerInfo->Identifier.SerialNumber,
+ &controllerId,
+ sizeof(controllerId));
+
+ } else {
+
+ //
+ // If either implicit or explicit ALUA state transition is supported,
+ // every controller is active. Else, if the devInfo's is in Active
+ // state, the controller is obviously in the active state.
+ //
+ if ((deviceInfo->ALUASupport != DSM_DEVINFO_ALUA_NOT_SUPPORTED) ||
+ (DsmpIsDeviceStateActive(deviceInfo->State))) {
+
+ controllerInfo->State = DSM_CONTROLLER_ACTIVE;
+
+ } else {
+
+ controllerInfo->State = DSM_CONTROLLER_STANDBY;
+ }
+
+ controllerInfo->Identifier.Type = controllerEntry->IdCodeSet;
+ controllerInfo->Identifier.Length = controllerEntry->IdLength;
+
+ if (controllerInfo->Identifier.Length > 32) {
+
+ controllerInfo->Identifier.Length = 32;
+ }
+
+ RtlCopyMemory(controllerInfo->Identifier.SerialNumber,
+ controllerEntry->Identifier,
+ controllerInfo->Identifier.Length);
+
+ controllerInfo->DeviceObject = controllerEntry->DeviceObject;
+ }
+
+ *ControllerInfo = controllerInfo;
+
+ } else if (Flags & DSM_CNTRL_FLAGS_CHECK_STATE) {
+
+ //
+ // Get the passed in struct.
+ //
+ controllerInfo = *ControllerInfo;
+
+ //
+ // If the enclosures supported by this DSM actually had controllers,
+ // there would be a list of them and a search based on
+ // ControllerIdentifier would be made.
+ //
+ controllerEntry = deviceInfo->Controller;
+
+ if (!controllerEntry) {
+
+ controllerInfo->State = DSM_CONTROLLER_NO_CNTRL;
+
+ } else {
+
+ //
+ // If either implicit or explicit ALUA state transition is supported,
+ // every controller is active. Else, if the devInfo's is in Active
+ // state, the controller is obviously in the active state.
+ //
+ if ((deviceInfo->ALUASupport != DSM_DEVINFO_ALUA_NOT_SUPPORTED) ||
+ (DsmpIsDeviceStateActive(deviceInfo->State))) {
+
+ controllerInfo->State = DSM_CONTROLLER_ACTIVE;
+
+ } else {
+
+ controllerInfo->State = DSM_CONTROLLER_STANDBY;
+ }
+ }
+ }
+
+__Exit_DsmGetControllerInfo:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmGetControllerInfo (DevInfo %p): Exiting function with status %x.\n",
+ DsmId,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmSetDeviceInfo(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDEVICE_OBJECT TargetObject,
+ _In_ IN PVOID DsmId,
+ _Inout_ IN OUT PVOID *PathId
+ )
+/*++
+
+Routine Description:
+
+ This routine associates the DsmId to the controlling MPDisk PDO,
+ the targetObject for DSM-initiated requests, and to a Path
+ (given by PathId).
+ This routine will update the PathId in a way that better explains
+ the topology to MPIO.
+ Additionally, if we are in failover LB policy, failback if this
+ path is preferred path.
+ Also, if PR is being used, send registration down this path.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ TargetObject - The D.O. to which DSM-initiated requests should be sent.
+ DsmId - Value returned from DMSInquireDriver.
+ PathId - Id that represents the path. The value passed in may be used
+ as is, or the DSM optionally can update it if it requires
+ additional state info to be kept.
+
+Return Value:
+
+ INSUFFICENT_RESOURCES for no-mem conditions.
+ STATUS_SUCCESS
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ PDSM_GROUP_ENTRY group = deviceInfo->Group;
+ PDSM_FAILOVER_GROUP failGroup;
+ PDSM_CONTEXT dsmContext;
+ PSCSI_ADDRESS scsiAddress;
+ ULONG primaryPath = 0;
+ ULONG optimizedPath = 0;
+ ULONG pathWeight = 0;
+ ULONG pathId;
+ NTSTATUS status = STATUS_SUCCESS;
+ WCHAR registryKeyName[256] = {0};
+ BOOLEAN newFOGroup = FALSE;
+ BOOLEAN registryKeyExists = FALSE;
+ KIRQL irql;
+ PVOID tempPathId = *PathId;
+ DSM_LOAD_BALANCE_TYPE loadBalanceType;
+ ULONGLONG preferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+ UCHAR explicitlySet = FALSE;
+ BOOLEAN vidpidPolicySet = FALSE;
+ BOOLEAN overallPolicySet = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ //
+ // 1. Set default LB policy.
+ // 2. Query LB policy from registry and update if necessary.
+ // 3. Set default value for primaryPath and optimizedPath based on device's
+ // access state
+ // 4. Map deviceInfo to real LUN by saving off the target for I/O
+ // 5. Build pathId from SCSI address
+ // 6. Find FOG for device. If none found, build one.
+ // Add deviceInfo to FOG.
+ // 7. Query registry for pathWeight, primaryPath and optimizedPath
+ // Update deviceInfo with results of query.
+ // 8. Compare deviceInfo access state with persistent value (based on
+ // primaryPath and optimizedPath) and update its DesiredState.
+ //
+
+ if (!TargetObject) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): No target object.\n",
+ deviceInfo));
+
+ //
+ // This deviceInfo will have no path or targetObject associated with it.
+ // Mark it in a failed state so it won't be used to handle any requests.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_UNDETERMINED;
+
+ goto __Exit_DsmSetDeviceInfo;
+ }
+
+ //
+ // Default LB type is Round Robin.
+ //
+ loadBalanceType = DSM_LB_ROUND_ROBIN;
+
+ //
+ // Override the default with whatever is the overall policy that needs to be
+ // applied for all LUNs controlled by MSDSM.
+ //
+ // Override that policy if one has been set for this device's VID/PID.
+ //
+ // Override that policy with whatever has been explicitly set for this particular
+ // device.
+ //
+ // In order to perform the above, first query the policy for this particular device.
+ // If it has not been explicity set, use MSDSM's overall policy or VID/PID policy.
+ //
+ status = DsmpQueryDeviceLBPolicyFromRegistry(deviceInfo,
+ group->RegistryKeyName,
+ &loadBalanceType,
+ &preferredPath,
+ &explicitlySet);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): Failed to query LB policy from registry. Status %x.\n",
+ deviceInfo,
+ status));
+
+ NT_ASSERT(NT_SUCCESS(status));
+
+ //
+ // This deviceInfo will have no path or targetObject associated with it.
+ // Mark it in a failed state so it won't be used to handle any requests.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_UNDETERMINED;
+
+ goto __Exit_DsmSetDeviceInfo;
+ }
+
+ //
+ // If this device's policy was not explicitly set, check to see if a policy
+ // was set for this device's VID/PID and use that.
+ // If VID/PID policy is not set, query the overall default policy
+ // that needs to be applied to all devices controlled by this DSM.
+ // If this setting hasn't been set, we'll fall back to using the default that was
+ // determined based on the storage's ALUA capabilities.
+ //
+ if (!explicitlySet) {
+
+ status = DsmpQueryTargetLBPolicyFromRegistry(deviceInfo,
+ &loadBalanceType,
+ &preferredPath);
+
+ if (NT_SUCCESS(status)) {
+
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_VID_PID;
+ vidpidPolicySet = TRUE;
+
+ } else if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ //
+ // Since the policy hasn't been set for this VID/PID, check if
+ // overall MSDSM-wide policy has been set.
+ //
+ status = DsmpQueryDsmLBPolicyFromRegistry(&loadBalanceType,
+ &preferredPath);
+ if (NT_SUCCESS(status)) {
+
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_DSM_WIDE;
+ overallPolicySet = TRUE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): Failed to query Dsm overall LB policy from registry. Status %x.\n",
+ deviceInfo,
+ status));
+
+ NT_ASSERT(status == STATUS_OBJECT_NAME_NOT_FOUND);
+ status = STATUS_SUCCESS;
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): Failed to query VID/PID LB policy from registry. Status %x.\n",
+ deviceInfo,
+ status));
+
+ NT_ASSERT(status == STATUS_OBJECT_NAME_NOT_FOUND);
+ status = STATUS_SUCCESS;
+ }
+ } else {
+
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_LUN_EXPLICIT;
+ }
+
+ if (!explicitlySet && !vidpidPolicySet && !overallPolicySet) {
+
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_ALUA_CAPABILITY;
+
+ }
+
+ //
+ // If ALUA is enabled and the load balance policy is set to Round Robin,
+ // we need to set it to Round Robin with Subset instead.
+ //
+ if (!DsmpIsSymmetricAccess(deviceInfo) && loadBalanceType == DSM_LB_ROUND_ROBIN) {
+ loadBalanceType = DSM_LB_ROUND_ROBIN_WITH_SUBSET;
+ }
+
+ group->LoadBalanceType = loadBalanceType;
+ group->PreferredPath = preferredPath;
+ dsmContext = (PDSM_CONTEXT) DsmContext;
+
+ irql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+
+ //
+ // Save the registry key name under which Load balance policies
+ // are stored. This will be used to query the LB policy later.
+ //
+ if (group->RegistryKeyName) {
+
+ registryKeyExists = TRUE;
+
+ if (!NT_SUCCESS(RtlStringCchCopyNW(registryKeyName,
+ sizeof(registryKeyName) / sizeof(registryKeyName[0]),
+ group->RegistryKeyName,
+ ((sizeof(registryKeyName) / sizeof(registryKeyName[0])) - sizeof(WCHAR))))) {
+
+ registryKeyName[(sizeof(registryKeyName) / sizeof(registryKeyName[0])) - 1] = L'\0';
+ }
+ }
+
+ //
+ // TargetObject is the destination for any requests created by this driver.
+ // Save this for future reference.
+ //
+ deviceInfo->TargetObject = TargetObject;
+
+ //
+ // Set the PathId - All devices on the same PathId will
+ // failover together. Currently the pathId is constructed
+ // from Port Number, Bus Number, and Target Id of the device.
+ //
+ scsiAddress = deviceInfo->ScsiAddress;
+ NT_ASSERT(scsiAddress);
+
+ pathId = 0x77;
+ pathId <<= 8;
+ pathId |= scsiAddress->PortNumber;
+ pathId <<= 8;
+ pathId |= scsiAddress->PathId;
+ pathId <<= 8;
+ pathId |= scsiAddress->TargetId;
+
+ *PathId = ((PVOID)((ULONG_PTR)(pathId)));
+
+ //
+ // PathId indicates the path on which this device resides. Meaning
+ // that when a Fail-Over occurs all device's on the same path fail
+ // together. Search for a matching F.O. Group
+ //
+ failGroup = DsmpFindFOGroup(DsmContext, *PathId);
+
+ //
+ // If not found, create a new failover group
+ //
+ if (!failGroup) {
+
+ failGroup = DsmpBuildFOGroup(DsmContext, deviceInfo, PathId);
+
+ if (failGroup) {
+
+ newFOGroup = TRUE;
+ failGroup->MPIOPath = tempPathId;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): Failed to build FO Group.\n",
+ DsmId));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // If this path is in the midst of failover processing, mark it as "good"
+ // again.
+ //
+ failGroup->State = DSM_FG_NORMAL;
+
+ //
+ // add this deviceInfo to the f.o. group.
+ //
+ status = DsmpUpdateFOGroup(DsmContext, failGroup, deviceInfo);
+ NT_ASSERT(NT_SUCCESS(status));
+ }
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+
+ if (NT_SUCCESS(status)) {
+
+ if (registryKeyExists) {
+
+ NTSTATUS queryStatus = STATUS_INVALID_PARAMETER;
+ ULONGLONG pathId64;
+
+ //
+ // If the overall default policy or a target-level policy has been set and
+ // this device's policy has not been explicitly set, there's no use querying
+ // its individual path (desired) states.
+ //
+ if ((!overallPolicySet && !vidpidPolicySet) || (explicitlySet)) {
+
+ //
+ // Created a new failover group. Query the LB policy
+ // for this device from registry.
+ //
+ pathId64 = (ULONGLONG)((ULONG_PTR)*PathId);
+
+ queryStatus = DsmpQueryLBPolicyForDevice(registryKeyName,
+ pathId64,
+ loadBalanceType,
+ &primaryPath,
+ &optimizedPath,
+ &pathWeight);
+ }
+
+ irql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+
+ if (NT_SUCCESS(queryStatus)) {
+
+ deviceInfo->PathWeight = pathWeight;
+
+ //
+ // If device doesn't support ALUA, update the device state
+ // based on the primary path info in the registry.
+ //
+ if (DsmpIsSymmetricAccess(deviceInfo)) {
+
+ if (primaryPath) {
+
+ deviceInfo->DesiredState = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ } else {
+
+ deviceInfo->DesiredState = DSM_DEV_STANDBY;
+ }
+
+ } else {
+
+ DSM_DEVICE_STATE devState;
+
+ if (primaryPath) {
+
+ devState = optimizedPath ? DSM_DEV_ACTIVE_OPTIMIZED : DSM_DEV_ACTIVE_UNOPTIMIZED;
+
+ } else {
+
+ devState = optimizedPath ? DSM_DEV_STANDBY : DSM_DEV_UNAVAILABLE;
+ }
+
+ //
+ // For ALUA, desired state makes sense for FOO.
+ // For RRWS, we assume desired state was explicitly selected
+ // by Admin if the ALUA state is different from the path
+ // state. Only under such cases would the path state have
+ // been saved in registry.
+ // In all other policies, state must just match the TPG state.
+ //
+ if (group->LoadBalanceType == DSM_LB_FAILOVER ||
+ group->LoadBalanceType == DSM_LB_ROUND_ROBIN_WITH_SUBSET) {
+
+ deviceInfo->DesiredState = devState;
+
+ } else {
+
+ deviceInfo->DesiredState = DSM_DEV_UNDETERMINED;
+ }
+ }
+ } else if (queryStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ deviceInfo->PathWeight = pathWeight;
+ deviceInfo->DesiredState = DSM_DEV_UNDETERMINED;
+
+ } else {
+
+ deviceInfo->PathWeight = 0;
+ deviceInfo->DesiredState = DSM_DEV_UNDETERMINED;
+ }
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): PathWeight %x, DesiredState %x, State %x, PrevState %x.\n",
+ deviceInfo,
+ deviceInfo->PathWeight,
+ deviceInfo->DesiredState,
+ deviceInfo->State,
+ deviceInfo->PreviousState));
+
+ if (NT_SUCCESS(status)) {
+
+ deviceInfo->Initialized = TRUE;
+
+ } else if (!NT_SUCCESS(status) && newFOGroup) {
+
+ //
+ // This deviceInfo will have no path associated with it.
+ // Mark it in a failed state so it won't be used to handle any requests.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_UNDETERMINED;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): No path associated with instance. Changing state from %u to %u.\n",
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+
+ DsmpRemoveDeviceFailGroup(DsmContext, failGroup, deviceInfo, TRUE);
+
+ if (failGroup->Count == 0) {
+
+ //
+ // Yank it from the list.
+ //
+ RemoveEntryList(&failGroup->ListEntry);
+ InterlockedDecrement((LONG volatile*)&dsmContext->NumberFOGroups);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): Removing FOGroup %p with path %p. Count of FOGroups %d.\n",
+ DsmId,
+ failGroup,
+ failGroup->PathId,
+ dsmContext->NumberFOGroups));
+
+ //
+ // Free the zombie group list and then the failover group.
+ //
+ DsmpFreeZombieGroupList(failGroup);
+ DsmpFreePool(failGroup);
+ }
+ }
+
+__Exit_DsmSetDeviceInfo:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmSetDeviceInfo (DevInfo %p): Exiting function with status %x.\n",
+ DsmId,
+ status));
+
+ return status;
+}
+
+
+BOOLEAN
+DsmIsPathActive(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID PathId,
+ _In_ IN PVOID DsmId
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to determine whether the path to DsmId is usable
+ (ie. able to handle requests without a failover).
+
+ Also, after a failover, the path validity will be queried.
+ If the path error was transitory and the DSM feels that the path is good,
+ then this request will be re-issued to determine whether it is usable.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ PathId - Value set in SetPathId.
+ DsmId - DSM Id returned during DsmInquire.
+
+Return Value:
+
+ TRUE if the path is active. FALSE otherwise.
+--*/
+{
+ PDSM_FAILOVER_GROUP foGroup;
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ PDSM_GROUP_ENTRY group = deviceInfo->Group;
+ PDSM_CONTEXT dsmContext = (PDSM_CONTEXT) DsmContext;
+ KIRQL irql;
+ BOOLEAN retVal;
+ ULONG SpecialHandlingFlag = 0;
+
+ //
+ // 1. If PR and reserved by this node, register the PR keys.
+ // 2. Find the FOG for the passed in PathId
+ // 3. Depending on the LB policy, set the appropriate devInfo states
+ // If FailOver, and DesiredState is AO, change the active
+ // devInfos to non-active state and make this one AO.
+ // If ALUA supported, send down SetTPG to make this change,
+ // else directly make the change.
+ // If RR/LWP/LQD, make this DevInfo ActiveOptimized.
+ // If RRS, and DesiredState is AO, change the active devInfos to
+ // their desired states and then make this one AO.
+ // If DesiredState is not AO, find a devInfo in AO state. If
+ // one is found, make this devInfo's state its desired state,
+ // else if one isn't found, make this one AO.
+ // 3. If this is preferredPath, and LB policy is failover-only, change the
+ // access state of deviceInfo to AO.
+ // If there is another devInfo currently in AO, change its state too.
+ // If ALUA supported, send down SetTPG to make these changes.
+ // 4. Get the appropriate AO DeviceInfo and mark the group's PTBU to its
+ // pathId.
+ //
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ //
+ // Initialize this instance to be usable so that during the possible processing
+ // of PR register, this device can be a candidate for certain kind of requests.
+ //
+ deviceInfo->Usable = TRUE;
+
+ //
+ // New path arriving. If this Node owns the reservation register this path.
+ //
+ if (group->PRKeyValid) {
+
+ NTSTATUS prRegStatus;
+ ULONG i;
+ PDSM_DEVICE_INFO devInfo;
+ ULONG ordinal;
+
+ prRegStatus = DsmpRegisterPersistentReservationKeys(deviceInfo, TRUE);
+
+ deviceInfo->RegisterServiced = TRUE;
+
+ if (NT_SUCCESS(prRegStatus)) {
+
+ deviceInfo->PRKeyRegistered = TRUE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): Failed (status %x) to register PR key\n",
+ deviceInfo,
+ prRegStatus));
+ }
+
+ for (i = 0; i < group->NumberDevices; i++) {
+
+ devInfo = group->DeviceList[i];
+ if (devInfo && devInfo == deviceInfo) {
+
+ ordinal = (1 << i);
+ group->ReservationList |= ordinal;
+ break;
+ }
+ }
+ }
+
+
+ irql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+
+ //
+ // Get the F.O. Group information.
+ //
+ foGroup = DsmpFindFOGroup(DsmContext, PathId);
+
+ //
+ // If there are any devices on this path, and it's not in a failed state
+ // it's capable of handling requests. So it's active.
+ //
+ if ((foGroup) &&
+ (foGroup->Count) &&
+ (foGroup->State == DSM_FG_NORMAL)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): Path %p is usable.\n",
+ DsmId,
+ PathId));
+
+ retVal = TRUE;
+
+ //
+ // Update the next path to be used for the group if it not set already.
+ //
+ deviceInfo = (PDSM_DEVICE_INFO)DsmId;
+
+ group = deviceInfo->Group;
+ DSM_ASSERT(group != NULL);
+ DSM_ASSERT(group->GroupSig == DSM_GROUP_SIG);
+
+ //
+ // If an invalidated path came back online before PnP removes came in,
+ // then MPIO's path recovery thread would have sent down a PathVerify
+ // just moments before by which we changed the state of the FOG to
+ // normal. Now it is time to change the deviceInfo's state to a "good"
+ // state.
+ //
+ if (deviceInfo->State >= DSM_DEV_FAILED) {
+
+ DSM_ASSERT(deviceInfo->State == DSM_DEV_INVALIDATED);
+
+ if (DsmpIsSymmetricAccess(deviceInfo)) {
+
+ //
+ // Mark it as AO. The SetLBForPathArrival will update the state
+ // appropriately.
+ //
+ deviceInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ } else {
+
+ //
+ // Set it to the state that was reported during the last RTPG
+ // call that was made.
+ //
+ deviceInfo->State = deviceInfo->ALUAState;
+ }
+ }
+
+ if (DsmpIsSymmetricAccess(deviceInfo)) {
+
+ DsmpSetLBForPathArrival(DsmContext, deviceInfo, SpecialHandlingFlag);
+
+ } else {
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+ DsmpSetLBForPathArrivalALUA(DsmContext, deviceInfo, SpecialHandlingFlag);
+ irql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): State set to %d\n",
+ deviceInfo,
+ deviceInfo->State));
+
+ if (group->PathToBeUsed == NULL) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): Will set PathToBeUsed for %p\n",
+ deviceInfo,
+ group));
+
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess(deviceInfo),
+ SpecialHandlingFlag);
+ if (deviceInfo != NULL) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): FOG %p set for PathToBeUsed for %p\n",
+ deviceInfo,
+ deviceInfo->FailGroup,
+ group));
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), deviceInfo->FailGroup);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): No active/alternative path available for group %p\n",
+ DsmId,
+ group));
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), NULL);
+ }
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): Path %p is NOT usable.\n",
+ DsmId,
+ PathId));
+
+ retVal = FALSE;
+ }
+
+ ((PDSM_DEVICE_INFO)DsmId)->Usable = retVal;
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmIsPathActive (DevInfo %p): Exiting function with retVal = %!bool!.\n",
+ DsmId,
+ retVal));
+
+ return retVal;
+}
+
+
+NTSTATUS
+DsmPathVerify(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PVOID PathId
+ )
+/*++
+
+Routine Description:
+
+ This routine ensures that the path to the device indicated by DsmId
+ is healthy. It's called periodically by the bus driver, and also
+ after a fail-over condition has been dealt with to ensure that
+ the path is able to handle requests.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ DsmId - Value returned from DMSInquire.
+ PathId - Value set in SetPathId.
+
+Return Value:
+
+ NTSTATUS
+--*/
+
+{
+ PDSM_CONTEXT dsmCtxt = (PDSM_CONTEXT) DsmContext;
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ PDSM_FAILOVER_GROUP foGroup;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ BOOLEAN found = FALSE;
+ KIRQL irql;
+ PLIST_ENTRY entry;
+ PDSM_FOG_DEVICELIST_ENTRY fogDeviceListEntry = NULL;
+ PDSM_GROUP_ENTRY group = deviceInfo->Group;
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmPathVerify (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ if (DsmpIsDeviceInitialized(deviceInfo)) {
+
+ irql = ExAcquireSpinLockExclusive(&(dsmCtxt->DsmContextLock));
+
+ //
+ // Get the failover group
+ //
+ foGroup = DsmpFindFOGroup(DsmContext, PathId);
+
+ if (foGroup) {
+
+ //
+ // Find the device.
+ //
+ for (entry = foGroup->FOG_DeviceList.Flink;
+ entry != &foGroup->FOG_DeviceList;
+ entry = entry->Flink) {
+
+ fogDeviceListEntry = CONTAINING_RECORD(entry, DSM_FOG_DEVICELIST_ENTRY, ListEntry);
+
+ if (fogDeviceListEntry && fogDeviceListEntry->DeviceInfo == deviceInfo) {
+
+ status = STATUS_SUCCESS;
+ found = TRUE;
+
+ break;
+ }
+ }
+ } else {
+
+ //
+ // This is not a good thing. It indicates that either we
+ // returned a bogus path to the bus-driver on a fail-over,
+ // or that the path evaporated between polls and PnP hasn't
+ // torn stuff down.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmPathVerify (DevInfo %p): Failed to find failover group for path %p.\n",
+ DsmId,
+ PathId));
+
+ status = STATUS_DEVICE_NOT_CONNECTED;
+ }
+
+ ExReleaseSpinLockExclusive(&(dsmCtxt->DsmContextLock), irql);
+
+ if (NT_SUCCESS(status)) {
+
+ if (found) {
+
+ //
+ // Send down TUR if ALUA is not supported.
+ // Else, send down ReportTargetPortGroups (sending TUR down non-A/O path will
+ // always result in a check condition).
+ //
+ if (deviceInfo->ALUASupport == DSM_DEVINFO_ALUA_NOT_SUPPORTED) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmPathVerify (DevInfo %p): Sending TUR using %p to verify path %p.\n",
+ DsmId,
+ deviceInfo,
+ deviceInfo->FailGroup->PathId));
+
+ status = DsmSendTUR(deviceInfo->TargetObject);
+
+ } else {
+
+ //
+ // Check for whether we should ignore sending down an RTPG:
+ // Flag set indicates that this PathVerify() is happening in response to device
+ // arrival and can be skipped since Inquire() has just already sent down an RTPG.
+ // All that needs to be done is to clear the flag so that subsequent PathVerify()
+ // sent in response to InitiateFO will send RTPG as a ping.
+ // This is an optimization with the idea of helping speed up boot time, which is
+ // is adversely impacted, especially if there are many LUNs, each with many paths.
+ //
+ if (deviceInfo->IgnorePathVerify) {
+
+ deviceInfo->IgnorePathVerify = FALSE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmPathVerify (DevInfo %p): Returning success immediately since RTPG was already just sent.\n",
+ DsmId));
+
+ status = STATUS_SUCCESS;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmPathVerify (DevInfo %p): Sending RTPG using %p to verify path %p.\n",
+ DsmId,
+ deviceInfo,
+ deviceInfo->FailGroup->PathId));
+
+ status = DsmpGetDeviceALUAState(dsmCtxt, deviceInfo, NULL);
+
+ //
+ // Since this RTPG may have resulted in us losing a UA, adjust
+ // the states if needed.
+ //
+ if (NT_SUCCESS(status)) {
+
+ DsmpAdjustDeviceStatesALUA(group, NULL, SpecialHandlingFlag);
+ }
+ }
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ if (deviceInfo->State >= DSM_DEV_FAILED) {
+
+ foGroup->State = DSM_FG_NORMAL;
+ deviceInfo->State = deviceInfo->LastKnownGoodState;
+ }
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmPathVerify (DevInfo %p): Exiting function with status %x.\n",
+ DsmId,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmInvalidatePath(
+ _In_ IN PVOID DsmContext,
+ _In_ IN ULONG ErrorMask,
+ _In_ IN PVOID PathId,
+ _Inout_ IN OUT PVOID *NewPathId
+ )
+/*++
+
+Routine Description:
+
+ This routine will mark up devices as failed on PathId, and find
+ an appropriate path to return to MPIO.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ ErrorMask - Value returned from InterpretError.
+ PathId - The failing path.
+ NewPathId - Pointer to the new path.
+
+Return Value:
+
+ NTSTATUS of the operation.
+
+--*/
+{
+ PDSM_CONTEXT context = DsmContext;
+ PDSM_FAILOVER_GROUP failGroup;
+ PDSM_FAILOVER_GROUP newPath = NULL;
+ PDSM_FAILOVER_GROUP pathId;
+ PDSM_DEVICE_INFO deviceInfo;
+ LIST_ENTRY reservedDeviceList;
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL irql;
+ PLIST_ENTRY entry;
+ PDSM_FOG_DEVICELIST_ENTRY fogDeviceListEntry = NULL;
+ BOOLEAN lockHeld = FALSE;
+
+ UNREFERENCED_PARAMETER(ErrorMask);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmInvalidatePath (PathId %p): Entering function.\n",
+ PathId));
+
+ DSM_ASSERT(ErrorMask & DSM_FATAL_ERROR);
+
+ *NewPathId = NULL;
+
+ InitializeListHead(&reservedDeviceList);
+
+ irql = ExAcquireSpinLockExclusive(&(context->DsmContextLock));
+ lockHeld = TRUE;
+
+ //
+ // Get the fail-over group corresponding to the PathId.
+ //
+ failGroup = DsmpFindFOGroup(DsmContext, PathId);
+
+ if (!failGroup || failGroup->State == DSM_FG_FAILED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInvalidatePath (PathId %p): Failed to find FailOver group.\n",
+ PathId));
+
+ status = STATUS_NO_SUCH_DEVICE;
+ goto __Exit_DsmInvalidatePath;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmInvalidatePath (PathId %p): Context %p, FOG %p failing.\n",
+ PathId,
+ DsmContext,
+ failGroup));
+
+ //
+ // Mark the path as failed.
+ //
+ failGroup->State = DSM_FG_FAILED;
+
+ //
+ // Check to see whether the port driver and PnP removed the devices
+ // BEFORE the fail-over indication actually occurred. Work-around
+ // of several Fibre miniports.
+ //
+ if (failGroup->Count == 0) {
+
+ //
+ // There are no longer any devices in this fail-over group, which means
+ // in order to get a back-pointer to the groups using this fail-over
+ // group, we need to go through the "zombie" group list. This should
+ // allow us to find a new path ID to return.
+ //Then go through failGroup->ZombieGroupList to do failover for each group.
+ //
+ PDSM_ZOMBIEGROUP_ENTRY group;
+ PDSM_GROUP_ENTRY groupEntry;
+
+ //
+ // Initialize all the entries to indicate that they haven't been processed.
+ //
+ for (entry = failGroup->ZombieGroupList.Flink; entry != &(failGroup->ZombieGroupList); entry = entry->Flink) {
+
+ group = CONTAINING_RECORD(entry, DSM_ZOMBIEGROUP_ENTRY, ListEntry);
+ group->Processed = FALSE;
+ }
+
+ //
+ // Since we need to drop the spin lock while processing an entry, it is possible
+ // that a removal in parallel frees up this entry during that time, thus making it
+ // impossible for us to move to the next entry in the list.
+ // In order to safely access each of the entries, we mark an entry as being processed
+ // just before dropping the spinlock, and always start processing from the beginning
+ // of the list, skipping over the already processed ones.
+ //
+ entry = failGroup->ZombieGroupList.Flink;
+
+ while (entry != &(failGroup->ZombieGroupList)) {
+
+ group = CONTAINING_RECORD(entry, DSM_ZOMBIEGROUP_ENTRY, ListEntry);
+ entry = entry->Flink;
+
+ if (!group || !group->Group || group->Processed) {
+ continue;
+ }
+
+ group->Processed = TRUE;
+ groupEntry = group->Group;
+
+ ExReleaseSpinLockExclusive(&context->DsmContextLock, irql);
+ lockHeld = FALSE;
+
+ pathId = DsmpSetNewPathUsingGroup((PDSM_CONTEXT)DsmContext, groupEntry);
+
+ if (!newPath) {
+ newPath = pathId; // Save off first good alternative path that we find
+ }
+
+ if (!lockHeld) {
+ irql = ExAcquireSpinLockExclusive(&(context->DsmContextLock));
+ lockHeld = TRUE;
+ entry = failGroup->ZombieGroupList.Flink;
+ }
+ }
+
+ if (!newPath) {
+ //
+ // This indicates that all of the devices have already been removed.
+ // If there were reservations outstanding, the RemoveDevice code
+ // should have updated them.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInvalidatePath (PathId %p): Failed to find new path using zombie group list.\n",
+ PathId));
+ }
+
+ } else {
+
+
+ //
+ // Process each device in the fail-over group
+ //
+ for (entry = failGroup->FOG_DeviceList.Flink;
+ entry != &failGroup->FOG_DeviceList;
+ entry = entry->Flink) {
+
+ fogDeviceListEntry = CONTAINING_RECORD(entry, DSM_FOG_DEVICELIST_ENTRY, ListEntry);
+
+ if (!fogDeviceListEntry) {
+ continue;
+ }
+
+ //
+ // Get the deviceInfo.
+ //
+ deviceInfo = fogDeviceListEntry->DeviceInfo;
+
+ if (!(DsmpIsDeviceFailedState(deviceInfo->State))) {
+
+ deviceInfo->LastKnownGoodState = deviceInfo->State;
+ }
+
+ //
+ // Set the state of the Failing Device
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_INVALIDATED;
+
+ InterlockedIncrement(&deviceInfo->BlockRemove);
+
+ ExReleaseSpinLockExclusive(&(context->DsmContextLock), irql);
+ lockHeld = FALSE;
+
+ pathId = DsmpSetNewPath(DsmContext, deviceInfo);
+
+ if (!newPath) {
+ newPath = pathId; // Save off first good alternative path that we find
+ }
+
+ if (!lockHeld) {
+ irql = ExAcquireSpinLockExclusive(&(context->DsmContextLock));
+ lockHeld = TRUE;
+ }
+
+ InterlockedDecrement(&deviceInfo->BlockRemove);
+ }
+ }
+
+ if (!newPath) {
+
+ //
+ // This indicates that no acceptable paths
+ // were found. Return the error to mpctl.
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInvalidatePath (PathId %p): No valid path found.\n",
+ PathId));
+
+ status = STATUS_NO_SUCH_DEVICE;
+
+ } else {
+
+ //
+ // return the new path.
+ //
+ *NewPathId = newPath->PathId;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmInvalidatePath (PathId %p): Returning %p as newPath.\n",
+ PathId,
+ newPath->PathId));
+ }
+
+__Exit_DsmInvalidatePath:
+
+ if (lockHeld) {
+ ExReleaseSpinLockExclusive(&(context->DsmContextLock), irql);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmInvalidatePath (PathId %p): Exiting function with status %x.\n",
+ PathId,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmMoveDevice(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PVOID MPIOPath,
+ _In_ IN PVOID SuggestedPath,
+ _In_ IN ULONG Flags
+ )
+/*++
+
+Routine Description:
+
+ This routine is invoked in response to an administrative request.
+ The device that's associated with SuggestedPath will be made active, and the
+ current active device, moved to stand-by.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during registration.
+ DsmIds - The collection of DSM IDs that pertain to the MPDisk.
+ MPIOPath - The original path value passed to SetDeviceInfo.
+ SuggestedPath - The path which should become the active path.
+ Flags - Bitmask indicating the intent of the move.
+
+Return Value:
+
+ NTSTATUS - STATUS_SUCCESS, unless SuggestedPath is somehow invalid.
+ STATUS_INVALID_PARAMETER is ADMIN is set and the path is invalid.
+
+--*/
+{
+ PDSM_CONTEXT context = DsmContext;
+ PDSM_DEVICE_INFO deviceInfo;
+ PDSM_FAILOVER_GROUP failGroup;
+ ULONG i;
+ NTSTATUS status;
+ KIRQL irql;
+ BOOLEAN adminRequest = FALSE;
+ PDSM_GROUP_ENTRY group = NULL;
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmMoveDevice (DsmIds %p): Entering function - DsmContext %p MPIOPath (%p) SuggestedPath %p.\n",
+ DsmIds,
+ DsmContext,
+ MPIOPath,
+ SuggestedPath));
+
+ //
+ // Capture the value of the ADMIN flag bit.
+ // Currently, permanent assignment of the device to "preferred path" isn't supported.
+ // This driver doesn't care about the pending remove flag (currently).
+ //
+ adminRequest = (BOOLEAN)(Flags & DSM_MOVE_ADMIN_REQUEST);
+
+ irql = ExAcquireSpinLockExclusive(&(context->DsmContextLock));
+
+ group = ((PDSM_DEVICE_INFO)(DsmIds->IdList[0]))->Group;
+
+ //
+ // Find the first active device.
+ //
+ deviceInfo = DsmpGetActivePathToBeUsed(group,
+ DsmpIsSymmetricAccess((PDSM_DEVICE_INFO)DsmIds->IdList[0]),
+ SpecialHandlingFlag);
+
+ if (!deviceInfo) {
+
+ //
+ // Didn't find an active device. Should LOG.
+ // Use the first one to piggy-back the request.
+ //
+ deviceInfo = DsmIds->IdList[0];
+ }
+
+ //
+ // Get the fail-over group associated with the Path.
+ //
+ failGroup = DsmpFindFOGroup(DsmContext,
+ SuggestedPath);
+
+ if (!failGroup) {
+
+ //
+ // The caller has made a terrible mistake.
+ // If it's an ADMIN request, blow it off.
+ //
+ if (adminRequest) {
+ status = STATUS_INVALID_PARAMETER;
+ } else {
+
+ //
+ // Try to set another path.
+ //
+ // Note that failGroup will be NULL going into
+ // SetNewPath. This is OK.
+ //
+ status = STATUS_SUCCESS;
+ }
+ } else {
+ status = STATUS_SUCCESS;
+ }
+
+ if (status == STATUS_SUCCESS) {
+
+ //
+ // Set the new path, using SuggestedPath.
+ //
+ InterlockedIncrement(&deviceInfo->BlockRemove);
+ ExReleaseSpinLockExclusive(&context->DsmContextLock, irql);
+ failGroup = DsmpSetNewPath(context,
+ deviceInfo);
+ irql = ExAcquireSpinLockExclusive(&(context->DsmContextLock));
+ InterlockedDecrement(&deviceInfo->BlockRemove);
+
+ //
+ // If we were able to make the suggested path active, that should be used.
+ //
+ for (i = 0, status = STATUS_UNSUCCESSFUL; i < DsmIds->Count && !NT_SUCCESS(status); i++) {
+
+ deviceInfo = DsmIds->IdList[i];
+
+ if (deviceInfo->FailGroup == failGroup) {
+
+ if (deviceInfo->State == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ InterlockedExchangePointer(&(group->PathToBeUsed), (PVOID)failGroup);
+ status = STATUS_SUCCESS;
+ }
+ }
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(context->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmMoveDevice (DsmIds %p): Exiting function with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmRemovePending(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId
+ )
+/*++
+
+Routine Description:
+
+ This routine indicates that the device represented by DsmId will be
+ removed, so the deviceInfo is marked up to indicate the pending removal,
+ so that it won't be used.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver
+ during registration.
+ DsmId - Value referring to the failed device.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+
+{
+ PDSM_CONTEXT dsmContext = DsmContext;
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ KIRQL irql;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmRemovePending (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ //
+ // DsmpSetNewPath then finds the next available device. This is basically a
+ // fail-over for just this device.
+ //
+ InterlockedIncrement(&deviceInfo->BlockRemove);
+ DsmpSetNewPath(DsmContext, deviceInfo);
+ irql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+ InterlockedDecrement(&deviceInfo->BlockRemove);
+
+ if (!(DsmpIsDeviceFailedState(deviceInfo->State))) {
+
+ deviceInfo->LastKnownGoodState = deviceInfo->State;
+ }
+
+ //
+ // Mark the device as being unavailable since remove will be sent shortly.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_REMOVE_PENDING;
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmRemovePending (DevInfo %p): Exiting function.\n",
+ DsmId));
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+DsmRemoveDevice(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PVOID PathId
+ )
+/*++
+
+Routine Description:
+
+ The device is gone and the port pdo has been removed. This routine will
+ update the internal structures and free any allocations.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ DsmId - Value referring to the failed device.
+ PathId - The path on which the Device lives.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+
+{
+ PDSM_CONTEXT dsmContext = DsmContext;
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ KIRQL irql;
+ PDSM_FAILOVER_GROUP failGroup = deviceInfo->FailGroup;
+ PDSM_GROUP_ENTRY group = deviceInfo->Group;
+ LONG block;
+
+ UNREFERENCED_PARAMETER(PathId);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmRemoveDevice (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ do {
+
+ irql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+ block = deviceInfo->BlockRemove;
+ NT_ASSERT(block >= 0);
+
+ if (block) {
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+ KeStallExecutionProcessor(10000);
+ }
+
+ } while (block);
+
+ if (!(DsmpIsDeviceFailedState(deviceInfo->State))) {
+
+ deviceInfo->LastKnownGoodState = deviceInfo->State;
+ }
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = DSM_DEV_REMOVED;
+
+ //
+ // Decrement the reference count for this device's controller entry and
+ // delete the entry if its reference count is now zero.
+ //
+ if (deviceInfo->Controller) {
+
+ if (InterlockedDecrement((LONG volatile*)&(deviceInfo->Controller->RefCount)) == 0) {
+
+ RemoveEntryList(&(deviceInfo->Controller->ListEntry));
+ DsmpFreeControllerEntry(dsmContext, deviceInfo->Controller);
+ deviceInfo->Controller = NULL;
+ InterlockedDecrement((LONG volatile*)&(dsmContext->NumberControllers));
+ }
+ }
+
+ //
+ // Ensure that the device has been fully initialized before trying to
+ // remove it from the FOG. If SetDeviceInfo has yet to be invoked, there
+ // will yet to be an association set.
+ //
+ if (failGroup) {
+
+ //
+ // Remove its entry from the Fail-Over Group.
+ //
+ DsmpRemoveDeviceFailGroup(DsmContext, failGroup, deviceInfo, FALSE);
+ }
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+
+ //
+ // Remove it from it's multi-path group. This has the side-effect
+ // of cleaning up the Group if the number of devices goes to zero.
+ //
+ DsmpRemoveDeviceEntry(DsmContext, group, deviceInfo);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmRemoveDevice (DevInfo %p): Exiting function.\n",
+ DsmId));
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+DsmRemovePath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PVOID PathId
+ )
+/*++
+
+Routine Description:
+
+ This routine indicates that the path is no longer valid, and that it should
+ be removed. Internal counts will be updated and any allocations associated
+ with this path freed.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during registration.
+ PathId - The path to remove.
+
+Return Value:
+
+ NTSTATUS of the operation.
+
+--*/
+
+{
+ PDSM_FAILOVER_GROUP failGroup;
+ KIRQL irql;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmRemovePath (PathId %p): Entering function.\n",
+ PathId));
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ failGroup = DsmpFindFOGroup(DsmContext, PathId);
+
+ if (failGroup) {
+
+ //
+ // The claim is that a path won't be removed, until all
+ // the devices on it are.
+ //
+ if (failGroup->Count == 0) {
+
+ //
+ // Yank it from the list.
+ //
+ RemoveEntryList(&failGroup->ListEntry);
+ InterlockedDecrement((LONG volatile*)&DsmContext->NumberFOGroups);
+
+ //
+ // Move this over to the stale FOG list if there are inflight requests.
+ // Otherwise free the allocation.
+ //
+ if (InterlockedCompareExchange(&failGroup->NumberOfRequestsInFlight, 0, 0) > 0) {
+
+ failGroup->State = DSM_FG_PENDING_REMOVE;
+ InsertTailList(&DsmContext->StaleFailGroupList, &failGroup->ListEntry);
+ InterlockedIncrement((LONG volatile*)&DsmContext->NumberStaleFOGroups);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmRemovePath (PathId %p): Outstanding requests %d. Moving FOGroup %p with path %p to stale path list.\n",
+ PathId,
+ failGroup->NumberOfRequestsInFlight,
+ failGroup,
+ failGroup->PathId));
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmRemovePath (PathId %p): Removing FOGroup %p with path %p. Count of FOGroups %d.\n",
+ PathId,
+ failGroup,
+ failGroup->PathId,
+ DsmContext->NumberFOGroups));
+
+ //
+ // Free the zombie group list and then the failover group.
+ //
+ DsmpFreeZombieGroupList(failGroup);
+ DsmpFreePool(failGroup);
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmRemovePath (PathId %p): Count %d. Not removing FOGroup %p.\n",
+ PathId,
+ failGroup->Count,
+ failGroup));
+
+ //
+ // Should never be here.
+ //
+ NT_ASSERT(failGroup->Count == 0);
+ }
+ } else {
+
+ //
+ // It's already been removed.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmRemovePath (PathId %p): Did not find the FO group.\n",
+ PathId));
+
+ NT_ASSERT(failGroup);
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmRemovePath (PathId %p): Exiting function.\n",
+ PathId));
+
+ return STATUS_SUCCESS;
+}
+
+
+PVOID
+DsmLBGetPath(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PDSM_IDS DsmList,
+ _In_ IN PVOID CurrentPath,
+ _Out_ OUT NTSTATUS *Status
+ )
+/*++
+
+Routine Description:
+
+ This routine is used by mpio to handle load-balancing.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ Srb - The current read/write Srb.
+ DsmList - List of our DSM IDs.
+ CurrentPath - The last path that was returned for this multi-path group.
+ Status - Storage to place NTSTATUS of the call.
+
+Return Value:
+
+ The path ID to which the request should be sent.
+
+--*/
+
+{
+ PDSM_CONTEXT dsmContext = DsmContext;
+ PDSM_DEVICE_INFO deviceInfo;
+ PDSM_GROUP_ENTRY group;
+ PDSM_FAILOVER_GROUP failGroup = NULL;
+ PVOID newPath = NULL;
+ PDSM_FAILOVER_GROUP oldFailGroup = NULL;
+ PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY failPathDevInfoEntry = NULL;
+ PCDB cdb = NULL;
+ UCHAR opCode = 0xFF;
+ BOOLEAN lockInExclusiveMode = FALSE;
+ ULONG SpecialHandlingFlag = 0;
+
+
+ if (Srb) {
+ cdb = SrbGetCdb(Srb);
+ if (cdb) {
+ opCode = cdb->AsByte[0];
+
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmLBGetPath (DsmIds %p): Entering function.\n",
+ DsmList));
+
+ //
+ // Up-front checking to minimally validate the list of
+ // DsmId's being passed in.
+ //
+ NT_ASSERT(DsmList->Count && DsmList->IdList[0]);
+ if (!(DsmList->Count && DsmList->IdList[0])) {
+
+ *Status = STATUS_NO_SUCH_DEVICE;
+ goto __Exit_DsmLBGetPath;
+ }
+
+ deviceInfo = DsmList->IdList[0];
+ group = deviceInfo->Group;
+
+
+ failGroup = DsmpGetPath(dsmContext, DsmList, Srb, SpecialHandlingFlag);
+
+ //
+ // If there wasn't a single active/optimized path found, check to see if
+ // there is an STPG in progress that may be making a path A/O.
+ //
+ if (!failGroup) {
+
+ //
+ // Take the last path used.
+ //
+ oldFailGroup = DsmpFindFOGroup(dsmContext, CurrentPath);
+
+ //
+ // Find the devInfo corresponding to this path.
+ //
+ deviceInfo = DsmpFindDevInfoFromGroupAndFOGroup(dsmContext,
+ group,
+ oldFailGroup);
+
+ if (deviceInfo) {
+
+ //
+ // Check if there is an alternate devInfo to be used temporarily
+ // for this deviceInfo
+ //
+ failPathDevInfoEntry = DsmpFindFailPathDevInfoEntry(dsmContext,
+ group,
+ deviceInfo);
+
+ if (failPathDevInfoEntry) {
+
+ //
+ // Use the alternate devInfo for now temporarily while the STPG
+ // that was previously sent (asynchronously) works on making the
+ // appropriate path active/optimized.
+ //
+ failGroup = (failPathDevInfoEntry->TempDeviceInfo)->FailGroup;
+ }
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmLBGetPath (DsmIds %p): Couldn't find FOG but FO in progress, so returning devInfo %p (FOG %p path %p).\n",
+ DsmList,
+ deviceInfo,
+ deviceInfo->FailGroup,
+ deviceInfo->FailGroup->PathId));
+ } else {
+
+ //
+ // Check if there is an RTPG in progress, if yes, return some path
+ // for the IO to be sent down.
+ //
+ if (InterlockedCompareExchange((LONG volatile*)&group->InFlightRTPG, 0, 0)) {
+
+ BOOLEAN sendTPG = FALSE;
+ deviceInfo = DsmpFindStandbyPathToActivateALUA(group, &sendTPG, SpecialHandlingFlag);
+
+ if (deviceInfo) {
+
+ failGroup = deviceInfo->FailGroup;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmLBGetPath (DsmIds %p): Couldn't find FOG but RTPG inflight, so returning devInfo %p (FOG %p path %p).\n",
+ DsmList,
+ deviceInfo,
+ deviceInfo->FailGroup,
+ deviceInfo->FailGroup->PathId));
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmLBGetPath (DsmIds %p): Couldn't find FOG but RTPG inflight, even then couldn't find alternative devInfo.\n",
+ DsmList));
+ }
+ }
+ }
+ }
+
+ if (failGroup) {
+
+ newPath = failGroup->PathId;
+ *Status = STATUS_SUCCESS;
+
+ //
+ // If this is a retried request, our SetCompletion would have been bypassed,
+ // and our completion routine won't yet get called, so update the old and
+ // the new paths' stats.
+ //
+ if (Srb && DsmIsReadWrite(opCode)) {
+
+ PDSM_FAILOVER_GROUP oldPath;
+ PIRP irp = (PIRP)SrbGetOriginalRequest(Srb);
+ PIO_STACK_LOCATION irpStack;
+
+ //
+ // This indicates that the request is being retried. So we need to:
+ // 1. Update old path's and new path's request count
+ // 2. If the old path was supposed to be removed, check if there are
+ // no more requests are outstanding, and if yes, remove the path
+ //
+
+ irpStack = IoGetCurrentIrpStackLocation(irp);
+ oldPath = irpStack->Parameters.Others.Argument3;
+
+ if (oldPath) {
+
+ NT_ASSERT(oldPath->FailOverSig == DSM_FOG_SIG);
+
+ if (DsmpDecrementCounters(oldPath, Srb)) {
+
+ //
+ // If there are no requests on a path that is supposed to be removed,
+ // remove it now.
+ //
+ if (oldPath->State == DSM_FG_PENDING_REMOVE) {
+ KIRQL irql;
+
+ NT_ASSERT(oldPath->Count == 0);
+
+ //
+ // We need to acquire the DsmContextLock in Exclusive mode since
+ // we are removing a path from the Failover Group list.
+ //
+ irql = ExAcquireSpinLockExclusive(&(dsmContext->DsmContextLock));
+ lockInExclusiveMode = TRUE;
+
+ RemoveEntryList(&oldPath->ListEntry);
+ InterlockedDecrement((LONG volatile*)&dsmContext->NumberStaleFOGroups);
+
+ ExReleaseSpinLockExclusive(&(dsmContext->DsmContextLock), irql);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmLBGetPath (DsmIds %p): Removing FOGroup %p with path %p.\n",
+ DsmList,
+ oldPath,
+ oldPath->PathId));
+
+ DsmpFreePool(oldPath);
+ }
+ }
+
+ irpStack->Parameters.Others.Argument3 = failGroup;
+
+ DsmpIncrementCounters(failGroup, Srb);
+ }
+ }
+
+ } else {
+
+ *Status = STATUS_NO_SUCH_DEVICE;
+
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmLBGetPath (DsmIds %p): Failed to get FO group in LBGetPath.\n",
+ DsmList));
+
+
+ }
+
+__Exit_DsmLBGetPath:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmLBGetPath (DsmIds %p): Exiting function returning path %p for request %p.\n",
+ DsmList,
+ newPath,
+ Srb));
+
+ return newPath;
+}
+
+_Success_(return == DSM_PATH_SET)
+ULONG
+DsmCategorizeRequest(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PVOID CurrentPath,
+ _Outptr_result_maybenull_ OUT PVOID *PathId,
+ _Out_ OUT NTSTATUS *Status
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when a request is received other than a read/write.
+ It will determine the best path to which the request is to be sent.
+
+ In order to support clusters, reserve and release need to be handled
+ via SrbControl.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during
+ registration.
+ DsmIds - List of our DSM IDs.
+ Irp - The Irp containing Srb.
+ Srb - The current non-read/write Srb.
+ CurrentPath - The last path that was returned for this multi-path group.
+ PathId - Placeholder for the PathID
+ Status - Storage to place NTSTATUS of the call.
+
+Return Value:
+
+ DSM_PATH_SET - Indicates PathID is valid.
+ DSM_ERROR - Couldn't get a path.
+
+--*/
+{
+ ULONG dsmStatus;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmCategorizeRequest (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // Determine whether this is a special-case request.
+ //
+ if (DsmpReservationCommand(Irp, Srb)) {
+
+ dsmStatus = DSM_WILL_HANDLE;
+ goto __Exit_DsmCategorizeRequest;
+ }
+
+
+ //
+ // If this is a mpio pass through or a mpio pass through direct request,
+ // pick the path that corresponds to the pathId specified.
+ //
+ if (DsmpMpioPassThroughPathCommand(Irp)) {
+
+ *PathId = DsmpGetPathIdFromPassThroughPath(DsmContext,
+ DsmIds,
+ Irp,
+ &status);
+ } else {
+
+ //
+ // For requests other than reservation-handling and pass through, punt
+ // it back to the bus-driver. Need to get a path for the request first,
+ // so call the Load-Balance function.
+ //
+ *PathId = DsmLBGetPath(DsmContext,
+ Srb,
+ DsmIds,
+ CurrentPath,
+ &status);
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ if (!*PathId) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmCategorizeRequest (DsmIds %p): DSM_PATH_SET didn't return a path.\n",
+ DsmIds));
+ }
+
+ //
+ // Indicate that the path is updated, and mpctl should handle the request.
+ //
+ dsmStatus = DSM_PATH_SET;
+
+ } else {
+
+ //
+ // Indicate the error back to mpctl.
+ //
+ dsmStatus = DSM_ERROR;
+
+ //
+ // Mark-up the Srb to show that a failure has occurred.
+ // This value is really only for this DSM to know what to do
+ // in the InterpretError routine - Fatal Error.
+ // It could be something more meaningful.
+ //
+ if (Srb) {
+ Srb->SrbStatus = SRB_STATUS_NO_DEVICE;
+ }
+
+ *PathId = NULL;
+ }
+
+ //
+ // Pass back status info to mpctl.
+ //
+ *Status = status;
+
+__Exit_DsmCategorizeRequest:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmCategorizeRequest (DsmIds %p): Exiting function with categorization %x.\n",
+ DsmIds,
+ dsmStatus));
+
+ return dsmStatus;
+}
+
+
+NTSTATUS
+DsmBroadcastRequest(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when the DSM has indicated that Srb should be
+ sent to the device down all paths. The DSM will update IoStatus
+ information and status, but not complete the request.
+
+ Currently MSDSM doesn't have a need for this.
+
+Arguments:
+
+ DsmIds - The collection of DSM IDs that pertain to the MPDisk.
+ Irp - Irp containing SRB.
+ Srb - Scsi request block
+ Event - DSM sets this once all sub-requests have completed and
+ the original request's IoStatus has been setup.
+
+Return Value:
+
+ NTSTATUS of the operation.
+
+--*/
+{
+ NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+ UNREFERENCED_PARAMETER(Srb);
+ UNREFERENCED_PARAMETER(Irp);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmBroadcastRequest (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // Currently nothing is handled via Broadcast. Just set the event to
+ // free up the request handling in the bus-driver.
+ //
+ NT_ASSERT(NT_SUCCESS(status));
+ KeSetEvent(Event, 0, FALSE);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmBroadcastReqeust (DsmIds %p): Exiting function with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmSrbDeviceControl(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when the DSM has indicated that it wants to handle
+ it internally (via returning DSM_WILL_HANDLE in CategorizeRequest).
+
+ It should set IoStatus (Status and Information) and the Event, but not
+ complete the request.
+
+Arguments:
+
+ DsmContext - The DSM's context
+ DsmIds - The collection of DSM IDs that pertain to the MPDISK.
+ Irp - Irp containing SRB.
+ Srb - Scsi request block
+ Event - Event to be set when the DSM is finished if DsmHandled is TRUE
+
+Return Value:
+
+ NTSTATUS of the request.
+
+--*/
+{
+ PDSM_CONTEXT dsmContext = DsmContext;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ NTSTATUS status;
+ UCHAR opCode = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmSrbDeviceControl (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ if (!DsmIds || !DsmIds->Count) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "DsmSrbDeviceControl (DsmIds %p): No DsmIds passed in.\n",
+ DsmIds));
+
+ status = STATUS_NO_SUCH_DEVICE;
+ goto __Exit_DsmSrbDeviceControl;
+ }
+
+ if (irpStack->MajorFunction == IRP_MJ_SCSI) {
+
+ //
+ // Determine the operation.
+ //
+ PCDB cdb = SrbGetCdb(Srb);
+ if (cdb) {
+ opCode = cdb->AsByte[0];
+ }
+
+ if (opCode == SCSIOP_PERSISTENT_RESERVE_OUT) {
+
+ status = DsmpPersistentReserveOut(dsmContext,
+ DsmIds,
+ Irp,
+ Srb,
+ Event);
+
+ } else if (opCode == SCSIOP_PERSISTENT_RESERVE_IN) {
+
+ status = DsmpPersistentReserveIn(dsmContext,
+ DsmIds,
+ Irp,
+ Srb,
+ Event);
+
+ } else {
+
+ //
+ // Should never be here.
+ //
+ DSM_ASSERT(FALSE);
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+ } else {
+ //
+ // Should never be here.
+ //
+ DSM_ASSERT(irpStack->MajorFunction == IRP_MJ_SCSI);
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+__Exit_DsmSrbDeviceControl:
+ if (status != STATUS_PENDING) {
+
+ //
+ // Set-up the Irp status for mpio's completion of the request.
+ // If it was IRP_MJ_SCSI, one of the helper routines set Srb->SrbStatus
+ // already.
+ //
+ if ((irpStack->MajorFunction == IRP_MJ_SCSI) &&
+ (Srb != NULL) &&
+ (Srb->SrbStatus == SRB_STATUS_PENDING)) {
+
+ Srb->SrbStatus = SRB_STATUS_ERROR;
+ }
+
+ Irp->IoStatus.Status = status;
+
+ //
+ // Set the event to free up the request handling in the bus-driver.
+ //
+ KeSetEvent(Event, 0, FALSE);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmSrbDeviceControl (DsmIds %p): Exiting function with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+VOID
+DsmSetCompletion(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _Inout_ IN OUT PDSM_COMPLETION_INFO DsmCompletion
+ )
+/*++
+
+Routine Description:
+
+ This routine is called before the actual submission of a request,
+ but after the categorisation of the I/O. This will be called only
+ for those requests not handled by the DSM directly:
+ Read/Write
+ Other requests not handled by SrbControl or Broadcast
+
+Arguments:
+
+ DsmContext - The DSM's context.
+ DsmId - Identifer that was indicated when the request was
+ categorized (or be LBGetPath)
+ Irp - Irp containing Srb.
+ Srb - The request
+ DsmCompletion - Completion info structure to be filled out by DSM.
+
+Return Value:
+
+ None
+
+--*/
+{
+ PDSM_CONTEXT dsmContext = DsmContext;
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PDSM_FAILOVER_GROUP failGroup = deviceInfo->FailGroup;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmSetCompletion (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ //
+ // Save off the path that was selected to service this request in Argument3.
+ //
+ irpStack->Parameters.Others.Argument3 = failGroup;
+
+ DsmpIncrementCounters(failGroup, Srb);
+
+ if (!dsmContext->DisableStatsGathering) {
+
+ //
+ // Indicate one more request on this device down this path.
+ //
+ InterlockedIncrement(&deviceInfo->NumberOfRequestsInProgress);
+ }
+
+ //
+ // Update the passed-in struct with our routine and context values.
+ //
+ DsmCompletion->DsmCompletionRoutine = DsmpRequestComplete;
+ DsmCompletion->DsmContext = DsmContext;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmSetCompletion (DevInfo %p): Exiting function.\n",
+ DsmId));
+
+ return;
+}
+
+
+ULONG
+DsmInterpretError(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _Inout_ IN OUT NTSTATUS *Status,
+ _Out_ OUT PBOOLEAN Retry,
+ _Out_ OUT PLONG RetryInterval,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ This routine is invoked by MPIO if Status is other than SUCCESS.
+ A few NTSTATUS and SRB_STATUS values indicate a fatal error.
+ Also checked are unit attentions, for which a retry is requested.
+
+Arguments:
+
+ DsmContext - The DSM's context.
+ DsmId - Identifers returned from DMS_INQUIRE_DRIVER.
+ Srb - The Srb with an error.
+ Status - NTSTATUS of the operation. Can be updated.
+ Retry - Allows the DSM to indicate whether to retry the IO.
+ RetryInterval - Lets DSM specify (in seconds) when this specific I/O
+ should be retried. Use MAXLONG to use the default
+ retry interval. Use zero to retry immediately.
+
+Return Value:
+
+ DSM_FATAL_ERROR indicates a fatal error.
+
+--*/
+{
+ //
+ // The requests that will be encountered can be divided into four categories:
+ // 1. The request that has failed.
+ // 2. Subsequent requests that were sent down the failing path that will
+ // complete with failure.
+ // 3. Requests that were already submitted to LBGetPath() just before InterpretError()
+ // was called for the failed request (but have yet to have the LB policy
+ // algo run).
+ // 4. Requests that come into the Dispatch() routine after the failed request
+ // has been processed by InterpretError().
+ //
+ // For the failed request:
+ // =======================
+ // 1. Find a standby path to make active/optimized.
+ // 2. Send STPG asynchronously as a scsi pass through via IRP_MJ_SCSI (this
+ // way it can be sent at DISPATCH_IRQL) after setting a completion routine.
+ // 3. Save the devInfo corresponding to the standby path for the failing devInfo.
+ // 4. Return FATAL to MPIO so that new IO are queued.
+ // 5. In the completion routine, update the new states for the devInfos. Then
+ // clear the saved (previously) standby devInfo for the failing devInfo.
+ //
+ // For the subsequent request that will fail (since it was sent on the failing path):
+ // ==================================================================================
+ // 1. If a standby devInfo has been saved off, it indicates that an STPG was
+ // already sent, so no need to send another one.
+ // 2. Return FATAL to MPIO so that this request gets queued.
+ //
+ // For the requests that were already submitted to LBGetPath() during this time:
+ // =============================================================================
+ // 1. If there is no active path, check if a standby devInfo has been saved
+ // away. If it has, return this path. Such requests will fail with check
+ // condition saying path used is in standby.
+ // 2. In InterpretError() retry (since the error indicates that request
+ // completed before STPG completed) without decrementing the remaining
+ // retries count.
+ //
+ // For new requests that come into Dispatch() after above processing:
+ // ==================================================================
+ // We don't need to worry about such requests, since MPIO will queue them
+ // automatically.
+ //
+
+ PDSM_DEVICE_INFO deviceInfo = DsmId;
+ ULONG errorMask = 0;
+ PVOID senseData = SrbGetSenseInfoBuffer(Srb);
+ UCHAR senseDataLength = SrbGetSenseInfoBufferLength(Srb);
+ BOOLEAN failover = FALSE;
+ BOOLEAN retry = FALSE;
+ BOOLEAN handled = FALSE;
+ BOOLEAN sendTPG = FALSE;
+ BOOLEAN tpgException = FALSE;
+ BOOLEAN devInfoException = FALSE;
+ PCDB cdb = SrbGetCdb(Srb);
+ UCHAR opCode = 0;
+ UCHAR scsiStatus = SrbGetScsiStatus(Srb);
+ BOOLEAN validSense = FALSE;
+ UCHAR senseKey = 0;
+ UCHAR addSenseCode = 0;
+ UCHAR addSenseCodeQualifier = 0;
+
+ if (cdb) {
+ opCode = cdb->AsByte[0];
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Entering function.\n",
+ DsmId));
+
+ *RetryInterval = MAXLONG;
+
+ if ((scsiStatus == SCSISTAT_RESERVATION_CONFLICT) ||
+ (*Status == STATUS_DEVICE_BUSY)) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Srb %p. Either busy or res. conflict (%x %x).\n",
+ DsmId,
+ Srb,
+ scsiStatus,
+ *Status));
+ }
+
+ //
+ // Go ahead and get the sense data if it's valid.
+ //
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) {
+
+ NT_ASSERT(senseData != NULL);
+
+ validSense = ScsiGetSenseKeyAndCodes(senseData,
+ senseDataLength,
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &addSenseCode,
+ &addSenseCodeQualifier);
+ }
+
+ //
+ // Sense data relating to logical block provisioning should be failed
+ // immediately back to the class layer for handling.
+ //
+ if (validSense) {
+ if (senseKey == SCSI_SENSE_NOT_READY &&
+ addSenseCode == SCSI_ADSENSE_LUN_NOT_READY &&
+ addSenseCodeQualifier == SCSI_SENSEQ_SPACE_ALLOC_IN_PROGRESS) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Temporary resource exhaustion. Fail Srb %p.\n",
+ DsmId,
+ Srb));
+
+ handled = TRUE;
+
+ } else if (senseKey == SCSI_SENSE_DATA_PROTECT &&
+ addSenseCode == SCSI_ADSENSE_WRITE_PROTECT &&
+ addSenseCodeQualifier == SCSI_SENSEQ_SPACE_ALLOC_FAILED_WRITE_PROTECT) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Permanent resource exhaustion. Fail Srb %p.\n",
+ DsmId,
+ Srb));
+
+ handled = TRUE;
+
+ } else if (senseKey == SCSI_SENSE_UNIT_ATTENTION &&
+ addSenseCode == SCSI_ADSENSE_LB_PROVISIONING &&
+ addSenseCodeQualifier == SCSI_SENSEQ_SOFT_THRESHOLD_REACHED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Soft threshold reached. Fail Srb %p.\n",
+ DsmId,
+ Srb));
+
+ handled = TRUE;
+
+ } else if (senseKey == SCSI_SENSE_UNIT_ATTENTION &&
+ addSenseCode == SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED &&
+ addSenseCodeQualifier == SCSI_SENSEQ_INQUIRY_DATA_CHANGED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Inquiry data changed. Fail Srb %p.\n",
+ DsmId,
+ Srb));
+
+ handled = TRUE;
+ } else if (senseKey == SCSI_SENSE_UNIT_ATTENTION &&
+ addSenseCode == SCSI_ADSENSE_PARAMETERS_CHANGED &&
+ addSenseCodeQualifier == SCSI_SENSEQ_CAPACITY_DATA_CHANGED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Capacity data changed. Fail Srb %p.\n",
+ DsmId,
+ Srb));
+
+ handled = TRUE;
+ }
+ }
+
+ if (handled) {
+ return errorMask;
+ }
+
+ //
+ // Check the NT Status first.
+ // Several are clearly failover conditions.
+ //
+ switch (*Status) {
+ case STATUS_DEVICE_NOT_CONNECTED:
+ case STATUS_DEVICE_DOES_NOT_EXIST:
+ case STATUS_NO_SUCH_DEVICE:
+ case STATUS_DELETE_PENDING: {
+
+ //
+ // The port pdo has either been removed or is
+ // very broken. A fail-over is necessary.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Will initiate fail over. Status %x. Opcode %x.\n",
+ DsmId,
+ *Status,
+ opCode));
+
+ handled = TRUE;
+ failover = TRUE;
+ break;
+ }
+
+ case STATUS_IO_DEVICE_ERROR: {
+
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) {
+
+ if (validSense) {
+
+ //
+ // See if it's a unit attention.
+ //
+ if (senseKey == SCSI_SENSE_UNIT_ATTENTION) {
+
+ switch (addSenseCode) {
+
+ case SCSI_ADSENSE_PARAMETERS_CHANGED: {
+
+ switch (addSenseCodeQualifier) {
+
+ case SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_CHANGED:
+ case SPC3_SCSI_SENSEQ_IMPLICIT_ASYMMETRIC_ACCESS_STATE_TRANSITION_FAILED: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): TPG states have changed. Requesting retry on Srb %p. Will send asyn RTPG.\n",
+ DsmId,
+ Srb));
+
+ //
+ // Retry but after sending RTPG, which will update the path states.
+ //
+ sendTPG = TRUE;
+ retry = TRUE;
+ handled = TRUE;
+ errorMask = DSM_RETRY_DONT_DECREMENT;
+
+ if (addSenseCodeQualifier == SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_CHANGED) {
+
+ //
+ // Worth retrying on the same path.
+ //
+ devInfoException = TRUE;
+ NT_ASSERT(!tpgException);
+ }
+
+ break;
+ }
+
+
+ case SPC3_SCSI_SENSEQ_RESERVATIONS_RELEASED: {
+
+ //
+ // This request needs to be immediately retried down the same path.
+ //
+ retry = TRUE;
+ *RetryInterval = 0;
+ handled = TRUE;
+ InterlockedExchangePointer(&(deviceInfo->Group->PathToBeUsed), deviceInfo->FailGroup);
+ break;
+ }
+
+ case SPC3_SCSI_SENSEQ_MODE_PARAMETERS_CHANGED:
+ case SPC3_SCSI_SENSEQ_RESERVATIONS_PREEMPTED:
+ case SPC3_SCSI_SENSEQ_REGISTRATIONS_PREEMPTED:
+ case SPC3_SCSI_SENSEQ_CAPACITY_DATA_HAS_CHANGED: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Failing request. STATUS_IO_DEVICE_ERROR (params changed). SrbStatus (%x) Scsi (%x) AddQual (%u).\n",
+ DsmId,
+ Srb->SrbStatus,
+ scsiStatus,
+ addSenseCodeQualifier));
+
+ //
+ // Just fail these back.
+ //
+ handled = TRUE;
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): UNIT_ATTENTION for params changed. ASCQ %x. Asking for retry on Srb %p.\n",
+ DsmId,
+ addSenseCodeQualifier,
+ Srb));
+
+ //
+ // Indicate that a retry is necessary.
+ //
+ retry = TRUE;
+ handled = TRUE;
+
+ break;
+ }
+ }
+
+ break;
+ }
+
+
+ case SPC3_SCSI_ADSENSE_COMMANDS_CLEARED_BY_ANOTHER_INITIATOR: {
+
+ if (addSenseCodeQualifier == 0x00) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): UNIT_ATTENTION (commands cleared by another initiator). Fail back to upper level. Srb %p.\n",
+ DsmId,
+ Srb));
+
+ //
+ // Commands cleared by another Initiator
+ //
+ handled = TRUE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): UNIT_ATTENTION (commands cleared by another initiator). ASCQ %x. Asking for retry on Srb %p.\n",
+ DsmId,
+ addSenseCodeQualifier,
+ Srb));
+
+ //
+ // Indicate that a retry is necessary.
+ //
+ retry = TRUE;
+ handled = TRUE;
+ }
+
+
+ break;
+ }
+
+ case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: {
+
+ if (addSenseCodeQualifier == SCSI_SENSEQ_VOLUME_SET_MODIFIED ||
+ addSenseCodeQualifier == SCSI_SENSEQ_REPORTED_LUNS_DATA_CHANGED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): VolumeSet/LunsData changed. Fail Srb %p.\n",
+ DsmId,
+ Srb));
+
+ //
+ // Fail back to upper layers.
+ //
+ handled = TRUE;
+
+ break;
+
+ } else {
+
+ //
+ // Fall through to default case (ie. retry the request)
+ //
+ }
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): UNIT_ATTENTION. ASC %x, ASCQ %x. Asking for retry on Srb %p.\n",
+ DsmId,
+ addSenseCode,
+ addSenseCodeQualifier,
+ Srb));
+
+ //
+ // Indicate that a retry is necessary.
+ //
+ retry = TRUE;
+ handled = TRUE;
+
+ break;
+ }
+ }
+ } else if (senseKey == SCSI_SENSE_NOT_READY) {
+
+ if (addSenseCode == SCSI_ADSENSE_LUN_NOT_READY) {
+
+ if (scsiStatus == SCSISTAT_CHECK_CONDITION) {
+
+ switch (addSenseCodeQualifier) {
+
+ //
+ // See if failure is due to device's current TPG state.
+ //
+ // If the failure is PORT_IN_STANDBY_STATE, we leave DSM_RETRY_DONT_DECREMENT unset if no active path exists,
+ // because otherwise MPIO will not be able to find a better path, and it will get into an infinite loop
+ // of trying and failing the command on a Standby path. See WCxeTfs:89150
+ //
+ case SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_TRANSITION:
+ case SPC3_SCSI_SENSEQ_TARGET_PORT_IN_UNAVAILABLE_STATE:
+
+ errorMask = DSM_RETRY_DONT_DECREMENT;
+
+ case SPC3_SCSI_SENSEQ_TARGET_PORT_IN_STANDBY_STATE:
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): TPG-transition/TPG-SB/TPG-UA. ASCQ %x. Will send down async RTPG. Asking for retry on Srb %p.\n",
+ DsmId,
+ addSenseCodeQualifier,
+ Srb));
+
+ //
+ // Indicate that a retry is necessary but without decrementing the remaining
+ // retries count. However, we may need to send down an STPG/RTPG also.
+ // And we must set PTBU to a path that is in a different TPG.
+ //
+ sendTPG = TRUE;
+ tpgException = TRUE;
+ NT_ASSERT(!devInfoException);
+ retry = TRUE;
+ handled = TRUE;
+
+ if ((addSenseCodeQualifier == SPC3_SCSI_SENSEQ_TARGET_PORT_IN_STANDBY_STATE) &&
+ DsmIsReadWrite(opCode)) {
+
+ PDSM_CONTEXT context = (PDSM_CONTEXT) deviceInfo->DsmContext;
+ KIRQL oldIrql = ExAcquireSpinLockExclusive(&(context->DsmContextLock));
+ BOOLEAN activePathExists = ( NULL != DsmpGetAnyActivePath(deviceInfo->Group, FALSE, NULL, 0) );
+ ExReleaseSpinLockExclusive(&(context->DsmContextLock), oldIrql);
+
+ if (activePathExists) {
+ errorMask = DSM_RETRY_DONT_DECREMENT;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Not decrementing error counter, as an active path exists in group %p and opcode %x is r/w\n",
+ DsmId,
+ deviceInfo->Group,
+ opCode));
+ }
+ }
+
+ break;
+
+ case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED:
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Manual intervention required. Asking for retry on Srb %p.\n",
+ DsmId,
+ Srb));
+
+ //
+ // This may be caused by NDU of controller firmware. It does not
+ // necessarily indicate that the device won't be ready via other path(s).
+ // Worth retrying instead of immediately failing back.
+ //
+ retry = TRUE;
+ handled = TRUE;
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ } else if (Srb->SrbStatus == SRB_STATUS_BUS_RESET) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): BUS_RESET. Failing back Srb %p.\n",
+ DsmId,
+ Srb));
+
+ //
+ // Upper layers will retry in this case. If we retry here it will
+ // have a multiplicative effect which may result in a very long
+ // IO completion time if the device persistently times out.
+ //
+ retry = FALSE;
+ handled = TRUE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Failing request. STATUS_IO_DEVICE_ERROR. SrbStatus (%x) ScsiStatus (%x).\n",
+ DsmId,
+ Srb->SrbStatus,
+ scsiStatus));
+ }
+
+ break;
+ }
+
+ case STATUS_BUFFER_OVERFLOW: {
+
+ if (DsmIsReadWrite(opCode)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): BUFFER_OVERFLOW: Retry.\n",
+ DsmId));
+
+ //
+ // Retry these, as this condition might indicate a torn write.
+ //
+ retry = TRUE;
+ handled = TRUE;
+ }
+
+ break;
+ }
+
+ case STATUS_DEVICE_BUSY: {
+
+ //
+ // See if it's a check condition for TPG states in transition.
+ //
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID &&
+ scsiStatus == SCSISTAT_CHECK_CONDITION) {
+
+ if (validSense) {
+
+ if (senseKey == SCSI_SENSE_NOT_READY &&
+ addSenseCode == SCSI_ADSENSE_LUN_NOT_READY &&
+ addSenseCodeQualifier == SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_TRANSITION) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): TPG transition. Will send down async RTPG. Asking for retry on Srb %p.\n",
+ DsmId,
+ Srb));
+
+ //
+ // Indicate that a retry is necessary but without decrementing the remaining
+ // retries count. However, we may need to send down an STPG/RTPG also.
+ // And we must set PTBU to a path that is in a different TPG.
+ //
+ sendTPG = TRUE;
+ tpgException = TRUE;
+ NT_ASSERT(!devInfoException);
+ retry = TRUE;
+ handled = TRUE;
+ errorMask = DSM_RETRY_DONT_DECREMENT;
+ }
+
+ }
+ }
+
+ break;
+ }
+
+ case STATUS_DEVICE_NOT_READY: {
+
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID &&
+ scsiStatus == SCSISTAT_CHECK_CONDITION) {
+
+ if (validSense) {
+ if (senseKey == SCSI_SENSE_NOT_READY &&
+ addSenseCode == SCSI_ADSENSE_LUN_NOT_READY) {
+
+ switch (addSenseCodeQualifier) {
+
+ case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Manual intervention required. Asking for retry on Srb %p.\n",
+ DsmId,
+ Srb));
+
+ //
+ // This may be caused by NDU of controller firmware. It does not
+ // necessarily indicate that the device won't be ready via other path(s).
+ // Worth retrying instead of immediately failing back.
+ //
+ retry = TRUE;
+ handled = TRUE;
+
+ break;
+ }
+
+ case SCSI_SENSEQ_SPACE_ALLOC_IN_PROGRESS: {
+ //
+ // This indicates a logical block provisioning temporary resource exhaustion
+ // condition and therefore we must allow the class layer to handle it.
+ //
+ retry = FALSE;
+ handled = TRUE;
+
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Unhandled AddQual %x.\n",
+ DsmId,
+ addSenseCodeQualifier));
+
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Unhandled status code %x.\n",
+ DsmId,
+ *Status));
+
+ break;
+ }
+ }
+
+ if (!handled) {
+
+ //
+ // The NTSTATUS didn't indicate a fail-over condition, but
+ // check various srb status for failover-class error.
+ //
+ switch (Srb->SrbStatus) {
+ case SRB_STATUS_SELECTION_TIMEOUT:
+ case SRB_STATUS_INVALID_LUN:
+ case SRB_STATUS_INVALID_TARGET_ID:
+ case SRB_STATUS_NO_DEVICE:
+ case SRB_STATUS_NO_HBA:
+ case SRB_STATUS_INVALID_PATH_ID: {
+
+ //
+ // All of these are fatal.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): SrbStatus 0x%x. Will initiate fail over.\n",
+ DsmId,
+ Srb->SrbStatus));
+
+ failover = TRUE;
+ break;
+ }
+
+
+ default: {
+
+ if ((scsiStatus == SCSISTAT_CHECK_CONDITION) &&
+ (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID)) {
+
+ if (validSense) {
+
+ switch (senseKey) {
+
+ case SCSI_SENSE_NO_SENSE: {
+
+ if (addSenseCode == SCSI_ADSENSE_NO_SENSE &&
+ addSenseCodeQualifier == SCSI_SENSEQ_CAUSE_NOT_REPORTABLE) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): CheckCondition with no sense info. Will initiate fail over.\n",
+ DsmId));
+
+ //
+ // This could be a transient error generated
+ // in response to potentially a hardware fault.
+ // Worth trying another path.
+ //
+ failover = TRUE;
+ handled = TRUE;
+ }
+
+ break;
+ }
+
+ case SCSI_SENSE_ILLEGAL_REQUEST: {
+
+ if (addSenseCode == SCSI_ADSENSE_INVALID_LUN) {
+
+ if (addSenseCodeQualifier == 0x00) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Invalid LUN. Will initiate fail over.\n",
+ DsmId));
+
+ //
+ // LUN may still exist on other path(s).
+ // Worth a failover.
+ //
+ failover = TRUE;
+ handled = TRUE;
+ }
+ }
+
+ break;
+ }
+
+ case SCSI_SENSE_HARDWARE_ERROR: {
+
+ if (addSenseCode == SPC3_SCSI_ADSENSE_LOGICAL_UNIT_COMMAND_FAILED) {
+
+ if (addSenseCodeQualifier == SPC3_SCSI_SENSEQ_SET_TARGET_PORT_GROUPS_FAILED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): STPG failed. Will initiate fail over.\n",
+ DsmId));
+
+ //
+ // If an STPG failed, treat as FATAL and get another
+ // path set to A/O via another STPG.
+ //
+ failover = TRUE;
+ handled = TRUE;
+ }
+ } else if ((addSenseCode == SCSI_ADSENSE_LOGICAL_UNIT_ERROR && addSenseCodeQualifier == SCSI_SENSEQ_TIMEOUT_ON_LOGICAL_UNIT) ||
+ (addSenseCode == SCSI_ADSENSE_DATA_TRANSFER_ERROR && addSenseCodeQualifier == SCSI_SENSEQ_INITIATOR_RESPONSE_TIMEOUT)) {
+
+ //
+ // Could potentially indicate a dropped FC packet. Retry (along another
+ // path, based on the LB policy).
+ //
+ retry = TRUE;
+ handled = TRUE;
+ }
+
+ break;
+ }
+
+ default: {
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (!handled) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Unhandled SRB Status 0x%x. Sense data %x|%x|%x.\n",
+ DsmId,
+ Srb->SrbStatus,
+ validSense ? senseKey : 0xFF,
+ validSense ? addSenseCode : 0xFF,
+ validSense ? addSenseCodeQualifier : 0xFF));
+ }
+
+ break;
+ }
+ }
+ }
+
+ if (failover) {
+ ULONG SpecialHandlingFlag = 0;
+
+ //
+ // If ALUA is supported, then it is possible that we may need to send
+ // down an STPG so build an IRP and fill in the SRB for STPG and send it down.
+ //
+ if (!DsmpIsSymmetricAccess(deviceInfo)) {
+
+ DsmpSetLBForPathFailingALUA(DsmContext, deviceInfo, TRUE, SpecialHandlingFlag);
+
+ } else {
+
+ //
+ // If device doesn't support ALUA, we just need to update
+ // states without sending down any commands (STPG)
+ //
+ DsmpSetLBForPathFailing(DsmContext, deviceInfo, TRUE, SpecialHandlingFlag);
+ }
+
+ errorMask = DSM_FATAL_ERROR;
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmInterpretError(DevInfo %p): Device changed to state %d\n",
+ deviceInfo,
+ deviceInfo->State));
+
+#if DBG
+ {
+ ULONG inx;
+ PDSM_GROUP_ENTRY group = deviceInfo->Group;
+ PDSM_DEVICE_INFO tempDevInfo;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Device %p in group %p being marked as failed. NTStatus 0x%x.\n",
+ DsmId,
+ deviceInfo,
+ group,
+ *Status));
+
+ for (inx = 0; inx < group->NumberDevices; inx++) {
+
+ tempDevInfo = group->DeviceList[inx];
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Device %p at %d. State %d.\n",
+ DsmId,
+ tempDevInfo,
+ inx,
+ tempDevInfo->State));
+ }
+ }
+#endif // DBG
+ }
+
+ if (retry) {
+
+ if (sendTPG) {
+
+ //
+ // If ALUA is supported, send down STPG/RTPG as appropriate.
+ //
+ if (!DsmpIsSymmetricAccess(deviceInfo)) {
+
+ DsmpSetPathForIoRetryALUA(DsmContext, deviceInfo, tpgException, devInfoException);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_RW,
+ "DsmInterpretError(DevInfo %p): SRB request %p will be retried. PTBU set to %p.\n",
+ deviceInfo,
+ Srb,
+ deviceInfo->Group->PathToBeUsed));
+ }
+ }
+ }
+
+
+ *Retry = retry;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmInterpretError (DevInfo %p): Exiting function returning errorMask %x.\n",
+ DsmId,
+ errorMask));
+
+ return errorMask;
+}
+
+BOOLEAN
+DsmIsAddressTypeSupported(
+ _In_ IN PVOID DsmContext,
+ _In_ IN ULONG AddressType
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when MPIO wants to know if the DSM supports a
+ particular storage address type.
+
+ This routine must be provided for DSMs of DsmType6 or higher.
+
+Arguments:
+
+ DsmContext - Context value passed to DsmInitialize()
+ AddressType - The storage address type being queried.
+
+Return Value:
+
+ TRUE - If the DSM supports the given storage address type.
+ FALSE - If the DSM does not support the given storage address type.
+
+--*/
+{
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ if (AddressType == STORAGE_ADDRESS_TYPE_BTL8)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+NTSTATUS
+DsmDeviceNotUsed(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId
+ )
+/*++
+
+Routine Description:
+
+ This routine indicates that the device represented by DsmId will not be
+ initialized completely by MPIO.
+ The DSM_ID list passed to other functions will no longer contain DsmId,
+ so internal structures should be updated accordingly.
+
+ This routine must be provided for DSMs of DsmType6 or higher.
+
+Arguments:
+
+ DsmContext - Context value given to the multipath driver during registration.
+ DsmId - Value referring to the uninitialized device.
+
+Return Value:
+
+ NTSTATUS of the operation.
+
+--*/
+{
+ PDSM_DEVICE_INFO deviceInfo = (PDSM_DEVICE_INFO)DsmId;
+
+ DSM_ASSERT(deviceInfo->Group != NULL);
+ DSM_ASSERT(deviceInfo->Group->GroupSig == DSM_GROUP_SIG);
+
+ //
+ // Undo anything we did to build up the device in DsmInquire().
+ //
+ DsmRemoveDevice((PDSM_CONTEXT)DsmContext, DsmId, deviceInfo->FailGroup);
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+DsmUnload(
+ _In_ IN PVOID DsmContext
+ )
+/*++
+
+Routine Description:
+
+ This routine is called when the main module requires the DSM to be unloaded
+ (ie. prior to the main module unload).
+
+Arguments:
+
+ DsmContext - Context value passed to DsmInitialize()
+
+Return Value:
+
+ STATUS_SUCCESS;
+
+--*/
+
+{
+ PVOID tempAddress = DsmContext;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmUnload (DsmCtxt %p): Entering function.\n",
+ DsmContext));
+
+ DsmpFreeDSMResources((PDSM_CONTEXT) DsmContext);
+
+ if (gMPIOControlObjectRefd) {
+
+ ObDereferenceObject(gMPIOControlObject);
+ gMPIOControlObjectRefd = FALSE;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmUnload (DsmCtxt %p): Exiting function.\n",
+ tempAddress));
+
+ //
+ // Stop the tracing subsystem.
+ //
+ WPP_CLEANUP(gDsmDriverObject);
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/tests/projects/wdk/kmdf/msdsm/msdsm.h b/tests/projects/wdk/kmdf/msdsm/msdsm.h
new file mode 100644
index 000000000..e1ddeb52d
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/msdsm.h
@@ -0,0 +1,1403 @@
+/*++
+
+Copyright (C) 2004-2010 Microsoft Corporation
+
+Module Name:
+
+ msdsm.h
+
+Abstract:
+
+ Header for the Microsoft Device Specific Module (DSM).
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+--*/
+
+#ifndef _MSDSM_H_
+#define _MSDSM_H_
+
+//
+// Maximum number of paths per device supported by the DSM.
+// This is a limit currently set by MPIO itself and needs to be updated if MPIO
+// supports more paths-per-device in the future.
+//
+#define DSM_MAX_PATHS 32
+
+//
+// MPIO control object's well known symbolic name
+//
+#define DSM_MPIO_CONTROL_OBJECT_SYMLINK L"\\DosDevices\\MPIOControl"
+
+//
+// Location of System class node in the registry
+//
+#define DSM_SYSTEM_CLASS_GUID_KEY L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Class\\{4D36E97D-E325-11CE-BFC1-08002BE10318}"
+
+//
+// Values used for matching and figuring out the DriverVersion
+//
+#define DSM_INF_PATH L"InfPath"
+#define DSM_MSDSM_INF_PATH L"msdsm.inf"
+#define DSM_DRIVER_VERSION L"DriverVersion"
+#define DSM_DRIVER_VERSION_FIELD_DELIMITER L'.'
+#define DSM_BUFFER_MAXCOUNT 64
+
+//
+// MSDSM's display name.
+//
+#define DSM_FRIENDLY_NAME L"Microsoft DSM"
+
+//
+// Name of the value for the supported devices in the registry, found in the
+// DSM's Services' Parameters key
+//
+#define DSM_SUPPORTED_DEVICELIST_VALUE_NAME L"DsmSupportedDeviceList"
+
+//
+// Value used to determine if per-IO statistics gathering needs to be turned OFF
+//
+#define DSM_DISABLE_STATISTICS L"DsmDisableStatistics"
+
+//
+// Names of the values in the registry for whether to use the same path for
+// sequential IOs when employing Least Blocks load balance policy, as well
+// as its size.
+//
+#define DSM_USE_CACHE_FOR_LEAST_BLOCKS L"DsmUseCacheForLeastBlocks"
+#define DSM_CACHE_SIZE_FOR_LEAST_BLOCKS L"DsmCacheSizeForLeastBlocks"
+
+//
+// Name of the value in the registry for the maximum request retry time during ALUA
+// state transitions. This value is found in the DSM's Services' Parameters key, and
+// applies only to Persistent Reservation commands.
+//
+#define DSM_MAX_STATE_TRANSITION_TIME_VALUE_NAME L"DsmMaximumStateTransitionTime"
+
+
+//
+// Default max amount of time (in seconds) that a PR failing with retry-able UA will be retried
+//
+#define DSM_MAX_PR_UNIT_ATTENTION_RETRY_TIME 3
+
+//
+// Macro to translate seconds to ticks. Each system tick is 10^(-7) seconds.
+//
+#define DSM_SECONDS_TO_TICKS(_Seconds) ((_Seconds) * 10000000)
+
+//
+// Size of the buffer allocated to retrieve device serial number.
+// This is as defined by SPC-3 spec. The identifier with the biggest size is
+// SCSI name type (0x8).
+//
+#define DSM_SERIAL_NUMBER_BUFFER_SIZE 255
+
+//
+// Number of LB Policies that are supported by this driver.
+//
+#define DSM_NUMBER_OF_LB_POLICIES 6
+
+//
+// Size of the buffer passed to read in Persistent Reserve keys.
+//
+#define DSM_READ_PERSISTENT_KEYS_BUFFER_SIZE 4096
+
+//
+// The default threshold for sequential IO for the Least Blocks load balance
+// policy is 1MB.
+//
+#define DSM_LEAST_BLOCKS_DEFAULT_THRESHOLD 0x00100000
+
+//
+// Initialization data structure that needs to be filled in for MPIO
+//
+DSM_INIT_DATA gDsmInitData;
+
+//
+// Macro used to round of a number to the nearest 8 byte aligned one.
+//
+#ifdef AlignOn8Bytes
+#undef AlignOn8Bytes
+#endif
+#define AlignOn8Bytes(x) (((x) + 7) & ~7)
+
+//
+// Macro for determining minimum of two numbers
+//
+#ifdef MIN
+#undef MIN
+#endif
+#define MIN(a, b) ((ULONGLONG)(a) < (ULONGLONG)(b) ? (a) : (b))
+
+//
+// Macro used to convert a 4 byte array to a ULONG (where byte 0 MSB, byte 3 LSB)
+//
+#define GetUlongFrom4ByteArray(UCharArray, ULongValue) \
+ ((UNALIGNED UCHAR *)&(ULongValue))[3] = ((UNALIGNED UCHAR *)(UCharArray))[0]; \
+ ((UNALIGNED UCHAR *)&(ULongValue))[2] = ((UNALIGNED UCHAR *)(UCharArray))[1]; \
+ ((UNALIGNED UCHAR *)&(ULongValue))[1] = ((UNALIGNED UCHAR *)(UCharArray))[2]; \
+ ((UNALIGNED UCHAR *)&(ULongValue))[0] = ((UNALIGNED UCHAR *)(UCharArray))[3];
+
+//
+// Macro used to convert a ULONG into a 4 byte array (as big-endian)
+//
+#define Get4ByteArrayFromUlong(ULongValue, UCharArray) \
+ ((UNALIGNED UCHAR *)(UCharArray))[3] = ((UNALIGNED UCHAR *)&(ULongValue))[0]; \
+ ((UNALIGNED UCHAR *)(UCharArray))[2] = ((UNALIGNED UCHAR *)&(ULongValue))[1]; \
+ ((UNALIGNED UCHAR *)(UCharArray))[1] = ((UNALIGNED UCHAR *)&(ULongValue))[2]; \
+ ((UNALIGNED UCHAR *)(UCharArray))[0] = ((UNALIGNED UCHAR *)&(ULongValue))[3];
+
+//
+// Macro to check if passed in opcode is a read, write
+//
+#define DsmIsReadRequest(_Opcode) (_Opcode == SCSIOP_READ || _Opcode == SCSIOP_READ16)
+#define DsmIsWriteRequest(_Opcode) (_Opcode == SCSIOP_WRITE || _Opcode == SCSIOP_WRITE16)
+#define DsmIsReadWrite(_Opcode) (_Opcode == SCSIOP_READ || _Opcode == SCSIOP_READ16 || \
+ _Opcode == SCSIOP_WRITE || _Opcode == SCSIOP_WRITE16)
+
+#define DsmIsReadCapacity( _Opcode ) (_Opcode == SCSIOP_READ_CAPACITY || _Opcode == SCSIOP_READ_CAPACITY16)
+
+
+//
+// Macro to find the number of bytes consumed by the array
+//
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+
+//
+// Signature used to identify various structures.
+// Used solely for debugging purposes.
+//
+#define DSM_DEVICE_SIG 0xAAAAAAAA
+#define DSM_GROUP_SIG 0x55555555
+#define DSM_FOG_SIG 0x88888888
+#define DSM_TARGET_PORT_GROUP_SIG 0x33333333
+#define DSM_TARGET_PORT_SIG 0xCCCCCCCC
+#define DSM_CONTROLLER_SIG 0xEEEEEEEE
+
+#define WNULL (L'\0')
+#define WNULL_SIZE (sizeof(WNULL))
+
+#if DBG
+
+//
+// NT_ASSERT wrapper.
+//
+#define DSM_ASSERT(exp) if (DoAssert) { \
+ NT_ASSERT(exp); \
+ }
+
+#else // DBG
+
+#define DSM_ASSERT(exp)
+
+#endif // DBG
+
+#define DSM_PARAMETER_PATH_W L"MSDSM\\Parameters"
+
+//
+// Pool Tags used in memory allocation
+//
+#define DSM_TAG_GENERIC '00ZZ'
+#define DSM_TAG_PASS_THRU '10ZZ'
+#define DSM_TAG_GROUP_ENTRY '20ZZ'
+#define DSM_TAG_FO_GROUP '30ZZ'
+#define DSM_TAG_DSM_CONTEXT '40ZZ'
+#define DSM_TAG_DEV_INFO '50ZZ'
+#define DSM_TAG_SERIAL_NUM '60ZZ'
+#define DSM_TAG_CTRL_INFO '70ZZ'
+#define DSM_TAG_SUPPORTED_DEV '80ZZ'
+#define DSM_TAG_REG_PATH '90ZZ'
+#define DSM_TAG_FOG_DEV_ENTRY 'A0ZZ'
+#define DSM_TAG_DEV_ID 'B0ZZ'
+#define DSM_TAG_DEV_NAME 'C0ZZ'
+#define DSM_TAG_LB_POLICY 'D0ZZ'
+#define DSM_TAG_PR_KEYS 'E0ZZ'
+#define DSM_TAG_RESERVED_DEVICE 'F0ZZ'
+#define DSM_TAG_BIN_TO_ASCII '01ZZ'
+#define DSM_TAG_TARGET_PORT_LIST_ENTRY '11ZZ'
+#define DSM_TAG_TARGET_PORT_GROUP_ENTRY '21ZZ'
+#define DSM_TAG_RELATIVE_TARGET_PORT_ID '31ZZ'
+#define DSM_TAG_TARGET_PORT_GROUPS '41ZZ'
+#define DSM_TAG_CONTROLLER_LIST_ENTRY '51ZZ'
+#define DSM_TAG_CONTROLLER_INFO '61ZZ'
+#define DSM_TAG_IO_STATUS_BLOCK '71ZZ'
+#define DSM_TAG_DEVICE_ID_LIST '81ZZ'
+#define DSM_TAG_TP_DEVICE_LIST_ENTRY '91ZZ'
+#define DSM_TAG_RETRY_RESERVE 'A1ZZ'
+#define DSM_TAG_WORKITEM 'B1ZZ'
+#define DSM_TAG_SCSI_ADDRESS 'C1ZZ'
+#define DSM_TAG_FAIL_DEVINFO_LIST_ENTRY 'D1ZZ'
+#define DSM_TAG_TPG_COMPLETION_CONTEXT 'E1ZZ'
+#define DSM_TAG_SCSI_REQUEST_BLOCK 'F1ZZ'
+#define DSM_TAG_SCSI_SENSE_INFO '02ZZ'
+#define DSM_TAG_SPT_DATA_BUFFER '12ZZ'
+#define DSM_TAG_REG_KEY_RELATED '22ZZ'
+#define DSM_TAG_DEV_HARDWARE_ID '32ZZ'
+#define DSM_TAG_REG_VALUE_RELATED '42ZZ'
+#define DSM_TAG_ZOMBIEGROUP_ENTRY '52ZZ'
+#define DSM_TAG_PERSISTENT_RESERVATION '62ZZ'
+
+//
+// Parameters subkey name under HKLM\System\CCS\Services\MSDSM
+//
+#define DSM_SERVICE_PARAMETERS L"Parameters"
+
+//
+// Load Balance settings are persisted in the registry under this key
+//
+#define DSM_LOAD_BALANCE_SETTINGS L"DsmLoadBalanceSettings"
+
+//
+// Load Balance settings on a VID/PID basis are persistented in the registry
+// under this key
+//
+#define DSM_TARGETS_LOAD_BALANCE_SETTING L"DsmTargetsLoadBalanceSetting"
+
+//
+// Values persisted per device:
+// 1. Load Balance Policy
+// 2. Preferred Path
+// 3. Whether LB policy has been explicitly set
+//
+#define DSM_LOAD_BALANCE_POLICY L"DsmLoadBalancePolicy"
+#define DSM_PREFERRED_PATH L"DsmPreferredPath"
+#define DSM_POLICY_EXPLICITLY_SET L"DsmLoadBalancePolicyExplicitlySet"
+
+//
+// Prefix for subkey created for each path
+//
+#define DSM_PATH L"DSMPath"
+
+//
+// Values persisted per path:
+// 1. Whether primary
+// 2. Whether optimized
+// 3. Path weight.
+//
+// Primary Optimized State
+//====================================
+// True True Active-Optimized
+// True False Active-Unoptimized
+// False True StandBy
+// False False Unavailable
+//
+#define DSM_PRIMARY_PATH L"DsmPrimaryPath"
+#define DSM_OPTIMIZED_PATH L"DsmOptimizedPath"
+#define DSM_PATH_WEIGHT L"DsmPathWeight"
+
+//
+// Indicates that device doesn't support ALUA.
+//
+#define DSM_DEVINFO_ALUA_NOT_SUPPORTED 0
+
+//
+// Implies that device supports implicit ALUA transistions.
+//
+#define DSM_DEVINFO_ALUA_IMPLICIT 1
+
+//
+// Implies that device supports explicit ALUA state transitions.
+//
+#define DSM_DEVINFO_ALUA_EXPLICIT 2
+
+//
+// Type of device identifier (VPD 0x83)
+//
+typedef enum _DSM_DEVID_TYPE {
+ DSM_DEVID_SERIAL_NUMBER = 1,
+ DSM_DEVID_RELATIVE_TARGET_PORT,
+ DSM_DEVID_TARGET_PORT_GROUP
+} DSM_DEVID_TYPE, *PDSM_DEVID_TYPE;
+
+#define _DSM_TERNARY_BOOLEAN UCHAR
+typedef _DSM_TERNARY_BOOLEAN DSM_TERNARY_BOOLEAN, *PDSM_TERNARY_BOOLEAN;
+#define DSM_TERNARY_UNKNOWN 0
+#define DSM_TERNARY_TRUE 1
+#define DSM_TERNARY_FALSE 2
+
+//
+// Macro to determine if _Id2 is more preferred than _Id1 to build a device's
+// serial number.
+//
+#define DsmpIsPreferredDeviceId(_Id1, _Id2) (((_Id2) == StorageIdTypeScsiNameString) || \
+ ((_Id2) == StorageIdTypeFCPHName && (_Id1) != StorageIdTypeScsiNameString) || \
+ ((_Id2) == StorageIdTypeEUI64 && (_Id1) != StorageIdTypeScsiNameString && (_Id1) != StorageIdTypeFCPHName) || \
+ ((_Id2) == StorageIdTypeVendorId && (_Id1) != StorageIdTypeScsiNameString && (_Id1) != StorageIdTypeFCPHName && (_Id1) != StorageIdTypeEUI64) || \
+ ((_Id2) == StorageIdTypeVendorSpecific && (_Id1) != StorageIdTypeScsiNameString && (_Id1) != StorageIdTypeFCPHName && (_Id1) != StorageIdTypeEUI64 && (_Id1) != StorageIdTypeVendorId))
+
+//
+// Device State
+//
+typedef enum _DSM_DEVICE_STATE {
+
+ //
+ // If ALUA is not supported, this state indicates that the device is active
+ // and a request can be sent to the device.
+ // If ALUA is supported, then this state indicates optimizied device-path
+ // pair for the device.
+ //
+ DSM_DEV_ACTIVE_OPTIMIZED = 0,
+
+ //
+ // If ALUA is not supported, this state is not used.
+ // If ALUA is supported, then this state indicates active but unoptimized
+ // device-path pairing for the device. Can be used in in case no
+ // active/optimized path is available to service the IO.
+ //
+ DSM_DEV_ACTIVE_UNOPTIMIZED,
+
+ //
+ // If ALUA is not supported, this state indicates that the device is in
+ // standby state. A request can be sent to the device in this state.
+ // If ALUA is supported, then this state indicates standby device-path
+ // pairing and only certain requests can be handled in this state.
+ //
+ DSM_DEV_STANDBY,
+
+ //
+ // If ALUA is not supported, this state is not used.
+ // If ALUA is supported, then this state indicates that the device-path pairing
+ // is not active and incapable of handling any requests.
+ //
+ DSM_DEV_UNAVAILABLE,
+
+ //
+ // If ALUA is not supported, this state is not used.
+ // If ALUA is supported, then this state indicates that the device-path pairing
+ // (actually its TPG) is in a transitioning state.
+ //
+ DSM_DEV_TRANSITIONING = 15,
+
+ //
+ // Initial state when devInfo is created.
+ //
+ DSM_DEV_NOT_USED_STATE = 16,
+
+ //
+ // Indicates that the state was undetermined (this is applicable only for
+ // a deviceInfo's DesiredState or if the device instance's path was not
+ // determined).
+ //
+ DSM_DEV_UNDETERMINED,
+
+ //
+ // Indicates that a request sent down previously failed with a fatal error
+ //
+ DSM_DEV_FAILED,
+
+ //
+ // Indicates that InvalidatePath has been called
+ //
+ DSM_DEV_INVALIDATED,
+
+ //
+ // This indicates the device is about to be removed. No new request
+ // should be sent to the device.
+ //
+ DSM_DEV_REMOVE_PENDING,
+
+ //
+ // This indicates the device has been removed.
+ //
+ DSM_DEV_REMOVED
+
+} DSM_DEVICE_STATE, *PDSM_DEVICE_STATE;
+
+//
+// Device states supported
+//
+#define DSM_STATE_ACTIVE_OPTIMIZED_SUPPORTED 0
+#define DSM_STATE_STANDBY_SUPPORTED 1
+#define DSM_STATE_ACTIVE_UNOPTIMIZED_SUPPORTED 2
+#define DSM_STATE_UNAVAILABLE_SUPPORTED 4
+
+
+//
+// Macro to determine if devInfo is in a failure state.
+//
+#define DsmpIsDeviceFailedState(_State) ((_State) > DSM_DEV_NOT_USED_STATE)
+
+//
+// Macro to determine if devInfo was initialized.
+//
+#define DsmpIsDeviceInitialized(_DeviceInfo) ((_DeviceInfo)->Initialized)
+
+//
+// Macro to determine if device is "usable" (ie. IsPathActive was successfully called).
+//
+#define DsmpIsDeviceUsable(_DeviceInfo) ((_DeviceInfo)->Usable)
+
+//
+// Macro to determine if devInfo was used to send down registration.
+// It the devInfo's group is not reserved, then the devInfo doesn't need to have
+// had a register go down it.
+// It the group is reserved, then the devInfo MUST have had a register go down it
+// for it to be used.
+//
+#define DsmpIsDeviceUsablePR(_DeviceInfo) (!(_DeviceInfo)->Group->PRKeyValid || (_DeviceInfo)->PRKeyRegistered)
+
+
+//
+// Macro to determine if _State2 is a more preferred state than _State1.
+//
+#define DsmpIsBetterDeviceState(_State1, _State2) (((_State1) == DSM_DEV_STANDBY && (_State2) == DSM_DEV_ACTIVE_UNOPTIMIZED) || \
+ ((_State1) == DSM_DEV_UNAVAILABLE && \
+ ((_State2) == DSM_DEV_ACTIVE_UNOPTIMIZED || (_State2) == DSM_DEV_STANDBY)) || \
+ ((_State1) == DSM_DEV_TRANSITIONING && \
+ ((_State2) == DSM_DEV_ACTIVE_UNOPTIMIZED || (_State2) == DSM_DEV_STANDBY) || (_State2) == DSM_DEV_UNAVAILABLE))
+
+//
+// Macro to determine if passed in _State is active.
+//
+#define DsmpIsDeviceStateActive(_State) ((_State) == DSM_DEV_ACTIVE_OPTIMIZED || (_State) == DSM_DEV_ACTIVE_UNOPTIMIZED)
+
+//
+// Macro to determine if symmetric access to the storage
+//
+#define DsmpIsSymmetricAccess(_DeviceInfo) ((_DeviceInfo)->ALUASupport == DSM_DEVINFO_ALUA_NOT_SUPPORTED || \
+ ((_DeviceInfo)->ALUASupport == DSM_DEVINFO_ALUA_IMPLICIT && \
+ (_DeviceInfo)->Group->Symmetric))
+
+//
+// Multi-path Group State
+//
+typedef enum _DSM_GROUP_STATE {
+
+ //
+ // This indicates that the device is in working state.
+ //
+ DSM_GP_NORMAL = 1,
+
+ //
+ // This indicates that there is a pending reservation failover
+ //
+ DSM_GP_PENDING,
+
+ //
+ // This indicates that the device has lost all its paths
+ //
+ DSM_GP_FAILED
+
+} DSM_GROUP_STATE, *PDSM_GROUP_STATE;
+
+//
+// Fail-Over Group State
+//
+typedef enum _DSM_FAILOVER_GROUP_STATE {
+
+ //
+ // This indicates that the path is in working state.
+ //
+ DSM_FG_NORMAL = 1,
+
+ //
+ // This indicates the path which had failed earlier
+ // is back to working state now.
+ //
+ DSM_FG_FAILBACK,
+
+ //
+ // This indicates the path is about to be removed
+ //
+ DSM_FG_PENDING_REMOVE,
+
+ //
+ // This indicates the path has failed.
+ //
+ DSM_FG_FAILED
+
+} DSM_FAILOVER_GROUP_STATE, *PDSM_FAILOVER_GROUP_STATE;
+
+#define DsmpIsPathFailedState(_State) ((_State) >= DSM_FG_PENDING_REMOVE)
+
+//
+// DSM Context is the global driver context that gets passed to each of the DSM
+// entry points.
+//
+// The DSM Context will maintain a list of all DeviceInfos (device-path pairing).
+// It will maintain a list of Group entries. Each entry in the Group list will
+// represent a LUN's different instances down different paths (i.e. DeviceInfos).
+// Each entry in the Group will maintain a list of target port groups.
+// Each entry in the target port group list will maintain a list of target
+// ports that make up the target port group. Every deviceInfo that isn't
+// in a failure state will be in the same state as the Asymmetric Access
+// State of the target port group.
+// There will be a list of Fail Over Group entries, where each entry represents
+// the list of devices that fail over as a group (i.e. devices on the same path).
+// There will also be a list of controller entries, representing the controllers
+// on all storages connected to the system.
+//
+typedef struct _DSM_CONTEXT {
+
+ //
+ // Used to synchronize access to the SupportedDevices list.
+ //
+ KSPIN_LOCK SupportedDevicesListLock;
+
+ //
+ // List of supported devices - added into the INF.
+ //
+ UNICODE_STRING SupportedDevices;
+
+ //
+ // Used to synchronize access to the elements in this structure.
+ //
+ EX_SPIN_LOCK DsmContextLock;
+
+ //
+ // Flag cached that indicates if statistics don't need to be gathered
+ //
+ BOOLEAN DisableStatsGathering;
+
+ UCHAR Reserved[3];
+
+ //
+ // Number of devices currently found.
+ //
+ ULONG NumberDevices;
+
+ //
+ // List of devices.
+ //
+ LIST_ENTRY DeviceList;
+
+ //
+ // Number of multi-path groups.
+ //
+ ULONG NumberGroups;
+
+ //
+ // List of multi-path groups.
+ //
+ LIST_ENTRY GroupList;
+
+ //
+ // Number of fail-over groups.
+ //
+ ULONG NumberFOGroups;
+
+ //
+ // List of fail-over groups.
+ //
+ LIST_ENTRY FailGroupList;
+
+ //
+ // Number of controllers.
+ //
+ ULONG NumberControllers;
+
+ //
+ // List of controllers
+ //
+ LIST_ENTRY ControllerList;
+
+ //
+ // Number of stale fail-over groups
+ //
+ ULONG NumberStaleFOGroups;
+
+ //
+ // List of stale fail-over groups maintained for paths for which all devices
+ // have gotten removed but for which there is still outstanding IO-statistics
+ //
+ LIST_ENTRY StaleFailGroupList;
+
+ //
+ // Context value passed to the DSM from MPIO.
+ //
+ PVOID MPIOContext;
+
+
+ //
+ // Look-aside list of completion routine context structures.
+ //
+ NPAGED_LOOKASIDE_LIST CompletionContextList;
+
+} DSM_CONTEXT, *PDSM_CONTEXT;
+
+//
+// Statistics structure. Used by the device and path routines.
+//
+typedef struct _DSM_STATS {
+
+ ULONG NumberReads;
+ ULONG NumberWrites;
+ ULONGLONG BytesRead;
+ ULONGLONG BytesWritten;
+
+} DSM_STATS, *PDSM_STATS;
+
+
+//
+// Information about each device that is supported by the DSM.
+//
+typedef struct _DSM_DEVICE_INFO {
+
+ //
+ // To link to the next device info structure in the list
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // The device SIG. Used for debug.
+ //
+ ULONG DeviceSig;
+
+ //
+ // Back-pointer to the DSM_CONTEXT.
+ //
+ PVOID DsmContext;
+
+ //
+ // The underlying port driver PDO.
+ //
+ PDEVICE_OBJECT PortPdo;
+
+ //
+ // The port FDO to which PortPdo is attached.
+ //
+ PDEVICE_OBJECT PortFdo;
+
+ //
+ // The DeviceObject to which I/Os generated by the DSM should
+ // be sent. This is given to us by MPIO.
+ //
+ PDEVICE_OBJECT TargetObject;
+
+ //
+ // The multi-path group to which this device belongs.
+ //
+ struct _DSM_GROUP_ENTRY *Group;
+
+ //
+ // The fail-over group to which this device belongs.
+ //
+ struct _DSM_FAILOVER_GROUP *FailGroup;
+
+ //
+ // The controller through which this device showed up.
+ //
+ struct _DSM_CONTROLLER_LIST_ENTRY *Controller;
+
+ //
+ // The Target Port Group that this device belongs to.
+ //
+ struct _DSM_TARGET_PORT_GROUP_ENTRY *TargetPortGroup;
+
+ //
+ // The Target Port that this device was exposed via.
+ //
+ struct _DSM_TARGET_PORT_LIST_ENTRY *TargetPort;
+
+ //
+ // The current state of this device: ACTIVE_O, ACTIVE_U, STANDBY, UNAVAILABLE, etc.
+ //
+ DSM_DEVICE_STATE State;
+
+ //
+ // Previous state of this device. Updated whenever this deviceInfo makes a
+ // state transition.
+ //
+ DSM_DEVICE_STATE PreviousState;
+
+ //
+ // The desired state of this device: based on PrimaryPath and OptimizedPath
+ // specified in the registry.
+ //
+ DSM_DEVICE_STATE DesiredState;
+
+ //
+ // The ALUA state of the TPG immediately after a ReportTPG is issued.
+ //
+ DSM_DEVICE_STATE ALUAState;
+
+ //
+ // Holds state information temporarily while applying LB policy. Used in case
+ // changes need to be reverted in case of failure to apply the policy.
+ //
+ DSM_DEVICE_STATE TempPreviousStateForLB;
+
+ //
+ // This is to save off the last known non-failed state.
+ // In case of an error down this deviceInfo, it is marked to be in Failed state.
+ // However, if no remove comes down for this device and a PathVerify down this
+ // deviceInfo succeeds, we need to put the deviceInfo back into a usable state.
+ //
+ DSM_DEVICE_STATE LastKnownGoodState;
+
+ //
+ // This counter indicates that this deviceInfo is being used and a remove
+ // must thus wait until the counter falls to 0.
+ //
+ LONG BlockRemove;
+
+
+ //
+ // This indicates whether this device has handled a register/register_ignore_existing request,
+ // irrespective of the actual status of the operation.
+ //
+ BOOLEAN RegisterServiced;
+
+ //
+ // This flag is set when a register/register_ignore_existing succeeds down this device-path pair.
+ //
+ BOOLEAN PRKeyRegistered;
+
+ //
+ // Indicates whether the serial number was embedded in the device
+ // descriptor, or it was allocated.
+ //
+ BOOLEAN SerialNumberAllocated;
+
+ //
+ // Flag to indicate that SetDeviceInfo has been called (and succeeded) on this device
+ //
+ BOOLEAN Initialized;
+
+ //
+ // Flag to indicate that IsPathActive has been called (and succeeded) on this device.
+ //
+ BOOLEAN Usable;
+
+ //
+ // Flag to indicate if IALUAE was disabled (via mode select)
+ //
+ BOOLEAN ImplicitDisabled;
+
+ //
+ // Flag to indicate that RTPG has already been sent down in Inquire, so
+ // PathVerify can ignore sending down one more if it is called during
+ // device initialization.
+ //
+ BOOLEAN IgnorePathVerify;
+
+ //
+ // Bit map indicating whether (and what kind) of ALUA support.
+ //
+ UCHAR ALUASupport;
+
+ //
+ // Weight assigned to this path by management application. This is used
+ // when doing Load Balancing based on weighted paths.
+ //
+ ULONG PathWeight;
+
+ //
+ // Number of requests outstanding on this device.
+ //
+ LONG NumberOfRequestsInProgress;
+
+ //
+ // I/O, Fail-Over statistics.
+ //
+ DSM_STATS DeviceStats;
+
+ //
+ // The device's serial number.
+ //
+ PSTR SerialNumber;
+
+ //
+ // The scsi address of the port pdo.
+ //
+ PSCSI_ADDRESS ScsiAddress;
+
+
+ //
+ // Kernel structure that describes this device. Passed in to Inquire.
+ //
+ // NOTE: Descriptor should be the LAST field in this structure
+ //
+ STORAGE_DEVICE_DESCRIPTOR Descriptor;
+
+} DSM_DEVICE_INFO, *PDSM_DEVICE_INFO;
+
+typedef enum _DSM_DEFAULT_LB_POLICY_TYPE {
+ DSM_DEFAULT_LB_POLICY_ALUA_CAPABILITY = 0, // DSM assigned based on LUN access capability
+ DSM_DEFAULT_LB_POLICY_DSM_WIDE, // Admin has set a DSM-wide default policy
+ DSM_DEFAULT_LB_POLICY_VID_PID, // Admin has set a default policy for LUN's VID/PID
+ DSM_DEFAULT_LB_POLICY_LUN_EXPLICIT // Admin has explicitly set the policy on the LUN
+} DSM_DEFAULT_LB_POLICY_TYPE, *PDSM_DEFAULT_LB_POLICY_TYPE;
+
+typedef ULONG DSM_LOAD_BALANCE_TYPE, *PDSM_LOAD_BALANCE_TYPE;
+
+
+//
+// Information about multi-path groups: The same device found via multiple paths
+// are put under one group. Each group will have it's own Load Balance policy
+// settings. In other words, Load Balance policy settings are on per-device basis.
+//
+typedef struct _DSM_GROUP_ENTRY {
+
+ //
+ // To link to the next entry in the multi-path group.
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // Group signature. Used for debug.
+ //
+ ULONG GroupSig;
+
+ //
+ // Ordinal of creation. Never decremented.
+ //
+ ULONG GroupNumber;
+
+ //
+ // State of the group.
+ //
+ DSM_GROUP_STATE State;
+
+ //
+ // Number of devices in the multi-path group.
+ //
+ ULONG NumberDevices;
+
+ //
+ // Array of devices belonging to this group.
+ //
+ PDSM_DEVICE_INFO DeviceList[DSM_MAX_PATHS];
+
+ //
+ // Max time to retry failed PR requests
+ //
+ ULONG MaxPRRetryTimeDuringStateTransition;
+
+ //
+ // Number of target port groups that this device is accessible via.
+ //
+ ULONG NumberTargetPortGroups;
+
+ //
+ // Array of the target port groups that this LUN belongs in.
+ //
+ struct _DSM_TARGET_PORT_GROUP_ENTRY *TargetPortGroupList[DSM_MAX_PATHS];
+
+ //
+ // Key used in Persistent Reserve\Release. This key is provided to the DSM
+ // by Cluster service. If cluster service has provided the key PRKeyValid
+ // is set to TRUE. PRKeyValid is set to FALSE otherwise.
+ // PRServiceAction, PRType and PRScope are the service action, type and
+ // scope associated with the PR registration.
+ //
+ UCHAR PersistentReservationRegisteredKey[8];
+ UCHAR PRServiceAction;
+ UCHAR PRType;
+ UCHAR PRScope;
+ UCHAR PRKeyValid;
+
+
+ //
+ // Flag used to denote that LU access is symmetric down all paths
+ //
+ BOOLEAN Symmetric;
+
+ //
+ // Flag to indicate whether or not to use same path for sequential IO
+ // when employing Least Blocks load balance policy.
+ //
+ BOOLEAN UseCacheForLeastBlocks;
+
+ //
+ // Flag used to indicate if a throttle request succeeded.
+ //
+ ULONG Throttled;
+
+ //
+ // Counter to track the number of RTPG in flight.
+ //
+ ULONG InFlightRTPG;
+
+ //
+ // A bitmask of which devices are currently reserved.
+ //
+ ULONG ReservationList;
+
+ //
+ // Which type of Load Balancing is being performed.
+ //
+ DSM_LOAD_BALANCE_TYPE LoadBalanceType;
+
+ //
+ // Indicates how the Load Balancing policy was selected.
+ //
+ DSM_DEFAULT_LB_POLICY_TYPE LBPolicySelection;
+
+ //
+ // The path to use when possible - if in F.O. Only, if failover had taken
+ // place and this path comes back online, failback to this path will take
+ // place.
+ //
+ ULONGLONG PreferredPath;
+
+ //
+ // The path to choose when Round Robin Load Balance policy is in use
+ //
+ PVOID PathToBeUsed;
+
+ //
+ // Size of cache set by Admin. Used in case of handling sequential
+ // IO in Least Blocks policy.
+ //
+ ULONGLONG CacheSizeForLeastBlocks;
+
+ //
+ // The HardwareId (VID/PID) of the LUN
+ //
+ PWSTR HardwareId;
+
+ //
+ // The registry key under which Load Balance Policy settings
+ // are stored in the registry for this Device Group.
+ //
+ PWSTR RegistryKeyName;
+
+ //
+ // Number of failing deviceInfos
+ //
+ ULONG NumberFailingDevInfos;
+
+ //
+ // To link the list of failed A/O devInfos and the corresponding non-A/O
+ // devInfos that are temporarily being used to service IO until STPG can
+ // properly update the device states. This is applicable only for ALUA
+ // devices.
+ //
+ LIST_ENTRY FailingDevInfoList;
+
+ //
+ // General Purpose Event.
+ //
+ KEVENT Event;
+
+} DSM_GROUP_ENTRY, *PDSM_GROUP_ENTRY;
+
+//
+// The collection of devices on one path. These fail-over as a unit.
+// A path is considered an I_T nexus, i.e. Initiator port to Target (controller) port.
+//
+typedef struct _DSM_FAILOVER_GROUP {
+
+ //
+ // To link to the next entry in the failover group
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // Signature. Used for debug.
+ //
+ ULONG FailOverSig;
+
+ //
+ // State of the Path.
+ //
+ DSM_FAILOVER_GROUP_STATE State;
+
+ //
+ // The pathId corresponding to this FOG. It may or may not be
+ // the same as what MPIO gave us as the default value.
+ //
+ PVOID PathId;
+
+ //
+ // The default pathId (port FDO).
+ //
+ PDEVICE_OBJECT MPIOPath;
+
+ //
+ // Last LBA
+ //
+ ULONGLONG LastLba;
+
+ //
+ // Cumulative outstanding IO (in terms of size)
+ //
+ ULONGLONG OutstandingBytesOfIO;
+
+ //
+ // Count of inflight IOs. This will be used in LQD load balance policy.
+ //
+ volatile LONG NumberOfRequestsInFlight;
+
+ //
+ // Number of devices in this FOG.
+ //
+ ULONG Count;
+
+ //
+ // List of devices that will over together.
+ //
+ LIST_ENTRY FOG_DeviceList;
+
+ //
+ // List of zombie groups (in case a device is removed before the failover
+ // processing begins).
+ //
+ LIST_ENTRY ZombieGroupList;
+
+} DSM_FAILOVER_GROUP, *PDSM_FAILOVER_GROUP;
+
+
+//
+// Information about a target port group entry for a given LUN.
+// Note: This is not a global list of all TPGs that are built. It is local to a Group entry.
+//
+typedef struct _DSM_TARGET_PORT_GROUP_ENTRY {
+
+ //
+ // Signature. Used for debug.
+ //
+ ULONG TargetPortGroupSig;
+
+ //
+ // The asymmetric access state for this target port group:
+ // ACTIVE_O, ACTIVE_U, STANDBY or UNAVAILABLE
+ //
+ DSM_DEVICE_STATE AsymmetricAccessState;
+
+ //
+ // Flag to indicate if this is the preferred target port group.
+ //
+ BOOLEAN Preferred;
+
+ //
+ // Supported access states
+ //
+ BOOLEAN ActiveOptimizedSupported;
+ BOOLEAN ActiveUnoptimizedSupported;
+ BOOLEAN StandBySupported;
+ BOOLEAN UnavailableSupported;
+
+ //
+ // Indicates if the device reports asymmetric state as being under transition.
+ //
+ BOOLEAN TransitioningSupported;
+
+ //
+ // Flag to indicate if this has been returned in any subsequent RTPG after
+ // it is initially built. (If this flag is not set after parsing the RTPG
+ // information, it indicates that this TPG entry is stale and should be
+ // deleted).
+ //
+ BOOLEAN Traversed;
+
+ UCHAR Reserved;
+
+ //
+ // The target group identifier
+ //
+ USHORT Identifier;
+
+ //
+ // Status code
+ //
+ UCHAR StatusCode;
+
+ //
+ // Vendor unique
+ //
+ UCHAR VendorUnique;
+
+ //
+ // Backpointer to owning group
+ //
+ PDSM_GROUP_ENTRY Group;
+
+ //
+ // Number of target ports that make up this group
+ //
+ ULONG NumberTargetPorts;
+
+ //
+ // Linked list of target ports that make up this target port group.
+ //
+ LIST_ENTRY TargetPortList;
+
+} DSM_TARGET_PORT_GROUP_ENTRY, *PDSM_TARGET_PORT_GROUP_ENTRY;
+
+
+//
+// Information about each target port list entry for a given target port group.
+// Note: this is not a global list of all TPs. It is local to a given TPG entry.
+//
+typedef struct _DSM_TARGET_PORT_LIST_ENTRY {
+
+ //
+ // Link
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // Signature. Used for debug.
+ //
+ ULONG TargetPortSig;
+
+ //
+ // Relative target port identifier
+ //
+ ULONG Identifier;
+
+ //
+ // Backpointer to owning target port group
+ //
+ PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup;
+
+ //
+ // Number of device instances exposed via this target port
+ //
+ ULONG Count;
+
+ //
+ // List of device instances exposed via this target port
+ //
+ LIST_ENTRY TP_DeviceList;
+
+} DSM_TARGET_PORT_LIST_ENTRY, *PDSM_TARGET_PORT_LIST_ENTRY;
+
+//
+// Information about each controller entry
+//
+typedef struct _DSM_CONTROLLER_LIST_ENTRY {
+
+ //
+ // To link to the next contoller entry.
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // It's signature. Used for debug.
+ //
+ ULONG ControllerSig;
+
+ //
+ // Device object (this controller's PDO).
+ //
+ PDEVICE_OBJECT DeviceObject;
+
+ //
+ // Port FDO through which this controller object was exposed.
+ //
+ PDEVICE_OBJECT PortObject;
+
+ //
+ // Identifier.
+ //
+ _Field_size_(IdLength) PUCHAR Identifier;
+
+ //
+ // Identifier length.
+ //
+ ULONG IdLength;
+
+ //
+ // Identifier code set.
+ //
+ STORAGE_IDENTIFIER_CODE_SET IdCodeSet;
+
+ //
+ // Controller's SCSI address.
+ //
+ PSCSI_ADDRESS ScsiAddress;
+
+ //
+ // Number of references to this entry.
+ //
+ UCHAR RefCount;
+
+ //
+ // Flag to indicate whether this is a fake entry built for storage that do
+ // NOT have controllers
+ //
+ BOOLEAN IsFakeController;
+
+ UCHAR Reserved[2];
+
+} DSM_CONTROLLER_LIST_ENTRY, *PDSM_CONTROLLER_LIST_ENTRY;
+
+//
+// Generic linked list of devices
+//
+typedef struct _DSM_DEVICELIST_ENTRY {
+
+ //
+ // To link to the next device info structure in the list
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // Representation of device-path pair
+ //
+ PDSM_DEVICE_INFO DeviceInfo;
+
+} DSM_DEVICELIST_ENTRY, *PDSM_DEVICELIST_ENTRY;
+
+//
+// Zombie Group List Entry
+//
+typedef struct _DSM_ZOMBIEGROUP_ENTRY {
+
+ //
+ // To link to the next zombie group structure in the list
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // Pointer to actual group entry
+ //
+ PDSM_GROUP_ENTRY Group;
+
+ //
+ // Flag to indicate that the failover thread has processed this entry.
+ //
+ BOOLEAN Processed;
+
+} DSM_ZOMBIEGROUP_ENTRY, *PDSM_ZOMBIEGROUP_ENTRY;
+
+//
+// Linked list of devices that will failover as a group
+//
+typedef DSM_DEVICELIST_ENTRY DSM_FOG_DEVICELIST_ENTRY, *PDSM_FOG_DEVICELIST_ENTRY;
+
+//
+// Linked list of the same device being exposed off of a particular target port
+// (possibly because the controller is connected to multiple HBAs).
+//
+typedef DSM_DEVICELIST_ENTRY DSM_TARGET_PORT_DEVICELIST_ENTRY, *PDSM_TARGET_PORT_DEVICELIST_ENTRY;
+
+//
+// Information about each failing devInfo and its corresponding devInfo
+// being used temporarily to service requests until STPG can update new
+// device states.
+//
+typedef struct _DSM_FAIL_PATH_PROCESSING_LIST_ENTRY {
+
+ //
+ // To link to the next device info structure in the list
+ //
+ LIST_ENTRY ListEntry;
+
+ //
+ // Representation of the failing device-path pair
+ //
+ PDSM_DEVICE_INFO FailingDeviceInfo;
+
+ //
+ // Representation of the new candidate device-path pair that will take over
+ // processing of requests
+ //
+ PDSM_DEVICE_INFO TempDeviceInfo;
+
+} DSM_FAIL_PATH_PROCESSING_LIST_ENTRY, *PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY;
+
+//
+// Completion context structure.
+//
+typedef struct _DSM_COMPLETION_CONTEXT {
+
+ //
+ // The device that handled the request.
+ //
+ PDSM_DEVICE_INFO DeviceInfo;
+
+ //
+ // The global context.
+ //
+ PDSM_CONTEXT DsmContext;
+
+ //
+ // These are used to store control code, pointer to KEVENT, etc.
+ //
+ PVOID RequestUnique1;
+
+ ULONG_PTR RequestUnique2;
+
+#if DBG
+ //
+ // Request time-stamp.
+ //
+ LARGE_INTEGER TickCount;
+#endif
+
+} DSM_COMPLETION_CONTEXT, *PDSM_COMPLETION_CONTEXT;
+
+//
+// Completion context structure for report/set target port groups.
+//
+typedef struct _DSM_TPG_COMPLETION_CONTEXT {
+
+ PDSM_COMPLETION_CONTEXT CompletionContext;
+
+ PSCSI_REQUEST_BLOCK Srb;
+
+ PVOID SenseInfoBuffer;
+
+ ULONG NumberRetries;
+
+ UCHAR SenseInfoBufferLength;
+
+} DSM_TPG_COMPLETION_CONTEXT, *PDSM_TPG_COMPLETION_CONTEXT;
+
+//
+// Version number used to determine whice version of MPIO_DSM_Path to use.
+//
+#define DSM_WMI_VERSION_1 1
+#define DSM_WMI_VERSION_2 2
+
+//
+// Version of MPIO_DSM_Path that is currently supported by this DSM.
+//
+#define DSM_WMI_VERSION DSM_WMI_VERSION_2
+
+//
+// This struct is used to save Load Balance Policy Settings in the registry
+//
+typedef struct _DSM_LOAD_BALANCE_POLICY_SETTINGS {
+
+ WCHAR RegistryKeyName[256];
+ ULONG LoadBalancePolicy;
+ ULONG PathCount;
+ MPIO_DSM_Path_V2 DsmPath[1];
+
+} DSM_LOAD_BALANCE_POLICY_SETTINGS, *PDSM_LOAD_BALANCE_POLICY_SETTINGS;
+
+//
+// This structure is used to pass in information used by the workitem
+// to failover reservations down another path.
+//
+typedef struct _DSM_RETRY_RESERVE {
+
+ PDSM_COMPLETION_CONTEXT CompletionContext;
+
+ PIRP Irp;
+
+ PKEVENT Event;
+
+} DSM_RETRY_RESERVE, *PDSM_RETRY_RESERVE;
+
+//
+// This structure defines the workitem that will be used to handle reservation
+// failover.
+//
+typedef struct _DSM_WORKITEM {
+
+ //
+ // Work item that should be freed by the worker routine
+ //
+ PIO_WORKITEM WorkItem;
+
+ //
+ // Context to be passed to worker routine
+ //
+ PVOID Context;
+
+} DSM_WORKITEM, *PDSM_WORKITEM;
+
+#endif // _MSDSM_H
+
+
diff --git a/tests/projects/wdk/kmdf/msdsm/msdsm.mof b/tests/projects/wdk/kmdf/msdsm/msdsm.mof
new file mode 100644
index 000000000..53ee61392
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/msdsm.mof
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2004 Microsoft Corporation
+//
+//
+// Microsoft DSM's internal classes
+//
+
+//
+// Perf class.
+//
+[WMI,
+ guid("{a34d03ec-6b0b-46a1-9178-82525f41133f}")]
+class MSDSM_DEVICEPATH_PERF
+{
+ [WmiDataId(1),
+ Description("Path Identifier.") : amended
+ ] uint64 PathId;
+
+ [WmiDataId(2),
+ Description("Number of Read Requests.") : amended
+ ] uint32 NumberReads;
+
+ [WmiDataId(3),
+ Description("Number of Write Requests.") : amended
+ ] uint32 NumberWrites;
+
+ [WmiDataId(4),
+ Description("Total Bytes Read.") : amended
+ ] uint64 BytesRead;
+
+ [WmiDataId(5),
+ Description("Total Bytes Written.") : amended
+ ] uint64 BytesWritten;
+};
+
+[WMI,
+ Dynamic,
+ Provider("WmiProv"),
+ Description("Retrieve MSDSM Performance Information.") : amended,
+ Locale("MS\\0x409"),
+ guid("{875b8871-4889-4114-93f6-cd064c001cea}")]
+class MSDSM_DEVICE_PERF
+{
+ [key, read]
+ string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1),
+ read,
+ Description("Number of paths.") : amended
+ ] uint32 NumberPaths;
+
+ [WmiDataId(2),
+ read,
+ Description("Array of Performance Information per path for the device.") : amended,
+ WmiSizeIs("NumberPaths")
+ ] MSDSM_DEVICEPATH_PERF PerfInfo[];
+};
+
+//
+// Methods
+// Clear perf counters.
+//
+[Dynamic,
+ Provider("WMIProv"),
+ WMI,
+ Description("MSDSM WMI Methods") : amended,
+ guid("{04517f7e-92bb-4ebe-aed0-54339fa5f544}"),
+ locale("MS\\0x409")
+]
+class MSDSM_WMI_METHODS
+{
+
+ [key, read]
+ string InstanceName;
+ [read] boolean Active;
+
+ [WmiMethodId(1),
+ Implemented,
+ Description("Clear path performance counters for the device.") : amended
+ ] void MSDsmClearCounters();
+};
diff --git a/tests/projects/wdk/kmdf/msdsm/msdsm.rc b/tests/projects/wdk/kmdf/msdsm/msdsm.rc
new file mode 100644
index 000000000..743640342
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/msdsm.rc
@@ -0,0 +1,24 @@
+//+-------------------------------------------------------------------------
+//
+// Microsoft Windows
+//
+// Copyright (C) Microsoft Corporation, 2004
+//
+// File: msdsm.rc
+//
+//--------------------------------------------------------------------------
+
+#include <windows.h>
+
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DRV
+#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
+#define VER_FILEDESCRIPTION_STR "Microsoft Device Specific Module"
+#define VER_INTERNALNAME_STR "msdsm.sys"
+#define VER_ORIGINALFILENAME_STR "msdsm.sys"
+
+#include "common.ver"
+
+MofResourceName MOFDATA msdsm.bmf
+DsmMofResourceName MOFDATA msdsmdsm.bmf
diff --git a/tests/projects/wdk/kmdf/msdsm/msdsmdsm.mof b/tests/projects/wdk/kmdf/msdsm/msdsmdsm.mof
new file mode 100644
index 000000000..f85eea270
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/msdsmdsm.mof
@@ -0,0 +1,141 @@
+//
+// Copyright (C) 2004 Microsoft Corporation
+//
+// Microsoft DSM's DSM-specific classes
+//
+
+//
+// Class used for retrieving and setting MSDSM-wide default load balance policy.
+//
+[WMI,
+ Dynamic,
+ Provider("WmiProv"),
+ Description("MSDSM-wide default load balance policies.") : amended,
+ Locale("MS\\0x409"),
+ guid("{c81b5681-f3ca-4c98-9325-707d0d62ffc4}")]
+class MSDSM_DEFAULT_LOAD_BALANCE_POLICY
+{
+ [key, read]
+ string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1),
+ read, write,
+ Description("Load Balance Policy to be applied to devices controlled by MSDSM.") : amended
+ ] uint32 LoadBalancePolicy;
+
+ [WmiDataId(2),
+ read,
+ Description("Reserved.") : amended
+ ] uint32 Reserved;
+
+ //
+ // Preferred path.
+ //
+ [WmiDataId(3),
+ read, write,
+ Description("Preferred Path.") : amended
+ ] uint64 PreferredPath;
+};
+
+//
+// Embedded class that describes a target and the default load balance policy
+// of its LUNs.
+//
+[WMI,
+ guid("{ddb00a72-0fab-418b-a89e-97370ae293a4}")]
+class MSDSM_TARGET_DEFAULT_POLICY_INFO
+{
+ //
+ // VID-PID string as an 8 + 16 character concatenated string.
+ // Spaces should be used to make the VID 8 chars and the PID 16 chars.
+ //
+ [WmiDataId(1),
+ MaxLen(31),
+ Description("Concatenated VendorID (8 characters) and ProductID (16 characters).") : amended
+ ] string HardwareId;
+
+ //
+ // The default load balance policy to be applied to LUNs from the target
+ // whose hardware id matches the VID/PID above.
+ // NOTE: Setting this to 0 will act as removal of default setting for this
+ // target.
+ //
+ [WmiDataId(2)] uint32 LoadBalancePolicy;
+
+ //
+ // Used for alignment reasons.
+ //
+ [WmiDataId(3)] uint32 Reserved;
+
+ //
+ // Preferred path.
+ //
+ [WmiDataId(4)] uint64 PreferredPath;
+};
+
+//
+// Class used for retrieving and setting target-level default load balance policy.
+//
+[WMI,
+ Dynamic,
+ Provider("WmiProv"),
+ Description("Target-level default load balance policies.") : amended,
+ Locale("MS\\0x409"),
+ guid("{5ccbcd91-1b56-4327-a2f3-0960335f8846}")]
+class MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICY
+{
+ [key, read]
+ string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1),
+ read, write,
+ Description("Number of targets specified.") : amended
+ ] uint32 NumberDevices;
+
+ [WmiDataId(2),
+ read,
+ Description("Reserved.") : amended
+ ] uint32 Reserved;
+
+ [WmiDataId(3),
+ read, write,
+ MaxLen(31),
+ Description("Array of target hardware identifiers with policy and preferred path information.") : amended,
+ WmiSizeIs("NumberDevices")
+ ] MSDSM_TARGET_DEFAULT_POLICY_INFO TargetDefaultPolicyInfo[];
+};
+
+//
+// Supported devices list class.
+//
+[WMI,
+ Dynamic,
+ Provider("WmiProv"),
+ Description("Retrieve MSDSM's supported devices list.") : amended,
+ Locale("MS\\0x409"),
+ guid("{c362d67c-371e-44d8-8bba-044619e4f245}")]
+class MSDSM_SUPPORTED_DEVICES_LIST
+{
+ [key, read]
+ string InstanceName;
+ [read] boolean Active;
+
+ [WmiDataId(1),
+ read,
+ Description("Number of supported devices.") : amended
+ ] uint32 NumberDevices;
+
+ [WmiDataId(2),
+ read,
+ Description("Reserved.") : amended
+ ] uint32 Reserved;
+
+ [WmiDataId(3),
+ read,
+ MaxLen(31),
+ Description("Array of device hardware identifiers.") : amended,
+ WmiSizeIs("NumberDevices")
+ ] string DeviceId[];
+};
diff --git a/tests/projects/wdk/kmdf/msdsm/precomp.h b/tests/projects/wdk/kmdf/msdsm/precomp.h
new file mode 100644
index 000000000..bca8595db
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/precomp.h
@@ -0,0 +1,34 @@
+
+/*++
+
+Copyright (c) 2004 Microsoft Corporation
+
+Module Name:
+
+ precomp.h
+
+Abstract:
+
+ Precompiled header file for Microsoft Device Specific Module (DSM).
+
+Revision History:
+
+--*/
+
+#pragma once
+
+#define DEBUG_MAIN_SOURCE 1
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "dsm.h"
+#include "mpiodisk.h"
+#include "msdsm.h"
+#include "prototypes.h"
+#include "trace.h"
+#include "srbhelper.h"
+
+#include <ntstrsafe.h>
+#include <ntintsafe.h>
+
diff --git a/tests/projects/wdk/kmdf/msdsm/precompsrc.c b/tests/projects/wdk/kmdf/msdsm/precompsrc.c
new file mode 100644
index 000000000..5944cf515
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/precompsrc.c
@@ -0,0 +1 @@
+#include "precomp.h" \ No newline at end of file
diff --git a/tests/projects/wdk/kmdf/msdsm/prototypes.h b/tests/projects/wdk/kmdf/msdsm/prototypes.h
new file mode 100644
index 000000000..ebcf42029
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/prototypes.h
@@ -0,0 +1,1436 @@
+
+/*++
+
+Copyright (C) 2004 Microsoft Corporation
+
+Module Name:
+
+ prototypes.h
+
+Abstract:
+
+ Contains function prototypes for all the functions defined
+ by Microsoft Device Specific Module (DSM).
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+--*/
+
+#pragma warning (disable:4214) // bit field usage
+#pragma warning (disable:4200) // zero-sized array
+
+#ifndef _PROTOTYPES_H_
+#define _PROTOTYPES_H_
+
+#define DSM_VENDOR_ID_LEN 8
+#define DSM_PRODUCT_ID_LEN 16
+#define DSM_VENDPROD_ID_LEN 24
+
+//
+// In accordance with SPC-3 specs
+//
+#define SPC3_TARGET_PORT_GROUPS_HEADER_SIZE 4
+
+typedef struct _SPC3_CDB_REPORT_TARGET_PORT_GROUPS {
+ UCHAR OperationCode;
+ UCHAR ServiceAction : 5;
+ UCHAR Reserved1 : 3;
+ UCHAR Reserved2[4];
+ UCHAR AllocationLength[4];
+ UCHAR Reserved3;
+ UCHAR Control;
+} SPC3_CDB_REPORT_TARGET_PORT_GROUPS, *PSPC3_CDB_REPORT_TARGET_PORT_GROUPS;
+
+typedef struct _SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR {
+ UCHAR AsymmetricAccessState : 4;
+ UCHAR Reserved : 3;
+ UCHAR Preferred : 1;
+ UCHAR ActiveOptimizedSupported : 1;
+ UCHAR ActiveUnoptimizedSupported : 1;
+ UCHAR StandbySupported : 1;
+ UCHAR UnavailableSupported : 1;
+ UCHAR Reserved2 : 3;
+ UCHAR TransitioningSupported : 1;
+ USHORT TPG_Identifier;
+ UCHAR Reserved3;
+ UCHAR StatusCode;
+ UCHAR VendorUnique;
+ UCHAR NumberTargetPorts;
+ ULONG TargetPortIds[0];
+} SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR, *PSPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR;
+
+typedef struct _SPC3_CDB_SET_TARGET_PORT_GROUPS {
+ UCHAR OperationCode;
+ UCHAR ServiceAction : 5;
+ UCHAR Reserved1 : 3;
+ UCHAR Reserved2[4];
+ UCHAR ParameterListLength[4];
+ UCHAR Reserved3;
+ UCHAR Control;
+} SPC3_CDB_SET_TARGET_PORT_GROUPS, *PSPC3_CDB_SET_TARGET_PORT_GROUPS;
+
+typedef struct _SPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR {
+ UCHAR AsymmetricAccessState : 4;
+ UCHAR Reserved1 : 4;
+ UCHAR Reserved2;
+ USHORT TPG_Identifier;
+} SPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR, *PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR;
+
+typedef struct _SPC3_CONTROL_EXTENSION_MODE_PAGE {
+ UCHAR PageCode : 6;
+ UCHAR SubpageFormat : 1;
+ UCHAR ParametersSavable : 1;
+ UCHAR SubpageCode;
+ UCHAR PageLength[2];
+ UCHAR ImplicitALUAEnable : 1;
+ UCHAR ScsiPrecendence : 1;
+ UCHAR TimestampChangeable : 1;
+ UCHAR Reserved1 : 5;
+ UCHAR InitialPriority : 4;
+ UCHAR Reserved2 : 4;
+ UCHAR Reserved3[26];
+} SPC3_CONTROL_EXTENSION_MODE_PAGE, *PSPC3_CONTROL_EXTENSION_MODE_PAGE;
+
+#define SPC3_SCSIOP_REPORT_TARGET_PORT_GROUPS 0xA3
+#define SPC3_SCSIOP_SET_TARGET_PORT_GROUPS 0xA4
+#define SPC3_SERVICE_ACTION_TARGET_PORT_GROUPS 0xA
+#define SPC3_RESERVATION_ACTION_REPORT_CAPABILITIES 0x2
+
+#define SPC3_SCSI_ADSENSE_COMMANDS_CLEARED_BY_ANOTHER_INITIATOR 0x2F
+#define SPC3_SCSI_ADSENSE_LOGICAL_UNIT_COMMAND_FAILED 0x67
+
+#define SPC3_SCSI_SENSEQ_MODE_PARAMETERS_CHANGED 0x1
+#define SPC3_SCSI_SENSEQ_RESERVATIONS_PREEMPTED 0x3
+#define SPC3_SCSI_SENSEQ_RESERVATIONS_RELEASED 0x4
+#define SPC3_SCSI_SENSEQ_REGISTRATIONS_PREEMPTED 0x5
+#define SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_CHANGED 0x6
+#define SPC3_SCSI_SENSEQ_IMPLICIT_ASYMMETRIC_ACCESS_STATE_TRANSITION_FAILED 0x7
+#define SPC3_SCSI_SENSEQ_CAPACITY_DATA_HAS_CHANGED 0x9
+#define SPC3_SCSI_SENSEQ_ASYMMETRIC_ACCESS_STATE_TRANSITION 0xA
+#define SPC3_SCSI_SENSEQ_TARGET_PORT_IN_STANDBY_STATE 0xB
+#define SPC3_SCSI_SENSEQ_TARGET_PORT_IN_UNAVAILABLE_STATE 0xC
+
+#define SPC3_SCSI_SENSEQ_SET_TARGET_PORT_GROUPS_FAILED 0xA
+
+#define SPC3_SET_TARGET_PORT_GROUPS_TIMEOUT 10
+#define SPC3_REPORT_TARGET_PORT_GROUPS_TIMEOUT 10
+
+
+//
+// Function prototypes for functions intrface.c
+//
+
+DRIVER_INITIALIZE DriverEntry;
+DRIVER_UNLOAD DsmDriverUnload;
+
+NTSTATUS
+DsmInquire (
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDEVICE_OBJECT TargetDevice,
+ _In_ IN PDEVICE_OBJECT PortObject,
+ _In_ IN PSTORAGE_DEVICE_DESCRIPTOR Descriptor,
+ _In_ IN PSTORAGE_DEVICE_ID_DESCRIPTOR DeviceIdList,
+ _Out_ OUT PVOID *DsmIdentifier
+ );
+
+BOOLEAN
+DsmCompareDevices(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId1,
+ _In_ IN PVOID DsmId2
+ );
+
+NTSTATUS
+DsmGetControllerInfo(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN ULONG Flags,
+ _Inout_ IN OUT PCONTROLLER_INFO *ControllerInfo
+ );
+
+NTSTATUS
+DsmSetDeviceInfo(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDEVICE_OBJECT TargetObject,
+ _In_ IN PVOID DsmId,
+ _Inout_ IN OUT PVOID *PathId
+ );
+
+BOOLEAN
+DsmIsPathActive(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID PathId,
+ _In_ IN PVOID DsmId
+ );
+
+NTSTATUS
+DsmPathVerify(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PVOID PathId
+ );
+
+NTSTATUS
+DsmInvalidatePath(
+ _In_ IN PVOID DsmContext,
+ _In_ IN ULONG ErrorMask,
+ _In_ IN PVOID PathId,
+ _Inout_ IN OUT PVOID *NewPathId
+ );
+
+NTSTATUS
+DsmMoveDevice(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PVOID MPIOPath,
+ _In_ IN PVOID SuggestedPath,
+ _In_ IN ULONG Flags
+ );
+
+NTSTATUS
+DsmRemovePending(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId
+ );
+
+NTSTATUS
+DsmRemoveDevice(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PVOID PathId
+ );
+
+NTSTATUS
+DsmRemovePath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PVOID PathId
+ );
+
+NTSTATUS
+DsmSrbDeviceControl(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ );
+
+PVOID
+DsmLBGetPath(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PDSM_IDS DsmList,
+ _In_ IN PVOID CurrentPath,
+ _Out_ OUT NTSTATUS *Status
+ );
+
+ULONG
+DsmInterpretError(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _Inout_ IN OUT NTSTATUS *Status,
+ _Out_ OUT PBOOLEAN Retry,
+ _Out_ OUT PLONG RetryInterval,
+ ...
+ );
+
+NTSTATUS
+DsmUnload(
+ _In_ IN PVOID DsmContext
+ );
+
+VOID
+DsmSetCompletion(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _Inout_ IN OUT PDSM_COMPLETION_INFO DsmCompletion
+ );
+
+_Success_(return == DSM_PATH_SET)
+ULONG
+DsmCategorizeRequest(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PVOID CurrentPath,
+ _Outptr_result_maybenull_ OUT PVOID *PathId,
+ _Out_ OUT NTSTATUS *Status
+ );
+
+NTSTATUS
+DsmBroadcastRequest(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ );
+
+BOOLEAN
+DsmIsAddressTypeSupported(
+ _In_ IN PVOID DsmContext,
+ _In_ IN ULONG AddressType
+ );
+
+NTSTATUS
+DsmDeviceNotUsed(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PVOID DsmId
+ );
+
+
+//
+// Function prototypes for functions in dsmmain.c
+//
+
+VOID
+DsmpFreeDSMResources(
+ _In_ IN PDSM_CONTEXT DsmContext
+ );
+
+PDSM_GROUP_ENTRY
+DsmpFindDevice(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN BOOLEAN AcquireDSMLockExclusive
+ );
+
+PDSM_GROUP_ENTRY
+DsmpBuildGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ );
+
+NTSTATUS
+DsmpParseTargetPortGroupsInformation(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_reads_bytes_(TargetPortGroupsInfoLength) IN PUCHAR TargetPortGroupsInfo,
+ _In_ IN ULONG TargetPortGroupsInfoLength
+ );
+
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpFindTargetPortGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_reads_bytes_(TPGs_BufferLength) IN PUCHAR TargetPortGroupsDescriptor,
+ _In_ IN ULONG TPGs_BufferLength
+ );
+
+_Success_(return!=0)
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpUpdateTargetPortGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_reads_bytes_(TPGs_BufferLength) IN PUCHAR TargetPortGroupsDescriptor,
+ _In_ IN ULONG TPGs_BufferLength,
+ _Out_ OUT PULONG DescriptorSize
+ );
+
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpBuildTargetPortGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_reads_bytes_(TPGs_BufferLength) IN PUCHAR TargetPortGroupsDescriptor,
+ _In_ IN ULONG TPGs_BufferLength,
+ _Out_ OUT PULONG DescriptorSize
+ );
+
+PDSM_TARGET_PORT_LIST_ENTRY
+DsmpFindTargetPortListEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN ULONG RelativeTargetPortId
+ );
+
+PDSM_TARGET_PORT_LIST_ENTRY
+DsmpBuildTargetPortListEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN ULONG RelativeTargetPortId
+ );
+
+PDSM_TARGET_PORT_GROUP_ENTRY
+DsmpFindTargetPortGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PUSHORT TargetPortGroupId
+ );
+
+PDSM_TARGET_PORT_LIST_ENTRY
+DsmpFindTargetPort(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN PULONG TargetPortGroupId
+ );
+
+NTSTATUS
+DsmpAddDeviceEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ );
+
+PDSM_CONTROLLER_LIST_ENTRY
+DsmpFindControllerEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDEVICE_OBJECT PortObject,
+ _In_ IN PSCSI_ADDRESS ScsiAddress,
+ _In_reads_(ControllerSerialNumberLength) IN PSTR ControllerSerialNumber,
+ _In_ IN SIZE_T ControllerSerialNumberLength,
+ _In_ IN STORAGE_IDENTIFIER_CODE_SET CodeSet,
+ _In_ IN BOOLEAN AcquireLock
+ );
+
+_Ret_maybenull_
+_Must_inspect_result_
+_When_(return != NULL, __drv_allocatesMem(Mem))
+PDSM_CONTROLLER_LIST_ENTRY
+DsmpBuildControllerEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_opt_ IN PDEVICE_OBJECT DeviceObject,
+ _In_ IN PDEVICE_OBJECT PortObject,
+ _In_ IN PSCSI_ADDRESS ScsiAddress,
+ _In_ IN PSTR ControllerSerialNumber,
+ _In_ IN STORAGE_IDENTIFIER_CODE_SET CodeSet,
+ _In_ IN BOOLEAN AcquireLock
+ );
+
+VOID
+DsmpFreeControllerEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ __drv_freesMem(Mem) IN PDSM_CONTROLLER_LIST_ENTRY ControllerEntry
+ );
+
+BOOLEAN
+DsmpIsDeviceBelongsToController(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PDSM_CONTROLLER_LIST_ENTRY ControllerEntry
+ );
+
+PDSM_DEVICE_INFO
+DsmpFindDevInfoFromGroupAndFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_FAILOVER_GROUP FOGroup
+ );
+
+PDSM_FAILOVER_GROUP
+DsmpFindFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PVOID PathId
+ );
+
+PDSM_FAILOVER_GROUP
+DsmpBuildFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PVOID *PathId
+ );
+
+NTSTATUS
+DsmpUpdateFOGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_FAILOVER_GROUP FailGroup,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ );
+
+VOID
+DsmpRemoveDeviceFailGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_FAILOVER_GROUP FailGroup,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN BOOLEAN AcquireDSMLockExclusive
+ );
+
+ULONG
+DsmpRemoveDeviceEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ );
+
+VOID
+DsmpRemoveDeviceFromTargetPortList(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ );
+
+PDSM_FAILOVER_GROUP
+DsmpSetNewPath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDevice
+ );
+
+PDSM_FAILOVER_GROUP
+DsmpSetNewPathUsingGroup(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY Group
+ );
+
+VOID
+DsmpRemoveZombieGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY ZombieGroup
+ );
+
+NTSTATUS
+DsmpUpdateTargetPortGroupDevicesStates(
+ _In_ IN PDSM_TARGET_PORT_GROUP_ENTRY TargetPortGroup,
+ _In_ IN DSM_DEVICE_STATE NewState
+ );
+
+VOID
+DsmpIncrementCounters(
+ _In_ PDSM_FAILOVER_GROUP FailGroup,
+ _In_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+BOOLEAN
+DsmpDecrementCounters(
+ _In_ PDSM_FAILOVER_GROUP FailGroup,
+ _In_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+PDSM_FAILOVER_GROUP
+DsmpGetPath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmList,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+PVOID
+DsmpGetPathIdFromPassThroughPath(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmList,
+ _In_ IN PIRP Irp,
+ _Inout_ IN OUT NTSTATUS *Status
+ );
+
+VOID
+DsmpRemoveGroupEntry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_GROUP_ENTRY GroupEntry,
+ _In_ IN BOOLEAN AcquireDSMLockExclusive
+ );
+
+BOOLEAN
+DsmpMpioPassThroughPathCommand(
+ _In_ IN PIRP Irp
+ );
+
+BOOLEAN
+DsmpReservationCommand(
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb
+ );
+
+VOID
+DsmpRequestComplete(
+ _In_ IN PVOID DsmId,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PVOID DsmContext
+ );
+
+NTSTATUS
+DsmpRegisterPersistentReservationKeys(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN BOOLEAN Register
+ );
+
+
+BOOLEAN
+DsmpShouldRetryPassThroughRequest(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ );
+
+BOOLEAN
+DsmpShouldRetryPersistentReserveCommand(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ );
+
+BOOLEAN
+DsmpShouldRetryTPGRequest(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ );
+
+BOOLEAN
+DsmpIsDeviceRemoved(
+ _In_ IN PVOID SenseData,
+ _In_ IN UCHAR SenseDataSize
+ );
+
+PDSM_DEVICE_INFO
+DsmpGetActivePathToBeUsed(
+ _In_ PDSM_GROUP_ENTRY Group,
+ _In_ BOOLEAN Symmetric,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+PDSM_DEVICE_INFO
+DsmpGetAnyActivePath(
+ _In_ PDSM_GROUP_ENTRY Group,
+ _In_ BOOLEAN Exception,
+ _In_opt_ PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+PDSM_DEVICE_INFO
+DsmpFindStandbyPathToActivate(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+PDSM_DEVICE_INFO
+DsmpFindStandbyPathToActivateALUA(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PBOOLEAN SendTPG,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+PDSM_DEVICE_INFO
+DsmpFindStandbyPathInAlternateTpgALUA(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetLBForDsmPolicyAdjustment(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ );
+
+NTSTATUS
+DsmpSetLBForVidPidPolicyAdjustment(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PWSTR TargetHardwareId,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ );
+
+NTSTATUS
+DsmpSetNewDefaultLBPolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_opt_ IN PDSM_DEVICE_INFO NewDeviceInfo,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetLBForPathArrival(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO NewDeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetLBForPathArrivalALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO NewDeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetLBForPathRemoval(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO RemovedDeviceInfo,
+ _In_opt_ IN OPTIONAL PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetLBForPathRemovalALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO RemovedDeviceInfo,
+ _In_opt_ IN OPTIONAL PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetLBForPathFailing(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDeviceInfo,
+ _In_ IN BOOLEAN MarkDevInfoFailed,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetLBForPathFailingALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDeviceInfo,
+ _In_ IN BOOLEAN MarkDevInfoFailed,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+NTSTATUS
+DsmpSetPathForIoRetryALUA(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO FailingDeviceInfo,
+ _In_ IN BOOLEAN TPGException,
+ _In_ IN BOOLEAN DeviceInfoException
+ );
+
+PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY
+DsmpFindFailPathDevInfoEntry(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO FailingDevInfo
+ );
+
+PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY
+DsmpBuildFailPathDevInfoEntry(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_DEVICE_INFO FailingDevInfo,
+ _In_ IN PDSM_DEVICE_INFO AlternateDevInfo
+ );
+
+IO_COMPLETION_ROUTINE DsmpPhase1ProcessPathFailingALUA;
+
+NTSTATUS
+DsmpRemoveFailPathDevInfoEntry(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN PDSM_FAIL_PATH_PROCESSING_LIST_ENTRY FailPathDevInfoEntry
+ );
+
+IO_COMPLETION_ROUTINE DsmpPhase2ProcessPathFailingALUA;
+
+NTSTATUS
+DsmpPersistentReserveOut(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ );
+
+__inline
+BOOLEAN
+DsmpIsPersistentReservationKeyZeroKey(
+ _In_ ULONG KeyLength,
+ _In_reads_bytes_(KeyLength) PUCHAR Key
+ )
+{
+ BOOLEAN zeroKey = FALSE;
+
+ NT_ASSERT(KeyLength == 8);
+
+ if ((KeyLength) == 8 &&
+ (Key[0] == 0 && Key[1] == 0 && Key[2] == 0 && Key[3] == 0 &&
+ Key[4] == 0 && Key[5] == 0 && Key[6] == 0 && Key[7] == 0)) {
+
+ zeroKey = TRUE;
+ }
+
+ return zeroKey;
+}
+
+
+NTSTATUS
+DsmpPersistentReserveIn(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN PSCSI_REQUEST_BLOCK Srb,
+ _In_ IN PKEVENT Event
+ );
+
+IO_COMPLETION_ROUTINE DsmpPersistentReserveCompletion;
+
+
+//
+// Function prototypes for functions in utils.c
+//
+
+_Success_(return != NULL)
+__drv_allocatesMem(Mem)
+_When_(((PoolType&0x1))!=0, _IRQL_requires_max_(APC_LEVEL))
+_When_(((PoolType&0x1))==0, _IRQL_requires_max_(DISPATCH_LEVEL))
+_When_(((PoolType&0x2))!=0,
+ __drv_reportError("Must succeed pool allocations are forbidden. "
+ "Allocation failures cause a system crash"))
+_When_(((PoolType&(0x2|POOL_RAISE_IF_ALLOCATION_FAILURE)))==0,
+ _Post_maybenull_ _Must_inspect_result_)
+_When_(((PoolType&(0x2|POOL_RAISE_IF_ALLOCATION_FAILURE)))!=0,
+ _Post_notnull_)
+_When_((PoolType&NonPagedPoolMustSucceed)!=0,
+ __drv_reportError("Must succeed pool allocations are forbidden. "
+ "Allocation failures cause a system crash"))
+_Post_writable_byte_size_(NumberOfBytes)
+PVOID
+DsmpAllocatePool(
+ _In_ _Strict_type_match_ IN POOL_TYPE PoolType,
+ _In_ IN SIZE_T NumberOfBytes,
+ _In_ IN ULONG Tag
+ );
+
+_Success_(return != NULL)
+_Post_maybenull_
+_Must_inspect_result_
+__drv_allocatesMem(Mem)
+_Post_writable_byte_size_(*BytesAllocated)
+_When_(((PoolType&0x1))!=0, _IRQL_requires_max_(APC_LEVEL))
+_When_(((PoolType&0x1))==0, _IRQL_requires_max_(DISPATCH_LEVEL))
+_When_((PoolType&NonPagedPoolMustSucceed)!=0,
+ __drv_reportError("Must succeed pool allocations are forbidden. "
+ "Allocation failures cause a system crash"))
+PVOID
+DsmpAllocateAlignedPool(
+ _In_ IN POOL_TYPE PoolType,
+ _In_ IN SIZE_T NumberOfBytes,
+ _In_ IN ULONG AlignmentMask,
+ _In_ IN ULONG Tag,
+ _Out_ OUT SIZE_T *BytesAllocated
+ );
+
+_IRQL_requires_max_(DISPATCH_LEVEL)
+VOID
+DsmpFreePool(
+ _In_opt_ __drv_freesMem(Mem) IN PVOID Block
+ );
+
+NTSTATUS
+DsmpGetStatsGatheringChoice(
+ _In_ IN PDSM_CONTEXT Context,
+ _Out_ OUT PULONG StatsGatherChoice
+ );
+
+NTSTATUS
+DsmpSetStatsGatheringChoice(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN ULONG StatsGatherChoice
+ );
+
+
+NTSTATUS
+DsmpGetDeviceList(
+ _In_ IN PDSM_CONTEXT Context
+ );
+
+_Success_(return==0)
+NTSTATUS
+DsmpGetStandardInquiryData(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _Out_ OUT PINQUIRYDATA InquiryData
+ );
+
+BOOLEAN
+DsmpCheckScsiCompliance(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _In_ IN PINQUIRYDATA InquiryData,
+ _In_ IN PSTORAGE_DEVICE_DESCRIPTOR Descriptor,
+ _In_ IN PSTORAGE_DEVICE_ID_DESCRIPTOR DeviceIdList
+ );
+
+BOOLEAN
+DsmpDeviceSupported(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PCSTR VendorId,
+ _In_ IN PCSTR ProductId
+ );
+
+BOOLEAN
+DsmpFindSupportedDevice(
+ _In_ IN PUNICODE_STRING DeviceName,
+ _In_ IN PUNICODE_STRING SupportedDevices
+ );
+
+_Success_(return!=0)
+PVOID
+DsmpParseDeviceID (
+ _In_ IN PSTORAGE_DEVICE_ID_DESCRIPTOR DeviceID,
+ _In_ IN DSM_DEVID_TYPE DeviceIdType,
+ _In_opt_ IN PULONG IdNumber,
+ _Out_opt_ PSTORAGE_IDENTIFIER_CODE_SET CodeSet,
+ _In_ IN BOOLEAN Legacy
+ );
+
+PUCHAR
+DsmpBinaryToAscii(
+ _In_reads_(Length) IN PUCHAR HexBuffer,
+ _In_ IN ULONG Length,
+ _Inout_ IN OUT PULONG UpdateLength,
+ _In_ IN BOOLEAN Legacy
+ );
+
+PSTR
+DsmpGetSerialNumber(
+ _In_ IN PDEVICE_OBJECT DeviceObject
+ );
+
+
+NTSTATUS
+DsmpDisableImplicitStateTransition(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _Out_ OUT PBOOLEAN DisableImplicit
+ );
+
+PWSTR
+DsmpBuildHardwareId(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ );
+
+PWSTR
+DsmpBuildDeviceNameLegacyPage0x80(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ );
+
+
+PWSTR
+DsmpBuildDeviceName(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_reads_(SerialNumberLength) IN PSTR SerialNumber,
+ _In_ IN SIZE_T SerialNumberLength
+ );
+
+NTSTATUS
+DsmpApplyDeviceNameCorrection(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_reads_(DeviceNameLegacyLen) PWSTR DeviceNameLegacy,
+ _In_ IN SIZE_T DeviceNameLegacyLen,
+ _In_reads_(DeviceNameLen) PWSTR DeviceName,
+ _In_ IN SIZE_T DeviceNameLen
+ );
+
+NTSTATUS
+DsmpQueryDeviceLBPolicyFromRegistry(
+ _In_ PDSM_DEVICE_INFO DeviceInfo,
+ _In_ PWSTR RegistryKeyName,
+ _Inout_ PDSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Inout_ PULONGLONG PreferredPath,
+ _Inout_ PUCHAR ExplicitlySet
+ );
+
+NTSTATUS
+DsmpQueryTargetLBPolicyFromRegistry(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _Out_ OUT PDSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Out_ OUT PULONGLONG PreferredPath
+ );
+
+NTSTATUS
+DsmpQueryDsmLBPolicyFromRegistry(
+ _Out_ OUT PDSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Out_ OUT PULONGLONG PreferredPath
+ );
+
+NTSTATUS
+DsmpSetDsmLBPolicyInRegistry(
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ );
+
+NTSTATUS
+DsmpSetVidPidLBPolicyInRegistry(
+ _In_ IN PWSTR TargetHardwareId,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ );
+
+NTSTATUS
+DsmpOpenLoadBalanceSettingsKey(
+ _In_ IN ACCESS_MASK AccessMask,
+ _Out_ OUT PHANDLE LoadBalanceSettingsKey
+ );
+
+NTSTATUS
+DsmpOpenTargetsLoadBalanceSettingKey(
+ _In_ IN ACCESS_MASK AccessMask,
+ _Out_ OUT PHANDLE TargetsLoadBalanceSettingKey
+ );
+
+NTSTATUS
+DsmpOpenDsmServicesParametersKey(
+ _In_ IN ACCESS_MASK AccessMask,
+ _Out_ OUT PHANDLE ParametersSettingsKey
+ );
+
+IO_COMPLETION_ROUTINE DsmpReportTargetPortGroupsSyncCompletion;
+
+_Success_(return==0)
+NTSTATUS
+DsmpReportTargetPortGroups(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Outptr_result_buffer_maybenull_(*TargetPortGroupsInfoLength) PUCHAR *TargetPortGroupsInfo,
+ _Out_ PULONG TargetPortGroupsInfoLength
+ );
+
+NTSTATUS
+DsmpReportTargetPortGroupsAsync(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PIO_COMPLETION_ROUTINE CompletionRoutine,
+ _Inout_ __drv_aliasesMem IN PDSM_TPG_COMPLETION_CONTEXT CompletionContext,
+ _In_ IN ULONG TargetPortGroupsInfoLength,
+ _Inout_ __drv_aliasesMem IN OUT PUCHAR TargetPortGroupsInfo
+ );
+
+NTSTATUS
+DsmpQueryLBPolicyForDevice(
+ _In_ IN PWSTR RegistryKeyName,
+ _In_ IN ULONGLONG PathId,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Out_ OUT PULONG PrimaryPath,
+ _Out_ OUT PULONG OptimizedPath,
+ _Out_ OUT PULONG PathWeight
+ );
+
+VOID
+DsmpGetDSMPathKeyName(
+ _In_ ULONGLONG DSMPathId,
+ _Out_writes_(DsmPathKeyNameSize) PWCHAR DsmPathKeyName,
+ _In_ ULONG DsmPathKeyNameSize
+ );
+
+UCHAR
+DsmpGetAsciiForBinary(
+ _In_ UCHAR BinaryChar
+ );
+
+NTSTATUS
+DsmpGetDeviceIdList (
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _Out_ OUT PSTORAGE_DESCRIPTOR_HEADER *Descriptor
+ );
+
+NTSTATUS
+DsmpSetTargetPortGroups(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _In_reads_bytes_(TargetPortGroupsInfoLength) IN PUCHAR TargetPortGroupsInfo,
+ _In_ IN ULONG TargetPortGroupsInfoLength
+ );
+
+NTSTATUS
+DsmpSetTargetPortGroupsAsync(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PIO_COMPLETION_ROUTINE CompletionRoutine,
+ _In_ __drv_aliasesMem IN PDSM_TPG_COMPLETION_CONTEXT CompletionContext,
+ _In_ IN ULONG TargetPortGroupsInfoLength,
+ _In_ __drv_aliasesMem IN PUCHAR TargetPortGroupsInfo
+ );
+
+PDSM_LOAD_BALANCE_POLICY_SETTINGS
+DsmpCopyLoadBalancePolicies(
+ _In_ IN PDSM_GROUP_ENTRY GroupEntry,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN PVOID SupportedLBPolicies
+ );
+
+NTSTATUS
+DsmpPersistLBSettings(
+ _In_ IN PDSM_LOAD_BALANCE_POLICY_SETTINGS LoadBalanceSettings
+ );
+
+NTSTATUS
+DsmpSetDeviceALUAState(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN DSM_DEVICE_STATE DevState
+ );
+
+NTSTATUS
+DsmpGetDeviceALUAState(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_opt_ IN PDSM_DEVICE_STATE DevState
+ );
+
+NTSTATUS
+DsmpAdjustDeviceStatesALUA(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_opt_ IN PDSM_DEVICE_INFO PreferredActiveDeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ );
+
+PDSM_WORKITEM
+DsmpAllocateWorkItem(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _In_ IN PVOID Context
+ );
+
+VOID
+DsmpFreeWorkItem(
+ _In_ IN PDSM_WORKITEM DsmWorkItem
+ );
+
+VOID
+DsmpFreeZombieGroupList(
+ _In_ IN PDSM_FAILOVER_GROUP FailGroup
+ );
+
+NTSTATUS
+DsmpRegCopyTree(
+ _In_ IN HANDLE SourceKey,
+ _In_ IN HANDLE DestKey
+ );
+
+NTSTATUS
+DsmpRegDeleteTree(
+ _In_ IN HANDLE KeyRoot
+ );
+
+#if defined (_WIN64)
+VOID
+DsmpPassThroughPathTranslate32To64(
+ _In_ IN PMPIO_PASS_THROUGH_PATH32 MpioPassThroughPath32,
+ _Inout_ IN OUT PMPIO_PASS_THROUGH_PATH MpioPassThroughPath64
+ );
+
+VOID
+DsmpPassThroughPathTranslate64To32(
+ _In_ IN PMPIO_PASS_THROUGH_PATH MpioPassThroughPath64,
+ _Inout_ IN OUT PMPIO_PASS_THROUGH_PATH32 MpioPassThroughPath32
+ );
+#endif
+
+NTSTATUS
+DsmpGetMaxPRRetryTime(
+ _In_ IN PDSM_CONTEXT Context,
+ _Out_ OUT PULONG RetryTime
+ );
+
+NTSTATUS
+DsmpQueryCacheInformationFromRegistry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _Out_ OUT PBOOLEAN UseCacheForLeastBlocks,
+ _Out_ OUT PULONGLONG CacheSizeForLeastBlocks
+ );
+
+BOOLEAN
+DsmpConvertSharedSpinLockToExclusive(
+ _Inout_ _Requires_lock_held_(*_Curr_) PEX_SPIN_LOCK SpinLock
+ );
+
+
+//
+// Function prototypes for functions in wmi.c
+//
+
+VOID
+DsmpDsmWmiInitialize(
+ _In_ IN PDSM_WMILIB_CONTEXT WmiGlobalInfo,
+ _In_ IN PUNICODE_STRING RegistryPath
+ );
+
+NTSTATUS
+DsmGlobalQueryData(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG InstanceCount,
+ _Inout_ IN OUT PULONG InstanceLengthArray,
+ _In_ IN ULONG BufferAvail,
+ _Out_writes_to_(BufferAvail, *DataLength) OUT PUCHAR Buffer,
+ _Out_ OUT PULONG DataLength,
+ ...
+ );
+
+NTSTATUS
+DsmGlobalSetData(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG BufferAvail,
+ _In_reads_bytes_(BufferAvail) IN PUCHAR Buffer,
+ ...
+ );
+
+VOID
+DsmpWmiInitialize(
+ _In_ IN PDSM_WMILIB_CONTEXT WmiInfo,
+ _In_ IN PUNICODE_STRING RegistryPath
+ );
+
+NTSTATUS
+DsmQueryData(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG InstanceCount,
+ _Inout_ IN OUT PULONG InstanceLengthArray,
+ _In_ IN ULONG BufferAvail,
+ _When_(GuidIndex == 0 || GuidIndex == 7, _Pre_notnull_ _Const_)
+ _When_(!(GuidIndex == 0 || GuidIndex == 7), _Out_writes_to_(BufferAvail, *DataLength))
+ OUT PUCHAR Buffer,
+ _Out_ OUT PULONG DataLength,
+ ...
+ );
+
+NTSTATUS
+DsmpQueryLoadBalancePolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN ULONG InBufferSize,
+ _In_ IN PULONG OutBufferSize,
+ _Out_writes_bytes_(*OutBufferSize) OUT PVOID Buffer
+ );
+
+NTSTATUS
+DsmpQuerySupportedLBPolicies(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG BufferAvail,
+ _In_ IN ULONG DsmWmiVersion,
+ _Out_ OUT PULONG OutBufferSize,
+ _Out_writes_to_(BufferAvail, *OutBufferSize) OUT PUCHAR Buffer
+ );
+
+NTSTATUS
+DsmExecuteMethod(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG MethodId,
+ _In_ IN ULONG InBufferSize,
+ _In_ IN PULONG OutBufferSize,
+ _Inout_ IN OUT PUCHAR Buffer,
+ ...
+ );
+
+NTSTATUS
+DsmpClearLoadBalancePolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds
+ );
+
+NTSTATUS
+DsmpSetLoadBalancePolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN ULONG InBufferSize,
+ _In_ IN PULONG OutBufferSize,
+ _In_ IN PVOID Buffer
+ );
+
+NTSTATUS
+DsmpValidateSetLBPolicyInput(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN PVOID SetLoadBalancePolicyIN,
+ _In_ IN ULONG InBufferSize
+ );
+
+VOID
+DsmpSaveDeviceState(
+ _In_ IN PVOID SupportedLBPolicies,
+ _In_ IN ULONG DsmWmiVersion
+ );
+
+VOID
+DsmpRestorePreviousDeviceState(
+ _In_ IN PVOID SupportedLBPolicies,
+ _In_ IN ULONG DsmWmiVersion
+ );
+
+VOID
+DsmpUpdateDesiredStateAndWeight(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN PVOID SupportedLBPolicies
+ );
+
+NTSTATUS
+DsmpQueryDevicePerf(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ PDSM_IDS DsmIds,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ );
+
+NTSTATUS
+DsmpClearPerfCounters(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds
+ );
+
+NTSTATUS
+DsmpQuerySupportedDevicesList(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ );
+
+NTSTATUS
+DsmpQueryTargetsDefaultPolicy(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ );
+
+NTSTATUS
+DsmpQueryDsmDefaultPolicy(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ );
+
+
+//
+// Function prototypes for functions in debug.c
+//
+
+VOID
+DsmpDebugPrint(
+ _In_ ULONG DebugPrintLevel,
+ _In_ PCCHAR DebugMessage,
+ ...
+ );
+
+//
+// SRB Helpers not found in srbhelper.h
+//
+_Success_(return != 0)
+__drv_allocatesMem(mem)
+_When_(((PoolType&0x1))!=0, _IRQL_requires_max_(APC_LEVEL))
+_When_(((PoolType&0x1))==0, _IRQL_requires_max_(DISPATCH_LEVEL))
+_When_(((PoolType&0x2))!=0,
+ __drv_reportError("Must succeed pool allocations are forbidden. "
+ "Allocation failures cause a system crash"))
+_When_(((PoolType&(0x2|POOL_RAISE_IF_ALLOCATION_FAILURE)))==0,
+ _Post_maybenull_ _Must_inspect_result_)
+_When_(((PoolType&(0x2|POOL_RAISE_IF_ALLOCATION_FAILURE)))!=0,
+ _Post_notnull_ )
+__inline PSTORAGE_REQUEST_BLOCK_HEADER
+SrbAllocateCopy(
+ _Inout_ PVOID Srb,
+ _In_ _Strict_type_match_ POOL_TYPE PoolType,
+ _In_ ULONG Tag
+ )
+/*
+
+Description:
+ This function returns an allocated copy of the given SRB. The memory is
+ allocated using DsmpAllocatePool().
+
+ ***It is up to the caller to free the memory returned by this function.***
+
+Arguments:
+ Srb - A pointer to either a STORAGE_REQUEST_BLOCK or a SCSI_REQUEST_BLOCK.
+ PoolType - The pool type to use. See documentation for ExAllocatePoolWithTag().
+ Tag - The allocation tag to use. See documentation for ExAllocatePoolWithTag().
+
+Returns:
+ NULL, if the copy could not be allocated; or
+ A pointer to either a STORAGE_REQUEST_BLOCK or a SCSI_REQUEST_BLOCK that is
+ direct copy of the given SRB.
+
+*/
+{
+ PSTORAGE_REQUEST_BLOCK srb = (PSTORAGE_REQUEST_BLOCK)Srb;
+ PSTORAGE_REQUEST_BLOCK_HEADER srbCopy = NULL;
+ ULONG allocationSize = 0;
+
+ if (srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK)
+ {
+ allocationSize = srb->SrbLength;
+ NT_ASSERT(allocationSize >= (sizeof(STORAGE_REQUEST_BLOCK) + sizeof(STOR_ADDR_BTL8)));
+ }
+ else
+ {
+ allocationSize = SCSI_REQUEST_BLOCK_SIZE;
+ NT_ASSERT(allocationSize >= sizeof(SCSI_REQUEST_BLOCK));
+ }
+
+ #pragma warning(suppress: 28160 28118) // False-positive; PoolType is simply passed through
+ srbCopy = (PSTORAGE_REQUEST_BLOCK_HEADER)DsmpAllocatePool(PoolType, allocationSize, Tag);
+ if (srbCopy != NULL)
+ {
+ RtlCopyMemory(srbCopy, Srb, allocationSize);
+ }
+
+ return srbCopy;
+}
+
+__inline
+BOOLEAN DsmpIsMPIOPassThroughEx(
+ ULONG ControlCode
+ )
+//
+// Returns TRUE if the given passthrough IOCTL's control code indicates it is
+// an "extended" passthrough. Returns FALSE otherwise.
+//
+{
+ if (ControlCode == IOCTL_MPIO_PASS_THROUGH_PATH_EX ||
+ ControlCode == IOCTL_MPIO_PASS_THROUGH_PATH_DIRECT_EX) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+__inline
+UCHAR DsmpNtStatusToSrbStatus(
+ _In_ NTSTATUS Status
+ )
+/*++
+
+Routine Description:
+
+ Translate an NT status value into a SCSI Srb status code.
+
+Arguments:
+
+ Status - Supplies the NT status code to translate.
+
+Return Value:
+
+ SRB status code.
+
+--*/
+{
+ switch (Status) {
+
+ case STATUS_DEVICE_BUSY:
+ return SRB_STATUS_BUSY;
+
+ case STATUS_INVALID_DEVICE_REQUEST:
+ return SRB_STATUS_BAD_FUNCTION;
+
+ case STATUS_INSUFFICIENT_RESOURCES:
+ return SRB_STATUS_INTERNAL_ERROR;
+
+ case STATUS_INVALID_PARAMETER:
+ return SRB_STATUS_INVALID_REQUEST;
+
+ default:
+ if (NT_SUCCESS (Status)) {
+ return SRB_STATUS_SUCCESS;
+ } else {
+ return SRB_STATUS_ERROR;
+ }
+ }
+}
+
+
+#endif // _PROTOTYPES_H_
+
diff --git a/tests/projects/wdk/kmdf/msdsm/trace.h b/tests/projects/wdk/kmdf/msdsm/trace.h
new file mode 100644
index 000000000..b9449cc8f
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/trace.h
@@ -0,0 +1,35 @@
+
+/*++
+
+Copyright (C) 2004 Microsoft Corporation
+
+Module Name:
+
+ trace.h
+
+Abstract:
+
+ Header file included by the Microsoft Device Specific Module (DSM).
+
+ This file contains Windows tracing related defines.
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+--*/
+
+//
+// Set component ID for DbgPrintEx calls
+//
+#define DEBUG_COMP_ID DPFLTR_MSDSM_ID
+
+//
+// Include header file and setup GUID for tracing
+//
+#include <storswtr.h>
+#define WPP_GUID_MSDSM (DEDADFF5, F99F, 4600, B8C9, 2D4D9B806B5B)
+#define WPP_CONTROL_GUIDS WPP_CONTROL_GUIDS_NORMAL_FLAGS(WPP_GUID_MSDSM)
+
diff --git a/tests/projects/wdk/kmdf/msdsm/utils.c b/tests/projects/wdk/kmdf/msdsm/utils.c
new file mode 100644
index 000000000..91d8f44ce
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/utils.c
@@ -0,0 +1,7946 @@
+
+/*++
+
+Copyright (C) 2004-2010 Microsoft Corporation
+
+Module Name:
+
+ utils.c
+
+Abstract:
+
+ This driver is the Microsoft Device Specific Module (DSM).
+ It exports behaviours that mpio.sys will use to determine how to
+ multipath SPC-3 compliant devices.
+
+ This file contains utility routines.
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+--*/
+
+#include "precomp.h"
+
+#ifdef DEBUG_USE_WPP
+#include "utils.tmh"
+#endif
+
+#pragma warning (disable:4305)
+
+extern BOOLEAN DoAssert;
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(PAGE, DsmpBuildDeviceNameLegacyPage0x80)
+ #pragma alloc_text(PAGE, DsmpBuildDeviceName)
+ #pragma alloc_text(PAGE, DsmpApplyDeviceNameCorrection)
+ #pragma alloc_text(PAGE, DsmpOpenLoadBalanceSettingsKey)
+ #pragma alloc_text(PAGE, DsmpQueryLBPolicyForDevice)
+ #pragma alloc_text(PAGE, DsmpOpenTargetsLoadBalanceSettingKey)
+ #pragma alloc_text(PAGE, DsmpOpenDsmServicesParametersKey)
+#endif
+
+_Success_(return != NULL)
+__drv_allocatesMem(Mem)
+_When_(((PoolType&0x1))!=0, _IRQL_requires_max_(APC_LEVEL))
+_When_(((PoolType&0x1))==0, _IRQL_requires_max_(DISPATCH_LEVEL))
+_When_(((PoolType&0x2))!=0,
+ __drv_reportError("Must succeed pool allocations are forbidden. "
+ "Allocation failures cause a system crash"))
+_When_(((PoolType&(0x2|POOL_RAISE_IF_ALLOCATION_FAILURE)))==0,
+ _Post_maybenull_ _Must_inspect_result_)
+_When_(((PoolType&(0x2|POOL_RAISE_IF_ALLOCATION_FAILURE)))!=0,
+ _Post_notnull_ )
+_When_((PoolType&NonPagedPoolMustSucceed)!=0,
+ __drv_reportError("Must succeed pool allocations are forbidden. "
+ "Allocation failures cause a system crash"))
+_Post_writable_byte_size_(NumberOfBytes)
+PVOID
+DsmpAllocatePool(
+ _In_ _Strict_type_match_ IN POOL_TYPE PoolType,
+ _In_ IN SIZE_T NumberOfBytes,
+ _In_ IN ULONG Tag
+ )
+/*+++
+
+Routine Description :
+
+ Allocates memory from the specified pool using the given tag.
+ If the allocation is successful, the entire buffer will be zeroed.
+
+Arguements:
+
+ PoolType - Pool to allocate from (NonPaged, Paged, etc)
+ NumberOfBytes - Size of the buffer to allocate
+ Tag - Tag (DSM_TAG_XXX) to be used for this allocation.
+ These tags are defined in msdsm.h
+
+Return Value:
+
+ Pointer to the buffer if allocation is successful
+ NULL otherwise
+
+--*/
+{
+ PVOID Block = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpAllocatePool (Tag %u): Entering function.\n",
+ Tag));
+
+ #pragma warning(suppress: 28118) // False-positive; PoolType is simply passed through
+ Block = ExAllocatePoolWithTag(PoolType, NumberOfBytes, Tag);
+ if (Block) {
+ RtlZeroMemory(Block, NumberOfBytes);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpAllocatePool (Tag %u): Exiting function with allocated block %p.\n",
+ Tag,
+ Block));
+
+ return Block;
+}
+
+
+_Success_(return != NULL)
+_Post_maybenull_
+_Must_inspect_result_
+__drv_allocatesMem(Mem)
+_Post_writable_byte_size_(*BytesAllocated)
+_When_(((PoolType&0x1))!=0, _IRQL_requires_max_(APC_LEVEL))
+_When_(((PoolType&0x1))==0, _IRQL_requires_max_(DISPATCH_LEVEL))
+_When_((PoolType&NonPagedPoolMustSucceed)!=0,
+ __drv_reportError("Must succeed pool allocations are forbidden. "
+ "Allocation failures cause a system crash"))
+PVOID
+#pragma warning(suppress:28195) // Allocation is not guaranteed, caller needs to check return value
+DsmpAllocateAlignedPool(
+ _In_ IN POOL_TYPE PoolType,
+ _In_ IN SIZE_T NumberOfBytes,
+ _In_ IN ULONG AlignmentMask,
+ _In_ IN ULONG Tag,
+ _Out_ OUT SIZE_T *BytesAllocated
+ )
+/*+++
+
+Routine Description :
+
+ Allocates memory from the specified pool using the given tag and alignment requirement.
+ If the allocation is successful, the entire buffer will be zeroed.
+
+Arguements:
+
+ PoolType - Pool to allocate from (NonPaged, Paged, etc)
+ NumberOfBytes - Size of the buffer to allocate
+ AlignmentMask - Alignment requirement specified by the device
+ Tag - Tag (DSM_TAG_XXX) to be used for this allocation.
+ These tags are defined in msdsm.h
+ BytesAllocated - Returns the number of bytes allocated, if the routine was successful
+
+Return Value:
+
+ Pointer to the buffer if allocation is successful
+ NULL otherwise
+
+--*/
+{
+ PVOID Block = NULL;
+ UINT_PTR align64 = (UINT_PTR)AlignmentMask;
+ ULONG totalSize = (ULONG)NumberOfBytes;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpAllocateAlignedPool (Tag %u): Entering function.\n",
+ Tag));
+
+ if (BytesAllocated == NULL) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit;
+ }
+
+ *BytesAllocated = 0;
+
+ if (AlignmentMask) {
+
+ status = RtlULongAdd((ULONG)NumberOfBytes, AlignmentMask, &totalSize);
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ #pragma warning(suppress: 6014 28118) // Block isn't leaked, this function is marked as an allocator; PoolType is simply passed through
+ Block = ExAllocatePoolWithTag(PoolType, totalSize, Tag);
+
+ if (Block != NULL) {
+
+ if (AlignmentMask) {
+
+ Block = (PVOID)(((UINT_PTR)Block + align64) & ~align64);
+ }
+ } else {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+__Exit:
+
+ if (NT_SUCCESS(status)) {
+
+ RtlZeroMemory(Block, totalSize);
+ *BytesAllocated = totalSize;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpAllocateAlignedPool (Tag %u): Exiting function with allocated block %p.\n",
+ Tag,
+ Block));
+
+ return Block;
+}
+
+
+_IRQL_requires_max_(DISPATCH_LEVEL)
+VOID
+DsmpFreePool(
+ _In_opt_ __drv_freesMem(Mem) IN PVOID Block
+ )
+/*+++
+
+Routine Description :
+
+ Frees the block passed in.
+
+Arguements:
+
+ Block - pointer to the memory to free.
+
+Return Value:
+
+ Nothing
+
+--*/
+{
+ PVOID tempAddress = Block;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFreePool (Block %p): Entering function.\n",
+ Block));
+
+ if (Block) {
+
+ ExFreePool(Block);
+ Block = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFreePool (Block %p): Exiting function.\n",
+ tempAddress));
+
+ return;
+}
+
+
+NTSTATUS
+DsmpGetStatsGatheringChoice(
+ _In_ IN PDSM_CONTEXT Context,
+ _Out_ OUT PULONG StatsGatherChoice
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to determine if the Admin wants statitics to be collected
+ on every IO. It queries the the services key for the value under
+ "msdsm\Parameters\DsmDisableStatistics"
+
+Arguments:
+
+ Context - The DSM Context value.
+ StatsGatherChoice - Returns the choice of whether or not to gather statistics
+
+Return Value:
+
+ Status of the RtlQueryRegistryValues call.
+
+--*/
+{
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+ WCHAR registryKeyName[56] = {0};
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetStatsGatherChoice (DsmCtxt %p): Entering function.\n",
+ Context));
+
+ if (!StatsGatherChoice) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_INIT,
+ "DsmpGetStatsGatherChoice (DsmCtxt %p): Invalid parameter - StatsGatherChoice is NULL.\n",
+ Context));
+
+ goto __Exit_DsmpGetStatsGatherChoice;
+ }
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ //
+ // Build the key value name that we want as the base of the query.
+ //
+ RtlStringCbPrintfW(registryKeyName,
+ sizeof(registryKeyName),
+ DSM_PARAMETER_PATH_W);
+
+ //
+ // The query table has two entries. One for the supporteddeviceList and
+ // the second which is the 'NULL' terminator.
+ //
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_DISABLE_STATISTICS;
+ queryTable[0].EntryContext = StatsGatherChoice;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
+ registryKeyName,
+ queryTable,
+ registryKeyName,
+ NULL);
+
+__Exit_DsmpGetStatsGatherChoice:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetStatsGatherChoice (DsmCtxt %p): Exiting function with status %x.\n",
+ Context,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetStatsGatheringChoice(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN ULONG StatsGatherChoice
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to set the value that indicates whether statistics will
+ be gathered on every IO. It updates the services key for the value under
+ "msdsm\Parameters\DsmDisableStatistics"
+
+Arguments:
+
+ Context - The DSM Context value.
+ StatsGatherChoice - Value indicating whether to gather statistics (TRUE) or not (FALSE)
+
+Return Value:
+
+ Status of the RtlWriteRegistryValue call.
+
+--*/
+{
+ WCHAR registryKeyName[56] = {0};
+ NTSTATUS status = STATUS_SUCCESS;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetStatsGatherChoice (DsmCtxt %p): Entering function.\n",
+ Context));
+
+ //
+ // Build the key value name that we want as the base of the query.
+ //
+ RtlStringCbPrintfW(registryKeyName,
+ sizeof(registryKeyName),
+ DSM_PARAMETER_PATH_W);
+
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_SERVICES,
+ registryKeyName,
+ DSM_DISABLE_STATISTICS,
+ REG_DWORD,
+ &StatsGatherChoice,
+ sizeof(ULONG));
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetStatsGatherChoice (DsmCtxt %p): Exiting function with status %x.\n",
+ Context,
+ status));
+
+ return status;
+}
+
+
+
+NTSTATUS
+DsmpGetDeviceList(
+ _In_ IN PDSM_CONTEXT Context
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to build the supported device list by querying the services
+ key for the values under "msdsm\Parameters\DsmSupportedDeviceList"
+
+Arguments:
+
+ Context - The DSM Context value. It contains storage for the multi_sz string that may
+ be built.
+
+Return Value:
+
+ Status of the RtlQueryRegistryValues call.
+
+--*/
+{
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+ WCHAR registryKeyName[56] = {0};
+ UNICODE_STRING inquiryStrings;
+ WCHAR defaultIDs[] = { L"\0" };
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceList (DsmCtxt %p): Entering function.\n",
+ Context));
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+ RtlInitUnicodeString(&inquiryStrings, NULL);
+
+ //
+ // Build the key value name that we want as the base of the query.
+ //
+ RtlStringCbPrintfW(registryKeyName,
+ sizeof(registryKeyName),
+ DSM_PARAMETER_PATH_W);
+
+ //
+ // The query table has two entries. One for the supporteddeviceList and
+ // the second which is the 'NULL' terminator.
+ //
+ // Indicate that there is NO call-back routine, and to give back the MULTI_SZ as
+ // one blob, as opposed to individual unicode strings.
+ //
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_NOEXPAND | RTL_QUERY_REGISTRY_TYPECHECK;
+
+ //
+ // The value to query.
+ //
+ queryTable[0].Name = DSM_SUPPORTED_DEVICELIST_VALUE_NAME;
+
+ //
+ // Where to put the strings. Note that we need to use an empty unicode_string
+ // for the query or else RtlQueryRegistryValues will only fill in enough
+ // entries as specified by the size of the unicode string's buffer, which
+ // is why we can't use Context->SupportedDevices directly in the call.
+ //
+ queryTable[0].EntryContext = &inquiryStrings;
+ queryTable[0].DefaultType = (REG_MULTI_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_MULTI_SZ;
+ queryTable[0].DefaultData = defaultIDs;
+ queryTable[0].DefaultLength = sizeof(defaultIDs);
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
+ registryKeyName,
+ queryTable,
+ registryKeyName,
+ NULL);
+
+ //
+ // If we successfully queried for the supported device list, we need to delete
+ // our cached list and update it with this new one.
+ //
+ if (NT_SUCCESS(status)) {
+
+ KIRQL oldIrql;
+ PWCHAR tempBuffer = NULL;
+
+ tempBuffer = DsmpAllocatePool(NonPagedPoolNx, inquiryStrings.MaximumLength, DSM_TAG_REG_VALUE_RELATED);
+
+ //
+ // This is a "best effort" operation. If we are unable to allocate a
+ // buffer for the strings, we just continue using our old cached list.
+ // We do NOT fall back to using inquiryStrings's buffer as we want to
+ // be able to work with the supported devices list at raised IRQL.
+ //
+ if (tempBuffer) {
+
+ RtlCopyMemory(tempBuffer, inquiryStrings.Buffer, inquiryStrings.Length);
+
+ KeAcquireSpinLock(&Context->SupportedDevicesListLock, &oldIrql);
+ DsmpFreePool(Context->SupportedDevices.Buffer);
+ Context->SupportedDevices.Buffer = tempBuffer;
+ Context->SupportedDevices.Length = inquiryStrings.Length;
+ Context->SupportedDevices.MaximumLength = inquiryStrings.MaximumLength;
+ KeReleaseSpinLock(&Context->SupportedDevicesListLock, oldIrql);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceList (DsmCtxt %p): Failed to allocate supported device list's buffer.\n",
+ Context));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ ExFreePool(inquiryStrings.Buffer);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceList (DsmCtxt %p): Exiting function with status %x.\n",
+ Context,
+ status));
+
+ return status;
+}
+
+
+_Success_(return==0)
+NTSTATUS
+DsmpGetStandardInquiryData(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _Out_ OUT PINQUIRYDATA InquiryData
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to send an inquiry with EVPD cleared to get the standard inquiry data.
+
+Arguments:
+
+ DeviceObject - The port PDO to which the command should be sent.
+ InquiryData - Pointer to inquiry data that will be returned to caller.
+
+Return Value:
+
+ STATUS_SUCCESS or failure NTSTATUS code.
+
+--*/
+{
+ PSCSI_PASS_THROUGH_WITH_BUFFERS passThrough = NULL;
+ PCDB cdb;
+ IO_STATUS_BLOCK ioStatus;
+ ULONG length;
+ NTSTATUS status = STATUS_SUCCESS;
+ PINQUIRYDATA inquiryData;
+ PSENSE_DATA senseData;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetStandardInquiryData (DevObj %p): Entering function.\n",
+ DeviceObject));
+
+ if (InquiryData == NULL) {
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpGetStandardInquiryData;
+ }
+
+ //
+ // Build a standard inquiry command.
+ //
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ passThrough = DsmpAllocatePool(NonPagedPoolNx,
+ length,
+ DSM_TAG_PASS_THRU);
+ if (!passThrough) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetStandardInquiryData (DevObj %p): Failed to allocate mem for passthrough.\n",
+ DeviceObject));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpGetStandardInquiryData;
+ }
+
+__Retry_Request:
+
+ //
+ // Build the cdb for SCSI-3 standard inquiry.
+ //
+ cdb = (PCDB)passThrough->ScsiPassThrough.Cdb;
+ cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
+ cdb->CDB6INQUIRY3.EnableVitalProductData = 0;
+ cdb->CDB6INQUIRY3.AllocationLength = sizeof(INQUIRYDATA);
+
+ passThrough->ScsiPassThrough.Length = sizeof(SCSI_PASS_THROUGH);
+ passThrough->ScsiPassThrough.CdbLength = 6;
+ passThrough->ScsiPassThrough.SenseInfoLength = SPTWB_SENSE_LENGTH;
+ passThrough->ScsiPassThrough.DataIn = 1;
+ passThrough->ScsiPassThrough.DataTransferLength = sizeof(INQUIRYDATA);
+ passThrough->ScsiPassThrough.TimeOutValue = 20;
+ passThrough->ScsiPassThrough.SenseInfoOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, SenseInfoBuffer);
+ passThrough->ScsiPassThrough.DataBufferOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, DataBuffer);
+
+ DsmSendDeviceIoControlSynchronous(IOCTL_SCSI_PASS_THROUGH,
+ DeviceObject,
+ passThrough,
+ passThrough,
+ length,
+ length,
+ FALSE,
+ &ioStatus);
+
+ status = ioStatus.Status;
+ senseData = (PSENSE_DATA)(passThrough->SenseInfoBuffer);
+
+ if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_GOOD) && (NT_SUCCESS(status))) {
+
+ //
+ // Get the returned data.
+ //
+ inquiryData = (PINQUIRYDATA)(passThrough->DataBuffer);
+
+ RtlCopyMemory(InquiryData, inquiryData, sizeof(INQUIRYDATA));
+
+ } else if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_CHECK_CONDITION) &&
+ (NT_SUCCESS(ioStatus.Status)) &&
+ (DsmpShouldRetryPassThroughRequest(senseData, passThrough->ScsiPassThrough.SenseInfoLength))) {
+
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ //
+ // Retry the request
+ //
+ RtlZeroMemory(passThrough, length);
+ goto __Retry_Request;
+
+ } else {
+
+ // Failed to get inquiry data
+ // Here it is possible that status is success, but scsi status is not.
+ // If so, set status to unsuccessful.
+ if (NT_SUCCESS(status)){
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetStandardInquiryData (DevObj %p): NTStatus 0x%x, ScsiStatus 0x%x.\n",
+ DeviceObject,
+ status,
+ passThrough->ScsiPassThrough.ScsiStatus));
+ }
+
+__Exit_DsmpGetStandardInquiryData:
+
+ //
+ // Free the passthrough + data buffer.
+ //
+ if (passThrough) {
+ DsmpFreePool(passThrough);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetStandardInquiryData (DevObj %p): Exiting function with status %x.\n",
+ DeviceObject,
+ status));
+
+ return status;
+}
+
+
+BOOLEAN
+DsmpCheckScsiCompliance(
+ _In_ IN PDEVICE_OBJECT TargetObject,
+ _In_ IN PINQUIRYDATA InquiryData,
+ _In_ IN PSTORAGE_DEVICE_DESCRIPTOR Descriptor,
+ _In_ IN PSTORAGE_DEVICE_ID_DESCRIPTOR DeviceIdList
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to determine if the device is SPC-3 compliant.
+
+Arguments:
+
+ DeviceObject - The port PDO that we're determining compliance for.
+ InquiryData - Pointer to its inquiry data.
+ Descriptor - Pointer to its VPD page 0x80 data
+ DeviceIdList - Pointer to its VPD page 0x83 data
+
+Return Value:
+
+ TRUE if compliant, else FALSE.
+
+--*/
+{
+ BOOLEAN supported = FALSE /* TRUE */;
+ UCHAR deviceType;
+ UCHAR qualifier;
+
+ UNREFERENCED_PARAMETER(DeviceIdList);
+ UNREFERENCED_PARAMETER(Descriptor);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpCheckScsiCompliance (DevObj %p): Entering function.\n",
+ TargetObject));
+
+ deviceType = InquiryData->DeviceType & 0x1F;
+ qualifier = (InquiryData->DeviceTypeQualifier >> 0x5) & 0x7;
+
+ if ((deviceType | qualifier) == 0x7F) {
+
+ supported = FALSE;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpCheckScsiCompliance (DevObj %p): Exiting function with Supported = %u.\n",
+ TargetObject,
+ supported));
+
+ return supported;
+}
+
+
+BOOLEAN
+DsmpDeviceSupported(
+ _In_ IN PDSM_CONTEXT Context,
+ _In_ IN PCSTR VendorId,
+ _In_ IN PCSTR ProductId
+ )
+/*++
+
+Routine Description:
+
+ This routine determines whether the device is supported by traversing the SupportedDevice
+ list and comparing to the VendorId/ProductId values passed in.
+
+Arguments:
+
+ Context - Context value given to the multipath driver during registration.
+ VendorId - Pointer to the inquiry data VendorId.
+ ProductId - Pointer to the inquiry data ProductId.
+
+Return Value:
+
+ TRUE - If VendorId/ProductId is found.
+
+--*/
+{
+ UNICODE_STRING deviceName;
+ UNICODE_STRING productName;
+ ANSI_STRING ansiVendor;
+ ANSI_STRING ansiProduct;
+ NTSTATUS status;
+ BOOLEAN supported = FALSE;
+ KIRQL oldIrql;
+ UNICODE_STRING tempStrings;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpDeviceSupported (DsmCtxt %p): Entering function.\n",
+ Context));
+
+ KeAcquireSpinLock(&Context->SupportedDevicesListLock, &oldIrql);
+
+ RtlInitUnicodeString(&tempStrings, NULL);
+ tempStrings.Buffer = DsmpAllocatePool(NonPagedPoolNx, Context->SupportedDevices.MaximumLength, DSM_TAG_REG_VALUE_RELATED);
+
+ if (tempStrings.Buffer) {
+
+ RtlCopyMemory(tempStrings.Buffer, Context->SupportedDevices.Buffer, Context->SupportedDevices.Length);
+ tempStrings.Length = Context->SupportedDevices.Length;
+ tempStrings.MaximumLength = Context->SupportedDevices.MaximumLength;
+
+ } else {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpDeviceSupported (DsmCtxt %p): Failed to allocate temporary list (error %x).\n",
+ Context,
+ status));
+
+ KeReleaseSpinLock(&Context->SupportedDevicesListLock, oldIrql);
+
+ goto __Exit_DsmpDeviceSupported;
+ }
+
+ KeReleaseSpinLock(&Context->SupportedDevicesListLock, oldIrql);
+
+ //
+ // The SupportedDevice list was built in DriverEntry from the services key.
+ //
+ if (tempStrings.MaximumLength == 0) {
+
+ //
+ // List is empty.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpDeviceSupported (DsmCtxt %p): No supported Device in the list.\n",
+ Context));
+
+ goto __Exit_DsmpDeviceSupported;
+ }
+
+ RtlInitUnicodeString(&productName, NULL);
+
+ //
+ // Convert the inquiry fields into ansi strings.
+ //
+ RtlInitAnsiString(&ansiVendor, VendorId);
+ RtlInitAnsiString(&ansiProduct, ProductId);
+
+ //
+ // Allocate the deviceName buffer. Needs to be 8+16 plus NULL.
+ // (productId length + vendorId length + NULL).
+ //
+ deviceName.MaximumLength = 25 * sizeof(WCHAR);
+ deviceName.Buffer = DsmpAllocatePool(PagedPool, deviceName.MaximumLength, DSM_TAG_SUPPORTED_DEV);
+
+ if (deviceName.Buffer) {
+
+ //
+ // Convert the vendorId to unicode.
+ //
+ status = RtlAnsiStringToUnicodeString(&deviceName, &ansiVendor, FALSE);
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Convert the productId to unicode.
+ //
+ status = RtlAnsiStringToUnicodeString(&productName, &ansiProduct, TRUE);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // 'cat' them.
+ //
+ status = RtlAppendUnicodeStringToString(&deviceName, &productName);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Run the list of supported devices that was captured from the registry
+ // and see if this one is in the list.
+ //
+ supported = DsmpFindSupportedDevice(&deviceName,
+ &tempStrings);
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpDeviceSupported (DsmCtxt %p): Failed to append product name. Status %x.\n",
+ Context,
+ status));
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpDeviceSupported (DsmCtxt %p): Failed to convert ansi vendor string to unicode. Status %x\n",
+ Context,
+ status));
+ }
+
+ DsmpFreePool(deviceName.Buffer);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpDeviceSupported (DsmCtxt %p): Failed to allocate device name buffer.\n",
+ Context));
+ }
+
+__Exit_DsmpDeviceSupported:
+
+ if (tempStrings.Buffer) {
+ DsmpFreePool(tempStrings.Buffer);
+ }
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpDeviceSupported (DsmCtxt %p): Exiting function with supported = %u.\n",
+ Context,
+ supported));
+
+ return supported;
+}
+
+
+BOOLEAN
+DsmpFindSupportedDevice(
+ _In_ IN PUNICODE_STRING DeviceName,
+ _In_ IN PUNICODE_STRING SupportedDevices
+ )
+/*++
+
+Routine Description:
+
+ This routine compares the two unicode strings for a match.
+
+Arguments:
+
+ DeviceName - String built from the current device's inquiry data.
+ SupportedDevices - MULTI_SZ of devices that are supported.
+
+Return Value:
+
+ TRUE - If VendorId/ProductId is found.
+
+--*/
+{
+ PWSTR devices = SupportedDevices->Buffer;
+ ULONG bufferLengthLeft = SupportedDevices->MaximumLength / sizeof(WCHAR);
+ UNICODE_STRING unicodeString;
+ USHORT originalLength = DeviceName->Length;
+ LONG compare;
+ BOOLEAN supported = FALSE;
+ WCHAR tempString[32];
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFindSupportedDevice (DevName %ws): Entering function.\n",
+ DeviceName->Buffer));
+
+ //
+ // 'devices' is the current buffer in the MULTI_SZ built from
+ // the registry.
+ //
+ while (devices[0]) {
+
+ RtlZeroMemory(tempString, sizeof(tempString));
+
+ if (!NT_SUCCESS(RtlStringCchCopyNW(tempString, sizeof(tempString) / sizeof(tempString[0]), devices, bufferLengthLeft))) {
+
+ tempString[(sizeof(tempString) / sizeof(tempString)) - 1] = L'\0';
+ }
+
+ //
+ // Make the current entry into a unicode string.
+ //
+ RtlInitUnicodeString(&unicodeString, tempString);
+
+ //
+ // Compare this one with the current device.
+ // However, for storages that make up the product id on-the-fly, MPIO
+ // allows for matching based just on substring (product-id-prefix so to
+ // speak).
+ //
+ if (unicodeString.Length < DeviceName->Length) {
+ DeviceName->Length = unicodeString.Length;
+ }
+
+ compare = RtlCompareUnicodeStrings(unicodeString.Buffer,
+ unicodeString.Length / sizeof(WCHAR),
+ DeviceName->Buffer,
+ DeviceName->Length / sizeof(WCHAR),
+ TRUE);
+
+ DeviceName->Length = originalLength;
+
+ if (compare == 0) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpFindSupportedDevice (DevName %ws): Device support found in the registry.\n",
+ DeviceName->Buffer));
+
+ supported = TRUE;
+ break;
+ }
+
+ //
+ // Advance to next entry in the MULTI_SZ.
+ //
+ devices += (unicodeString.MaximumLength / sizeof(WCHAR));
+
+ bufferLengthLeft -= (unicodeString.MaximumLength / sizeof(WCHAR));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpFindSupportedDevice (DevName %ws): Exiting function with Supported = %u.\n",
+ DeviceName->Buffer,
+ supported));
+
+ return supported;
+}
+
+_Success_(return!=0)
+PVOID
+DsmpParseDeviceID(
+ _In_ IN PSTORAGE_DEVICE_ID_DESCRIPTOR DeviceID,
+ _In_ IN DSM_DEVID_TYPE DeviceIdType,
+ _In_opt_ IN PULONG IdNumber,
+ _Out_opt_ OUT PSTORAGE_IDENTIFIER_CODE_SET CodeSet,
+ _In_ IN BOOLEAN Legacy
+ )
+/*++
+
+Routine Description:
+
+ This routine builds a serial number string based on the information
+ in the VPD page 0x83 data if serial number is requested, else it
+ returns the appropriate identifier requested.
+
+ Caller must free the buffer.
+
+Arguments:
+
+ DeviceIdList - VPD Page 0x83 information.
+ DeviceIdType - Type of identifier that the DeviceID is being parsed for
+ IdNumber - If there are multiple identifiers of type DeviceIdType, this parameter
+ determines which among them to actually return.
+ IMPORTANT: This number is one-based (not zero-based).
+ CodeSet - Of relevance only if the DeviceIdType is DSM_DEVID_SERIAL_NUMBER. This
+ returns the code set that was used when building the serial number.
+ Legacy - Of relevance only if the DeviceIdType is DSM_DEVID_SERIAL_NUMBER. If the
+ code set of the identifier is StorageIdCodeSetBinary, this determines
+ whether to use the legacy method of binary to ascii conversion.
+
+Return Value:
+
+ Requested Device identifier.
+
+--*/
+{
+ PSTORAGE_IDENTIFIER identifier;
+ STORAGE_IDENTIFIER_CODE_SET codeSet = StorageIdCodeSetReserved; // Preload with a bogus value.
+ STORAGE_IDENTIFIER_TYPE type = 0xF;
+ STORAGE_ASSOCIATION_TYPE association = 0xF;
+ ULONG numberIds;
+ ULONG i;
+ ULONG identifierSize = 0;
+ PUCHAR bytes = NULL;
+ PVOID buffer = NULL;
+ BOOLEAN done = FALSE;
+ ULONG idNumber = MAXULONG;
+ ULONG matches = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpParseDeviceID (DevIdDesc %p): Entering function - IdType %x.\n",
+ DeviceID,
+ DeviceIdType));
+
+ if (IdNumber) {
+ idNumber = *IdNumber;
+ }
+
+ //
+ // Get the number of encapsulated identifiers.
+ //
+ numberIds = DeviceID->NumberOfIdentifiers;
+
+ if (idNumber != MAXULONG && idNumber > numberIds) {
+ goto __Exit_DsmpParseDeviceID;
+ }
+
+ //
+ // Get a pointer to the first one.
+ //
+ identifier = (PSTORAGE_IDENTIFIER)(DeviceID->Identifiers);
+
+ for (i = 0; i < numberIds && !done; i++) {
+
+ switch (DeviceIdType) {
+
+ case DSM_DEVID_SERIAL_NUMBER: {
+
+ //
+ // The way this works is that we will go through all the identifiers
+ // Order of preference will be LUN-associated over Target-associated.
+ // Further, upon same association, preference will be based on type as
+ // follows: 0x8, 0x3, 0x2, 0x1, 0x0.
+ // So an existing identifier will be discarded if a better one is found.
+ // If two identifiers have the same type, we will prefer the one will
+ // the larger length.
+ //
+
+ //
+ // 1. Ensure that the association is for either the LUN or target. (If neither, ignore id).
+ // 2. If association is with target, don't it consider if current candidate has assocation with LUN.
+ // 3. If considering this identifier, order of preference is 8 > 3 > 2 > 1 > 0.
+ // 4. If this id type is same as current candidate, consider it only if it is of greater length.
+ //
+ if (((identifier->Association == StorageIdAssocDevice) ||
+ (identifier->Association == 0x2 && association != StorageIdAssocDevice)) &&
+ ((type == identifier->Type && identifierSize < identifier->IdentifierSize) ||
+ (type != identifier->Type && DsmpIsPreferredDeviceId(type, identifier->Type)))) {
+
+ //
+ // Get a pointer to the id itself.
+ //
+ bytes = identifier->Identifier;
+
+ //
+ // The id's size.
+ //
+ identifierSize = identifier->IdentifierSize;
+
+ //
+ // Get the type, code set, and association.
+ //
+ type = identifier->Type;
+ codeSet = identifier->CodeSet;
+ association = identifier->Association;
+
+ matches++;
+ }
+
+ break;
+ }
+
+ case DSM_DEVID_RELATIVE_TARGET_PORT: {
+
+ //
+ // Ensure that the association is for the target port.
+ //
+ if (identifier->Association != StorageIdAssocPort) {
+
+ if ((i + 1) < numberIds) {
+ identifier = (PSTORAGE_IDENTIFIER)((PUCHAR)identifier + identifier->NextOffset);
+ }
+
+ continue;
+ }
+
+ if (identifier->Type == StorageIdTypePortRelative) {
+
+ //
+ // Get a pointer to the id itself.
+ //
+ bytes = identifier->Identifier;
+
+ //
+ // The id's size.
+ //
+ identifierSize = identifier->IdentifierSize;
+
+ type = identifier->Type;
+ codeSet = identifier->CodeSet;
+ association = identifier->Association;
+
+ matches++;
+ }
+
+ break;
+ }
+
+ case DSM_DEVID_TARGET_PORT_GROUP: {
+
+ //
+ // Ensure that the association is for the target port.
+ //
+ if (identifier->Association != StorageIdAssocPort) {
+
+ if ((i + 1) < numberIds) {
+ identifier = (PSTORAGE_IDENTIFIER)((PUCHAR)identifier + identifier->NextOffset);
+ }
+
+ continue;
+ }
+
+ if (identifier->Type == 0x5) {
+
+ //
+ // Get a pointer to the id itself.
+ //
+ bytes = identifier->Identifier;
+
+ //
+ // Move this by two bytes because first two bytes are reservered
+ //
+ bytes += sizeof(USHORT);
+
+ //
+ // The id's size. Reduce the size by 2 bytes (to account
+ // for the reservered bytes)
+ //
+ identifierSize = identifier->IdentifierSize - sizeof(USHORT);
+
+ type = identifier->Type;
+ codeSet = identifier->CodeSet;
+ association = identifier->Association;
+
+ matches++;
+ }
+
+ break;
+ }
+
+ default: break;
+ }
+
+
+ if (idNumber != MAXULONG && idNumber == matches) {
+ done = TRUE;
+ }
+
+ //
+ // Advance to the next identifier in the buffer.
+ //
+ if ((i + 1) < numberIds) {
+ identifier = (PSTORAGE_IDENTIFIER)((PUCHAR)identifier + identifier->NextOffset);
+ }
+ }
+
+ if (idNumber != MAXULONG && idNumber > matches) {
+ goto __Exit_DsmpParseDeviceID;
+ }
+
+ if (DeviceIdType == DSM_DEVID_SERIAL_NUMBER) {
+
+ if (type != StorageIdTypeScsiNameString &&
+ type != StorageIdTypeFCPHName &&
+ type != StorageIdTypeEUI64 &&
+ type != StorageIdTypeVendorId &&
+ type != StorageIdTypeVendorSpecific) {
+
+ DSM_ASSERT(FALSE);
+ bytes = NULL;
+ identifierSize = 0;
+ type = association = 0xF;
+ codeSet = StorageIdCodeSetReserved;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpParseDeviceID (DevIdDesc %p): IdentifierSize = %u, Type = %u, Association = %u, CodeSet = %u.\n",
+ DeviceID,
+ identifierSize,
+ type,
+ association,
+ codeSet));
+
+ if (!bytes) {
+ goto __Exit_DsmpParseDeviceID;
+ }
+
+ if (codeSet == StorageIdCodeSetBinary) {
+
+ //
+ // Need to convert to ascii.
+ //
+ buffer = DsmpBinaryToAscii(bytes,
+ identifierSize,
+ &identifierSize,
+ Legacy);
+
+ } else {
+
+ if (identifierSize) {
+ //
+ // Allocate a buffer that is the size of the data, plus one for NULL.
+ //
+ buffer = DsmpAllocatePool(NonPagedPoolNx, identifierSize + 1, DSM_TAG_DEV_ID);
+ DSM_ASSERT(buffer);
+
+ if (buffer) {
+
+ //
+ // Copy over the id.
+ //
+ RtlCopyMemory(buffer, bytes, identifierSize);
+ }
+ }
+ }
+
+ if (CodeSet) {
+ *CodeSet = codeSet;
+ }
+
+ } else {
+
+ if (identifierSize) {
+
+ DSM_ASSERT((DeviceIdType == DSM_DEVID_RELATIVE_TARGET_PORT && identifierSize == sizeof(ULONG)) ||
+ (DeviceIdType == DSM_DEVID_TARGET_PORT_GROUP && identifierSize == sizeof(USHORT)));
+
+ _Analysis_assume_((DeviceIdType == DSM_DEVID_RELATIVE_TARGET_PORT && identifierSize == sizeof(ULONG)) ||
+ (DeviceIdType == DSM_DEVID_TARGET_PORT_GROUP && identifierSize == sizeof(USHORT)));
+
+ buffer = DsmpAllocatePool(NonPagedPoolNx, identifierSize, DSM_TAG_DEV_ID);
+
+ if (buffer) {
+
+ if (DeviceIdType == DSM_DEVID_RELATIVE_TARGET_PORT) {
+
+ GetUlongFrom4ByteArray(bytes, *((PULONG)buffer));
+
+ } else if (DeviceIdType == DSM_DEVID_TARGET_PORT_GROUP) {
+
+ *((PUSHORT)buffer) = (bytes[0] << 8) | (bytes[1]);
+ }
+ }
+ }
+ }
+
+__Exit_DsmpParseDeviceID:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpParseDeviceID (DevIdDesc %p): Exiting function with buffer %p.\n",
+ DeviceID,
+ buffer));
+
+ return buffer;
+}
+
+
+PUCHAR
+DsmpBinaryToAscii(
+ _In_reads_(Length) IN PUCHAR HexBuffer,
+ _In_ IN ULONG Length,
+ _Inout_ IN OUT PULONG UpdateLength,
+ _In_ IN BOOLEAN Legacy
+ )
+/*++
+
+Routine Description:
+
+ This routine will convert HexBuffer into an ascii NULL-terminated string.
+
+ Note: This routine will allocate memory for storing the ascii string. It is
+ the responsibility of the caller to free this buffer.
+
+Arguments:
+
+ HexBuffer - Pointer to the binary data.
+ Length - Length, in bytes, of HexBuffer.
+ UpdateLength - Storage to place the actual length of the returned string.
+ Legacy - Use the legacy method for the conversion.
+
+Return Value:
+
+ Serial Number string, or NULL if an error occurred.
+
+--*/
+{
+ static UCHAR IntegerTable[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+ ULONG i;
+ ULONG j;
+ ULONG actualLength;
+ PUCHAR buffer = NULL;
+ UCHAR highWord;
+ UCHAR lowWord;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBinaryToAscii (HexBuff %p): Entering function.\n",
+ HexBuffer));
+
+ if (Length == 0) {
+ *UpdateLength = 0;
+ goto __Exit_DsmpBinaryToAscii;
+ }
+
+ if (Legacy) {
+ //
+ // Do a pre-test on the buffer to determine the length actually needed.
+ //
+ for (i = 0, actualLength = 0; i < Length; i++) {
+
+ if (HexBuffer[i] < 0x10) {
+ actualLength++;
+ } else {
+ actualLength += 2;
+ }
+ }
+
+ //
+ // Add room for a terminating NULL.
+ //
+ actualLength++;
+ } else {
+ //
+ // We need one character for each nibble, plus one for the terminating NULL.
+ //
+ actualLength = (Length * 2) + 1;
+ }
+
+ //
+ // Allocate the buffer.
+ //
+ buffer = DsmpAllocatePool(NonPagedPoolNx,
+ actualLength,
+ DSM_TAG_BIN_TO_ASCII);
+ if (!buffer) {
+ *UpdateLength = 0;
+ goto __Exit_DsmpBinaryToAscii;
+ }
+
+ for (i = 0, j = 0; i < Length && j < actualLength; i++) {
+
+ if (Legacy && (HexBuffer[i] < 0x10)) {
+
+ //
+ // If legacy is mentioned and it's 0x0F or less,
+ // just convert the entire byte.
+ //
+ buffer[j++] = IntegerTable[HexBuffer[i]];
+ } else {
+
+ //
+ // Split out each nibble from the binary byte.
+ //
+ highWord = HexBuffer[i] >> 4;
+ lowWord = HexBuffer[i] & 0x0F;
+
+ //
+ // Using the lookup table, convert and stuff into
+ // the ascii buffer.
+ //
+ buffer[j++] = IntegerTable[highWord];
+ buffer[j++] = IntegerTable[lowWord];
+ }
+ }
+
+ //
+ // Update the caller's length field.
+ //
+ *UpdateLength = actualLength;
+
+__Exit_DsmpBinaryToAscii:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBinaryToAscii (HexBuff %p): Exiting function with buffer %s.\n",
+ HexBuffer,
+ (const char*) buffer));
+
+ return buffer;
+}
+
+
+PSTR
+DsmpGetSerialNumber(
+ _In_ IN PDEVICE_OBJECT DeviceObject
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to send an inquiry with EVPD set to get the serial number page.
+ Used if the serial number is not embedded in the device descriptor (this device probably
+ doesn't support VPD page 0x00).
+
+ Note: This routine will allocate memory for storing the serial number. It is
+ the responsibility of the caller to free this buffer.
+
+Arguments:
+
+ DeviceObject - The port PDO to which the command should be sent.
+
+Return Value:
+
+ The serial number (null-terminated string) or NULL if the call fails.
+
+--*/
+{
+ PSCSI_PASS_THROUGH_WITH_BUFFERS passThrough = NULL;
+ PVPD_SERIAL_NUMBER_PAGE serialPage;
+ PCDB cdb;
+ PSTR serialNumber = NULL;
+ IO_STATUS_BLOCK ioStatus;
+ ULONG length;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetSerialNumber (DevObj %p): Entering function.\n",
+ DeviceObject));
+
+ //
+ // Build an inquiry command with EVPD and pagecode of 0x80 (serial number).
+ //
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ passThrough = DsmpAllocatePool(NonPagedPoolNx,
+ length,
+ DSM_TAG_PASS_THRU);
+ if (!passThrough) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetSerialNumber (DevObj %p): Failed to allocate mem for passthrough.\n",
+ DeviceObject));
+
+ goto __Exit_DsmpGetSerialNumber;
+ }
+
+ //
+ // Build the cdb.
+ //
+ cdb = (PCDB)passThrough->ScsiPassThrough.Cdb;
+ cdb->CDB6INQUIRY.OperationCode = SCSIOP_INQUIRY;
+ cdb->CDB6INQUIRY.Reserved1 = 1;
+ cdb->CDB6INQUIRY.PageCode = VPD_SERIAL_NUMBER;
+ cdb->CDB6INQUIRY.AllocationLength = DSM_SERIAL_NUMBER_BUFFER_SIZE;
+
+ passThrough->ScsiPassThrough.Length = sizeof(SCSI_PASS_THROUGH);
+ passThrough->ScsiPassThrough.CdbLength = 6;
+ passThrough->ScsiPassThrough.SenseInfoLength = SPTWB_SENSE_LENGTH;
+ passThrough->ScsiPassThrough.DataIn = 1;
+ passThrough->ScsiPassThrough.DataTransferLength = DSM_SERIAL_NUMBER_BUFFER_SIZE;
+ passThrough->ScsiPassThrough.TimeOutValue = 20;
+ passThrough->ScsiPassThrough.SenseInfoOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, SenseInfoBuffer);
+ passThrough->ScsiPassThrough.DataBufferOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, DataBuffer);
+
+ DsmSendDeviceIoControlSynchronous(IOCTL_SCSI_PASS_THROUGH,
+ DeviceObject,
+ passThrough,
+ passThrough,
+ length,
+ length,
+ FALSE,
+ &ioStatus);
+ if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_GOOD) &&
+ (NT_SUCCESS(ioStatus.Status))) {
+
+ ULONG inx;
+
+ //
+ // Get the returned data.
+ //
+ serialPage = (PVPD_SERIAL_NUMBER_PAGE)(passThrough->DataBuffer);
+
+ //
+ // Allocate a buffer to hold just the serial number plus a null terminator
+ //
+ serialNumber = DsmpAllocatePool(NonPagedPoolNx,
+ serialPage->PageLength + 1,
+ DSM_TAG_SERIAL_NUM);
+ if (serialNumber) {
+
+ //
+ // Copy it over.
+ //
+ RtlCopyMemory(serialNumber, serialPage->SerialNumber, serialPage->PageLength);
+
+ //
+ // Some devices return binary data for the serial number.
+ // Convert to a more ascii-ish format so that other routines don't have a problem.
+ //
+ for (inx = 0; inx < serialPage->PageLength; inx++) {
+ if (serialNumber[inx] == '\0') {
+ serialNumber[inx] = ' ';
+ }
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetSerialNumber (DevObj %p): Failed to allocate mem for serialnumber.\n",
+ DeviceObject));
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetSerialNumber (DevObj %p): NTStatus 0%x, ScsiStatus 0x%x.\n",
+ DeviceObject,
+ ioStatus.Status,
+ passThrough->ScsiPassThrough.ScsiStatus));
+ }
+
+__Exit_DsmpGetSerialNumber:
+
+ //
+ // Free the passthrough + data buffer.
+ //
+ if (passThrough) {
+
+ DsmpFreePool(passThrough);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetSerialNumber (DevObj %p): Exiting function with serial number %s.\n",
+ DeviceObject,
+ (const char*)serialNumber));
+
+ //
+ // Return the sn.
+ //
+ return serialNumber;
+}
+
+
+NTSTATUS
+DsmpDisableImplicitStateTransition(
+ _In_ IN PDEVICE_OBJECT TargetDevice,
+ _Out_ OUT PBOOLEAN DisableImplicit
+ )
+/*++
+
+Routine Description:
+
+ Send down request to disable implicit ALUA state transition.
+ The function first sends down a mode sense to get the control extension mode
+ sense data. It then clears the IALUAE bit and sends down a mode select.
+
+Arguements:
+
+ TargetDevice - Device object that will be target of this command.
+ DisableImplicit - Flag returned to the caller to indicate whether or not
+ implicit transitions are disabled.
+
+Return Value :
+
+ STATUS_SUCCESS if the command succeeds.
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PSCSI_PASS_THROUGH_WITH_BUFFERS passThrough = NULL;
+ PCDB cdb;
+ IO_STATUS_BLOCK ioStatus;
+ ULONG length;
+ PSPC3_CONTROL_EXTENSION_MODE_PAGE controlExtensionPage = NULL;
+ PSENSE_DATA senseData = NULL;
+ BOOLEAN implicitDisabled = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpDisableImplicitStateTransition (DevObj %p): Entering function.\n",
+ TargetDevice));
+
+ //
+ // First build the mode sense command to get the control extension parameters.
+ //
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ passThrough = DsmpAllocatePool(NonPagedPoolNx,
+ length,
+ DSM_TAG_PASS_THRU);
+ if (!passThrough) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpDisableImplicitStateTransition (DevObj %p): Failed to allocate mem for passthrough.\n",
+ TargetDevice));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpDisableImplicitStateTransition;
+ }
+
+__Retry_ModeSense:
+
+ passThrough->ScsiPassThrough.Length = sizeof(SCSI_PASS_THROUGH);
+ passThrough->ScsiPassThrough.CdbLength = 6;
+ passThrough->ScsiPassThrough.SenseInfoLength = SPTWB_SENSE_LENGTH;
+ passThrough->ScsiPassThrough.DataIn = 1;
+ passThrough->ScsiPassThrough.DataTransferLength = sizeof(SPC3_CONTROL_EXTENSION_MODE_PAGE);
+ passThrough->ScsiPassThrough.TimeOutValue = 20;
+ passThrough->ScsiPassThrough.SenseInfoOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, SenseInfoBuffer);
+ passThrough->ScsiPassThrough.DataBufferOffset = FIELD_OFFSET(SCSI_PASS_THROUGH_WITH_BUFFERS, DataBuffer);
+
+ //
+ // Build the cdb for mode sense.
+ //
+ cdb = (PCDB)passThrough->ScsiPassThrough.Cdb;
+ cdb->MODE_SENSE.OperationCode = SCSIOP_MODE_SENSE;
+ cdb->MODE_SENSE.Dbd = 1;
+ cdb->MODE_SENSE.PageCode = 0xA;
+ cdb->MODE_SENSE.SubPageCode = 0x01;
+ cdb->MODE_SENSE.AllocationLength = sizeof(SPC3_CONTROL_EXTENSION_MODE_PAGE);
+
+ DsmSendDeviceIoControlSynchronous(IOCTL_SCSI_PASS_THROUGH,
+ TargetDevice,
+ passThrough,
+ passThrough,
+ length,
+ length,
+ FALSE,
+ &ioStatus);
+
+ status = ioStatus.Status;
+ senseData = (PSENSE_DATA)(passThrough->SenseInfoBuffer);
+
+ if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_GOOD) && (NT_SUCCESS(status))) {
+
+ controlExtensionPage = (PSPC3_CONTROL_EXTENSION_MODE_PAGE)(passThrough->DataBuffer);
+
+ if (controlExtensionPage->ImplicitALUAEnable) {
+
+ controlExtensionPage->ImplicitALUAEnable = 0;
+
+__Retry_ModeSelect:
+
+ RtlZeroMemory(passThrough->SenseInfoBuffer, passThrough->ScsiPassThrough.SenseInfoLength);
+
+ passThrough->ScsiPassThrough.DataIn = 0;
+
+ //
+ // Build the cdb for mode select.
+ //
+ RtlZeroMemory(cdb, 6);
+ cdb->MODE_SELECT.OperationCode = SCSIOP_MODE_SELECT;
+ cdb->MODE_SELECT.SPBit = 0;
+ cdb->MODE_SELECT.PFBit = 1;
+ cdb->MODE_SELECT.ParameterListLength = sizeof(SPC3_CONTROL_EXTENSION_MODE_PAGE);
+
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ DsmSendDeviceIoControlSynchronous(IOCTL_SCSI_PASS_THROUGH,
+ TargetDevice,
+ passThrough,
+ passThrough,
+ length,
+ length,
+ FALSE,
+ &ioStatus);
+
+ status = ioStatus.Status;
+ senseData = (PSENSE_DATA)(passThrough->SenseInfoBuffer);
+
+ if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_GOOD) && (NT_SUCCESS(status))) {
+
+ implicitDisabled = TRUE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpDisableImplicitStateTransition (DevObj %p): Implicit transitions turned off successfully.\n",
+ TargetDevice));
+
+ } else if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_CHECK_CONDITION) &&
+ (NT_SUCCESS(status)) &&
+ (DsmpShouldRetryPassThroughRequest(senseData, passThrough->ScsiPassThrough.SenseInfoLength))) {
+
+ //
+ // Retry the request
+ //
+ goto __Retry_ModeSelect;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpDisableImplicitStateTransition (DevObj %p): ModeSelect failed - NTStatus 0x%x, ScsiStatus 0x%x.\n",
+ TargetDevice,
+ status,
+ passThrough->ScsiPassThrough.ScsiStatus));
+ }
+ } else {
+
+ implicitDisabled = TRUE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpDisableImplicitStateTransition (DevObj %p): Implicit transitions already turned OFF.\n",
+ TargetDevice));
+ }
+ } else if ((passThrough->ScsiPassThrough.ScsiStatus == SCSISTAT_CHECK_CONDITION) &&
+ (NT_SUCCESS(status)) &&
+ (DsmpShouldRetryPassThroughRequest(senseData, passThrough->ScsiPassThrough.SenseInfoLength))) {
+
+ length = sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS);
+
+ //
+ // Retry the request
+ //
+ RtlZeroMemory(passThrough, length);
+ goto __Retry_ModeSense;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpDisableImplicitStateTransition (DevObj %p): ModeSense failed - NTStatus 0x%x, ScsiStatus 0x%x.\n",
+ TargetDevice,
+ status,
+ passThrough->ScsiPassThrough.ScsiStatus));
+ }
+
+__Exit_DsmpDisableImplicitStateTransition:
+
+ //
+ // Free the passthrough + data buffer.
+ //
+ if (passThrough) {
+ DsmpFreePool(passThrough);
+ }
+
+ //
+ // Return whether IALUAE is set to 0.
+ //
+ if (DisableImplicit) {
+
+ *DisableImplicit = implicitDisabled;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpDisableImplicitStateTransition (DevObj %p): Exiting function with status %x.\n",
+ TargetDevice,
+ status));
+
+ return status;
+}
+
+
+PWSTR
+DsmpBuildHardwareId(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ )
+/*++
+
+Routine Description:
+
+ Construct a string concatinating VendorId with ProductId.
+
+Arguements:
+
+ DeviceInfo - Device Extension
+
+Return Value :
+
+ NULL terminated hardware id if it was built successfully.
+ NULL in case of failure.
+
+--*/
+{
+ PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor;
+ PWSTR hardwareId = NULL;
+ SIZE_T vendorIDLength = 0;
+ SIZE_T productIDLength = 0;
+ PCSZ vendorIdOffset;
+ PCSZ productIdOffset;
+ SIZE_T sizeNeeded;
+ NTSTATUS status = STATUS_SUCCESS;
+ ANSI_STRING ansiString;
+ UNICODE_STRING unicodeString;
+ ULONG offset;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildHardwareId (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ deviceDescriptor = &(DeviceInfo->Descriptor);
+
+ //
+ // Save the vendorid and productid offset in Device Descriptor
+ //
+ offset = deviceDescriptor->ProductIdOffset;
+ if ((offset != 0) && (offset != MAXULONG)) {
+
+ productIDLength = (strlen(((PCHAR)deviceDescriptor) + offset) * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ offset = deviceDescriptor->VendorIdOffset;
+ if ((offset != 0) && (offset != MAXULONG)) {
+
+ vendorIDLength = (strlen(((PCHAR)deviceDescriptor) + offset) * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ if (!vendorIDLength || !productIDLength) {
+
+ status = STATUS_UNSUCCESSFUL;
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "DsmpBuildHardwareId (DevInfo %p): Invalid vendor and/or product id.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildHardwareId;
+ }
+
+ sizeNeeded = vendorIDLength + productIDLength;
+ hardwareId = DsmpAllocatePool(NonPagedPoolNx, sizeNeeded, DSM_TAG_DEV_HARDWARE_ID);
+ if (!hardwareId) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildHardwareId (DevInfo %p): Failed to allocate memory for device name.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpBuildHardwareId;
+ }
+
+ //
+ // Build the NULL terminated hardwareId whose format is :
+ //
+ // VendorIdProductId
+ //
+ vendorIdOffset = (PCSZ)((PUCHAR)deviceDescriptor + deviceDescriptor->VendorIdOffset);
+ RtlInitAnsiString(&ansiString, vendorIdOffset);
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT)vendorIDLength;
+ unicodeString.Buffer = hardwareId;
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildHardwareId (DevInfo %p): Failed to convert vendor id to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildHardwareId;
+ }
+
+ productIdOffset = (PCSZ)((PUCHAR)deviceDescriptor + deviceDescriptor->ProductIdOffset);
+ RtlInitAnsiString(&ansiString, productIdOffset);
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT)productIDLength;
+ unicodeString.Buffer = hardwareId + strlen(((PCHAR)deviceDescriptor) + offset);
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildHardwareId (DevInfo %p): Failed to convert product id to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildHardwareId;
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildHardwareId (DevInfo %p): HardwareId is %ws.\n",
+ DeviceInfo,
+ hardwareId));
+
+__Exit_DsmpBuildHardwareId:
+
+ if (hardwareId && !NT_SUCCESS(status)) {
+ DsmpFreePool(hardwareId);
+ hardwareId = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildHardwareId (DevInfo %p): Exiting function with deviceName %ws.\n",
+ DeviceInfo,
+ hardwareId));
+
+ return hardwareId;
+}
+
+
+PWSTR
+DsmpBuildDeviceNameLegacyPage0x80(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo
+ )
+/*++
+
+Routine Description:
+
+ Construct a string from VendorId, ProductId, and SerialNumber (page 0x80
+ info) of the device.
+
+Arguements:
+
+ DeviceInfo - Device Extension
+
+Return Value :
+
+ STATUS_SUCCESS if the device name was built successfully.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor;
+ PWCHAR deviceName = NULL;
+ PWCHAR tmpPtr;
+ PWCHAR vendorID = NULL;
+ PWCHAR productID = NULL;
+ PWCHAR serialID = NULL;
+ ANSI_STRING ansiString;
+ UNICODE_STRING unicodeString;
+ UNICODE_STRING unicodeDeviceName;
+ SIZE_T vendorIDLength = 0;
+ SIZE_T productIDLength = 0;
+ SIZE_T serialIDLength = 0;
+ ULONG offset;
+ SIZE_T sizeNeeded;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ deviceDescriptor = &(DeviceInfo->Descriptor);
+
+ //
+ // Save the vendorid, productid, and serialnumber offset
+ // in Device Descriptor
+ //
+ offset = deviceDescriptor->VendorIdOffset;
+ if ((offset != 0) && (offset != MAXULONG)) {
+
+ vendorIDLength = (strlen(((PCHAR)deviceDescriptor) + offset) * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ offset = deviceDescriptor->ProductIdOffset;
+ if ((offset != 0) && (offset != MAXULONG)) {
+
+ productIDLength = (strlen(((PCHAR)deviceDescriptor) + offset) * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ offset = deviceDescriptor->SerialNumberOffset;
+ if ((offset != 0) && (offset != MAXULONG)) {
+
+ serialIDLength = (strlen(((PCHAR)deviceDescriptor) + offset) * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ //
+ // Allocate buffers to use to convert the IDs from ANSI to Unicode and
+ // eventually build the device name.
+ //
+ if (vendorIDLength > 0) {
+ vendorID = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, vendorIDLength, DSM_TAG_DEV_NAME);
+ if (!vendorID) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to allocate memory for vendor ID.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (productIDLength > 0) {
+ productID = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, productIDLength, DSM_TAG_DEV_NAME);
+ if (!productID) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to allocate memory for product ID.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (serialIDLength > 0) {
+ serialID = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, serialIDLength, DSM_TAG_DEV_NAME);
+ if (!serialID) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to allocate memory for serial ID.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ sizeNeeded = vendorIDLength + productIDLength + serialIDLength;
+ if (sizeNeeded > 0) {
+
+ //
+ // Account for the terminating NULL if serial id is empty.
+ //
+
+ sizeNeeded += (serialIDLength ? 0 : WNULL_SIZE);
+
+ deviceName = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, sizeNeeded, DSM_TAG_DEV_NAME);
+ if (!deviceName) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to allocate memory for device name.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ } else {
+
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ goto __Exit_DsmpBuildDeviceNameLegacyPage0x80;
+ }
+
+ //
+ // Build the NULL terminated device name whose format is :
+ //
+ // VendorId_ProductId_SerialNumber
+ //
+
+ unicodeDeviceName.Length = 0;
+ unicodeDeviceName.MaximumLength = (USHORT)sizeNeeded;
+ unicodeDeviceName.Buffer = deviceName;
+
+ if (vendorIDLength) {
+
+ PCSZ vendorIdOffset;
+
+ vendorIdOffset = (PCSZ)((PUCHAR)deviceDescriptor +
+ deviceDescriptor->VendorIdOffset);
+
+ RtlInitAnsiString(&ansiString, vendorIdOffset);
+
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT) vendorIDLength;
+ unicodeString.Buffer = vendorID;
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to convert vendor id to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceNameLegacyPage0x80;
+ }
+
+ //
+ // If there are spaces in the id, set NULL at the first space.
+ //
+ tmpPtr = wcschr(vendorID, L' ');
+ if (tmpPtr != NULL) {
+ *tmpPtr = WNULL;
+ }
+
+ status = RtlUnicodeStringCatString(&unicodeDeviceName, vendorID);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to concatenate vendor ID to device name.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceNameLegacyPage0x80;
+ }
+
+ RtlUnicodeStringCatString(&unicodeDeviceName, L"_");
+ }
+
+ if (productIDLength) {
+
+ PCSZ productIdOffset;
+
+ productIdOffset = (PCSZ)((PUCHAR)deviceDescriptor +
+ deviceDescriptor->ProductIdOffset);
+
+ RtlInitAnsiString(&ansiString, productIdOffset);
+
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT) productIDLength;
+ unicodeString.Buffer = productID;
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to convert product id to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceNameLegacyPage0x80;
+ }
+
+ //
+ // If there are spaces in the id, set NULL at the first space.
+ //
+ tmpPtr = wcschr(productID, L' ');
+ if (tmpPtr != NULL) {
+ *tmpPtr = WNULL;
+ }
+
+ status = RtlUnicodeStringCatString(&unicodeDeviceName, productID);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to concatenate product ID to device name.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceNameLegacyPage0x80;
+ }
+
+ RtlUnicodeStringCatString(&unicodeDeviceName, L"_");
+ }
+
+ //
+ // Serial number
+ //
+ if (serialIDLength) {
+
+ PCSZ serialNumberOffset;
+
+ serialNumberOffset = (PCSZ)((PUCHAR)deviceDescriptor +
+ deviceDescriptor->SerialNumberOffset);
+
+ RtlInitAnsiString(&ansiString, serialNumberOffset);
+
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT) serialIDLength;
+ unicodeString.Buffer = serialID;
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to convert serial number to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceNameLegacyPage0x80;
+ }
+
+ //
+ // If there are spaces in the id, set NULL at the first space.
+ //
+ tmpPtr = wcschr(serialID, L' ');
+ if (tmpPtr != NULL) {
+ *tmpPtr = WNULL;
+ }
+
+ status = RtlUnicodeStringCatString(&unicodeDeviceName, serialID);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Failed to concatenate serial number to device name.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceNameLegacyPage0x80;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Device Name is %ws.\n",
+ DeviceInfo,
+ deviceName));
+
+__Exit_DsmpBuildDeviceNameLegacyPage0x80:
+
+ if (vendorID) {
+ DsmpFreePool(vendorID);
+ }
+
+ if (productID) {
+ DsmpFreePool(productID);
+ }
+
+ if (serialID) {
+ DsmpFreePool(serialID);
+ }
+
+ if (deviceName && !NT_SUCCESS(status)) {
+ DsmpFreePool(deviceName);
+ deviceName = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceNameLegacyPage0x80 (DevInfo %p): Exiting function with deviceName %ws.\n",
+ DeviceInfo,
+ deviceName));
+
+ return deviceName;
+}
+
+
+
+PWSTR
+DsmpBuildDeviceName(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_reads_(SerialNumberLength) IN PSTR SerialNumber,
+ _In_ IN SIZE_T SerialNumberLength
+ )
+/*++
+
+Routine Description:
+
+ Construct a string from VendorId, ProductId, and SerialNumber (page 0x83
+ identifiers) of the device.
+
+Arguements:
+
+ DeviceInfo - Device Extension
+ SerialNumber - Device serial number built from appropriate page 0x83 identifier
+ SerialNumberLength - Length (in chars) of the passed in serial number buffer
+
+Return Value :
+
+ Device name if it was built successfully.
+ NULL in case of failure.
+
+--*/
+{
+ PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor;
+ PWCHAR deviceName = NULL;
+ PWCHAR tmpPtr;
+ PWCHAR vendorID = NULL;
+ PWCHAR productID = NULL;
+ PWCHAR serialID = NULL;
+ ANSI_STRING ansiString;
+ UNICODE_STRING unicodeString;
+ UNICODE_STRING unicodeDeviceName;
+ SIZE_T vendorIDLength = 0;
+ SIZE_T productIDLength = 0;
+ SIZE_T serialIDLength = 0;
+ ULONG offset;
+ SIZE_T sizeNeeded;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ deviceDescriptor = &(DeviceInfo->Descriptor);
+
+ //
+ // Save the vendorid, productid, and serialnumber offset
+ // in Device Descriptor
+ //
+ offset = deviceDescriptor->VendorIdOffset;
+ if ((offset != 0) && (offset != MAXULONG)) {
+
+ vendorIDLength = (strlen(((PCHAR)deviceDescriptor) + offset) * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ offset = deviceDescriptor->ProductIdOffset;
+ if ((offset != 0) && (offset != -1)) {
+
+ productIDLength = (strlen(((PCHAR)deviceDescriptor) + offset) * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ if (SerialNumber) {
+
+ serialIDLength = (SerialNumberLength * sizeof(WCHAR)) + WNULL_SIZE;
+ }
+
+ //
+ // Allocate buffers to use to convert the IDs from ANSI to Unicode and
+ // eventually build the device name.
+ //
+ if (vendorIDLength > 0) {
+ vendorID = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, vendorIDLength, DSM_TAG_DEV_NAME);
+ if (!vendorID) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to allocate memory for vendor ID.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (productIDLength > 0) {
+ productID = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, productIDLength, DSM_TAG_DEV_NAME);
+ if (!productID) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to allocate memory for product ID.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (serialIDLength > 0) {
+ serialID = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, serialIDLength, DSM_TAG_DEV_NAME);
+ if (!serialID) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to allocate memory for serial ID.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ sizeNeeded = vendorIDLength + productIDLength + serialIDLength;
+ if (sizeNeeded > 0) {
+
+ //
+ // Account for the terminating NULL if serial id is empty.
+ //
+
+ sizeNeeded += (serialIDLength ? 0 : WNULL_SIZE);
+
+ deviceName = (PWCHAR)DsmpAllocatePool(NonPagedPoolNx, sizeNeeded, DSM_TAG_DEV_NAME);
+ if (!deviceName) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to allocate memory for device name.\n",
+ DeviceInfo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ } else {
+
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ goto __Exit_DsmpBuildDeviceName;
+ }
+
+ //
+ // Build the NULL terminated device name whose format is :
+ //
+ // VendorId_ProductId_SerialNumber
+ //
+
+ unicodeDeviceName.Length = 0;
+ unicodeDeviceName.MaximumLength = (USHORT)sizeNeeded;
+ unicodeDeviceName.Buffer = deviceName;
+
+ if (vendorIDLength) {
+
+ PCSZ vendorIdOffset;
+
+ vendorIdOffset = (PCSZ)((PUCHAR)deviceDescriptor +
+ deviceDescriptor->VendorIdOffset);
+
+ RtlInitAnsiString(&ansiString, vendorIdOffset);
+
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT) vendorIDLength;
+ unicodeString.Buffer = vendorID;
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to convert vendor id to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceName;
+ }
+
+ //
+ // If there are spaces in the id, set NULL at the first space.
+ //
+ tmpPtr = wcschr(vendorID, L' ');
+ if (tmpPtr != NULL) {
+ *tmpPtr = WNULL;
+ }
+
+ status = RtlUnicodeStringCatString(&unicodeDeviceName, vendorID);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to concatenate vendor ID to device name.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceName;
+ }
+
+ RtlUnicodeStringCatString(&unicodeDeviceName, L"_");
+ }
+
+ if (productIDLength) {
+
+ PCSZ productIdOffset;
+
+ productIdOffset = (PCSZ)((PUCHAR)deviceDescriptor +
+ deviceDescriptor->ProductIdOffset);
+
+ RtlInitAnsiString(&ansiString, productIdOffset);
+
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT) productIDLength;
+ unicodeString.Buffer = productID;
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to convert product id to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceName;
+ }
+
+ //
+ // If there are spaces in the id, set NULL at the first space.
+ //
+ tmpPtr = wcschr(productID, L' ');
+ if (tmpPtr != NULL) {
+ *tmpPtr = WNULL;
+ }
+
+ status = RtlUnicodeStringCatString(&unicodeDeviceName, productID);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to concatenate product ID to device name.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceName;
+ }
+
+ RtlUnicodeStringCatString(&unicodeDeviceName, L"_");
+ }
+
+ //
+ // Serial number
+ //
+ if (serialIDLength) {
+
+ PSTR serialNumberOffset;
+
+ serialNumberOffset = SerialNumber;
+
+ RtlInitAnsiString(&ansiString, serialNumberOffset);
+
+ unicodeString.Length = 0;
+ unicodeString.MaximumLength = (USHORT) serialIDLength;
+ unicodeString.Buffer = serialID;
+
+ status = RtlAnsiStringToUnicodeString(&unicodeString,
+ &ansiString,
+ FALSE);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to convert serial number to unicode string.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceName;
+ }
+
+ //
+ // If there are spaces in the id, set NULL at the first space.
+ //
+ tmpPtr = wcschr(serialID, L' ');
+ if (tmpPtr != NULL) {
+ *tmpPtr = WNULL;
+ }
+
+ status = RtlUnicodeStringCatString(&unicodeDeviceName, serialID);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Failed to concatenate serial number to device name.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpBuildDeviceName;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Device Name is %ws.\n",
+ DeviceInfo,
+ deviceName));
+
+__Exit_DsmpBuildDeviceName:
+
+ if (vendorID) {
+ DsmpFreePool(vendorID);
+ }
+
+ if (productID) {
+ DsmpFreePool(productID);
+ }
+
+ if (serialID) {
+ DsmpFreePool(serialID);
+ }
+
+ if (deviceName && !NT_SUCCESS(status)) {
+ DsmpFreePool(deviceName);
+ deviceName = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpBuildDeviceName (DevInfo %p): Exiting function with deviceName %ws.\n",
+ DeviceInfo,
+ deviceName));
+
+ return deviceName;
+}
+
+
+NTSTATUS
+DsmpApplyDeviceNameCorrection(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_reads_(DeviceNameLegacyLen) PWSTR DeviceNameLegacy,
+ _In_ IN SIZE_T DeviceNameLegacyLen,
+ _In_reads_(DeviceNameLen) PWSTR DeviceName,
+ _In_ IN SIZE_T DeviceNameLen
+ )
+/*++
+
+Routine Description:
+
+ If the registry has a key name built with a legacy device name, this
+ function updates the key name with the current device name.
+
+Arguements:
+
+ DeviceInfo - Device instance
+ DeviceNameLegacy - Device name built using legacy methods.
+ DeviceNameLegacyLen - Number of chars (including NULL) of the DeviceNameLegacy buffer.
+ DeviceName - Device name built using current methods.
+ DeviceNameLen - Number of chars (including NULL) of the DeviceName buffer.
+
+Return Value :
+
+ STATUS_SUCCESS if the device's key was updated successfully.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE lbSettingsKey = NULL;
+ HANDLE deviceKeyLegacy = NULL;
+ HANDLE deviceKey = NULL;
+ OBJECT_ATTRIBUTES objectAttributes;
+ NTSTATUS status;
+ UNICODE_STRING deviceNameLegacy;
+ UNICODE_STRING deviceName;
+
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(DeviceNameLen);
+ UNREFERENCED_PARAMETER(DeviceNameLegacyLen);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ //
+ // First open LoadBalanceSettings key under the service key.
+ //
+ status = DsmpOpenLoadBalanceSettingsKey(KEY_ALL_ACCESS, &lbSettingsKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Failed to open LB Settings key. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ goto __Exit_DsmpApplyDeviceNameCorrection;
+ }
+
+ RtlInitUnicodeString(&deviceNameLegacy, DeviceNameLegacy);
+
+ InitializeObjectAttributes(&objectAttributes,
+ &deviceNameLegacy,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ lbSettingsKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ //
+ // Open the old device key under DsmLoadBalanceSettings key.
+ // The name of this key is the one built using legacy methods - either a
+ // serial number from VPD page 0x80 or an aliased serial number from VPD
+ // page 0x83.
+ //
+ status = ZwOpenKey(&deviceKeyLegacy,
+ KEY_ALL_ACCESS,
+ &objectAttributes);
+
+ if (NT_SUCCESS(status)) {
+
+ ULONG disposition;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Key with old device name exists.\n",
+ DeviceInfo));
+
+ RtlInitUnicodeString(&deviceName, DeviceName);
+
+ InitializeObjectAttributes(&objectAttributes,
+ &deviceName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ lbSettingsKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ //
+ // Since the old name key exists, create one with the new name.
+ //
+ status = ZwCreateKey(&deviceKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ &disposition);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // The new key shouldn't exist if the old one does.
+ // If it does, it indicates a error occured the previous time
+ // this was tried, so just copy over the old subtree anyways now.
+ //
+ DSM_ASSERT(disposition == REG_CREATED_NEW_KEY);
+
+ //
+ // Copy over the entire subtree of the old key over to the new key.
+ //
+ status = DsmpRegCopyTree(deviceKeyLegacy, deviceKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Failed to copy over the old device key's subtree. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ goto __Exit_DsmpApplyDeviceNameCorrection;
+ }
+
+ //
+ // Delete the old key name.
+ //
+ status = DsmpRegDeleteTree(deviceKeyLegacy);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Failed to delete the old device key's subtree. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ goto __Exit_DsmpApplyDeviceNameCorrection;
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Failed to create the new device key. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ goto __Exit_DsmpApplyDeviceNameCorrection;
+ }
+
+ } else if (status == STATUS_INVALID_HANDLE ||
+ status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Key with old device name does not exist.\n",
+ DeviceInfo));
+
+ status = STATUS_SUCCESS;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Failed to query key with old device name. Status %x\n",
+ DeviceInfo,
+ status));
+ }
+
+__Exit_DsmpApplyDeviceNameCorrection:
+
+ if (deviceKey) {
+ ZwClose(deviceKey);
+ }
+
+ if (deviceKeyLegacy) {
+ ZwClose(deviceKeyLegacy);
+ }
+
+ if (lbSettingsKey) {
+ ZwClose(lbSettingsKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpApplyDeviceNameCorrection (DevInfo %p): Exiting function with status %x\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryDeviceLBPolicyFromRegistry(
+ _In_ PDSM_DEVICE_INFO DeviceInfo,
+ _In_ PWSTR RegistryKeyName,
+ _Inout_ PDSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Inout_ PULONGLONG PreferredPath,
+ _Inout_ PUCHAR ExplicitlySet
+ )
+/*++
+
+Routine Description:
+
+ Query the saved load balance policy and preferred path for this device from
+ the registry.
+ Also returns whether this setting was explicitly set via WMI call to SetLBPolicy,
+ (as opposed to the settings being made based on defaults determined through the
+ storage's ALUA capabilities).
+
+Arguements:
+
+ DeviceInfo - The instance of the LUN through a paricular path
+ RegistryKeyName - DeviceName representing this LUN
+ LoadBalanceType - Type of LB policy.
+ PreferredPath - The preferred path for the device.
+ ExplicitlySet - Flag reflecting if LB policy was explicitly set.
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully query the registry for the info.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE lbSettingsKey = NULL;
+ HANDLE deviceKey = NULL;
+ UNICODE_STRING subKeyName;
+ OBJECT_ATTRIBUTES objectAttributes;
+ NTSTATUS status;
+ UNICODE_STRING keyValueName;
+ ULONG length;
+ struct _explicitSet {
+ KEY_VALUE_PARTIAL_INFORMATION KeyValueInfo;
+ UCHAR Data;
+ } explicitSet;
+ struct _preferredPath {
+ KEY_VALUE_PARTIAL_INFORMATION KeyValueInfo;
+ ULONGLONG Data;
+ } preferredPath;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ //
+ // Query the Load Balance settings for the given device from the registry.
+ // First open LoadBalanceSettings key under the service key.
+ //
+ status = DsmpOpenLoadBalanceSettingsKey(KEY_ALL_ACCESS, &lbSettingsKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): Failed to open LB Settings key. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ goto __Exit_DsmpQueryDeviceLBPolicyFromRegistry;
+ }
+
+ RtlInitUnicodeString(&subKeyName, RegistryKeyName);
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ lbSettingsKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ //
+ // Create or Open the device key under DsmLoadBalanceSettings key.
+ // The name of this key is the one built in DsmpBuildDeviceName
+ //
+ status = ZwCreateKey(&deviceKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (NT_SUCCESS(status)) {
+
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT |
+ RTL_QUERY_REGISTRY_REQUIRED |
+ RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_LOAD_BALANCE_POLICY;
+ queryTable[0].EntryContext = LoadBalanceType;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ queryTable,
+ deviceKey,
+ NULL);
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): LB Policy is %d.\n",
+ DeviceInfo,
+ *LoadBalanceType));
+
+ } else if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ //
+ // The device key must have been newly created.
+ // Set the default load balance policy for this device
+ //
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ DSM_LOAD_BALANCE_POLICY,
+ REG_DWORD,
+ LoadBalanceType,
+ sizeof(ULONG));
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): Failed to write LB policy. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ goto __Exit_DsmpQueryDeviceLBPolicyFromRegistry;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ RtlInitUnicodeString(&keyValueName, DSM_POLICY_EXPLICITLY_SET);
+ status = ZwQueryValueKey(deviceKey,
+ &keyValueName,
+ KeyValuePartialInformation,
+ &explicitSet,
+ sizeof(explicitSet),
+ &length);
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(explicitSet.KeyValueInfo.DataLength == sizeof(UCHAR));
+
+ *ExplicitlySet = *((UCHAR UNALIGNED *)&(explicitSet.KeyValueInfo.Data));
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): ExplicitlySet is %!bool!.\n",
+ DeviceInfo,
+ *ExplicitlySet));
+
+ } else if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ *ExplicitlySet = FALSE;
+
+ //
+ // The device key must have been newly created.
+ // Set ExplicitlySet to 0 to indicate that the default was used.
+ //
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ DSM_POLICY_EXPLICITLY_SET,
+ REG_BINARY,
+ ExplicitlySet,
+ sizeof(UCHAR));
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): Failed to write ExplicitlySet. Status %x.\n",
+ DeviceInfo,
+ status));
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ RtlInitUnicodeString(&keyValueName, DSM_PREFERRED_PATH);
+ status = ZwQueryValueKey(deviceKey,
+ &keyValueName,
+ KeyValuePartialInformation,
+ &preferredPath,
+ sizeof(preferredPath),
+ &length);
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(preferredPath.KeyValueInfo.DataLength == sizeof(ULONGLONG));
+
+ *PreferredPath = *((ULONGLONG UNALIGNED *)&(preferredPath.KeyValueInfo.Data));
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): PreferredPath is %I64x.\n",
+ DeviceInfo,
+ *PreferredPath));
+
+ } else if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ *PreferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+
+ //
+ // The device key must have been newly created.
+ // Set a bogus preferred path as default.
+ //
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ DSM_PREFERRED_PATH,
+ REG_BINARY,
+ PreferredPath,
+ sizeof(ULONGLONG));
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): Failed to write PreferredPath. Status %x.\n",
+ DeviceInfo,
+ status));
+ }
+ }
+ }
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): Failed to create LB policy registry key. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ deviceKey = NULL;
+ }
+
+__Exit_DsmpQueryDeviceLBPolicyFromRegistry:
+
+ if (deviceKey) {
+ ZwClose(deviceKey);
+ }
+
+ if (lbSettingsKey) {
+ ZwClose(lbSettingsKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDeviceLBPolicyFromRegistry (DevInfo %p): Exiting function with status %x.\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryTargetLBPolicyFromRegistry(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _Out_ OUT PDSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Out_ OUT PULONGLONG PreferredPath
+ )
+/*++
+
+Routine Description:
+
+ Query the load balance policy for the VID/PID of the passed in device from
+ the registry if it has been set.
+
+Arguements:
+
+ DeviceInfo - Device's whose VID/PID we need to compare against.
+ LoadBalanceType - Type of LB policy.
+ PreferredPath - The preferred path for the device.
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully query the registry for the info.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE targetsLBSettingKey = NULL;
+ HANDLE targetKey = NULL;
+ UNICODE_STRING subKeyName;
+ OBJECT_ATTRIBUTES objectAttributes;
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+ UNICODE_STRING keyValueName;
+ ULONG length;
+ struct _preferredPath {
+ KEY_VALUE_PARTIAL_INFORMATION KeyValueInfo;
+ ULONGLONG Data;
+ } preferredPath;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ if (!LoadBalanceType || !PreferredPath) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Invalid parameter.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpQueryTargetLBPolicyFromRegistry;
+ }
+
+ if (!DeviceInfo->Group->HardwareId) {
+
+ status = STATUS_UNSUCCESSFUL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Couldn't build hardware id for passed in device.\n",
+ DeviceInfo));
+
+ goto __Exit_DsmpQueryTargetLBPolicyFromRegistry;
+ }
+
+ //
+ // Query the Load Balance settings for the given target from the registry.
+ // First open TargetsLoadBalanceSetting key under the service key.
+ //
+ status = DsmpOpenTargetsLoadBalanceSettingKey(KEY_ALL_ACCESS, &targetsLBSettingKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Failed to open Targets LB Setting key. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ goto __Exit_DsmpQueryTargetLBPolicyFromRegistry;
+ }
+
+ RtlInitUnicodeString(&subKeyName, DeviceInfo->Group->HardwareId);
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ targetsLBSettingKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ //
+ // Open the VID/PID key under DsmTargetsLoadBalanceSetting key.
+ //
+ status = ZwOpenKey(&targetKey, KEY_ALL_ACCESS, &objectAttributes);
+
+ if (NT_SUCCESS(status)) {
+
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT |
+ RTL_QUERY_REGISTRY_REQUIRED |
+ RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_LOAD_BALANCE_POLICY;
+ queryTable[0].EntryContext = LoadBalanceType;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ targetKey,
+ queryTable,
+ targetKey,
+ NULL);
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): LB Policy is %d.\n",
+ DeviceInfo,
+ *LoadBalanceType));
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Failed to query LB Policy - error %x.\n",
+ DeviceInfo,
+ status));
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ RtlInitUnicodeString(&keyValueName, DSM_PREFERRED_PATH);
+ status = ZwQueryValueKey(targetKey,
+ &keyValueName,
+ KeyValuePartialInformation,
+ &preferredPath,
+ sizeof(preferredPath),
+ &length);
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(preferredPath.KeyValueInfo.DataLength == sizeof(ULONGLONG));
+
+ *PreferredPath = *((ULONGLONG UNALIGNED *)&(preferredPath.KeyValueInfo.Data));
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): PreferredPath is %I64x.\n",
+ DeviceInfo,
+ *PreferredPath));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Failed to query PreferredPath. Status %x.\n",
+ DeviceInfo,
+ status));
+ }
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Failed to open LB policy registry key. Status %x.\n",
+ DeviceInfo,
+ status));
+
+ targetKey = NULL;
+ }
+
+__Exit_DsmpQueryTargetLBPolicyFromRegistry:
+
+ if (targetKey) {
+ ZwClose(targetKey);
+ }
+
+ if (targetsLBSettingKey) {
+ ZwClose(targetsLBSettingKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryTargetLBPolicyFromRegistry (DevInfo %p): Exiting function with status %x.\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryDsmLBPolicyFromRegistry(
+ _Out_ OUT PDSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Out_ OUT PULONGLONG PreferredPath
+ )
+/*++
+
+Routine Description:
+
+ Query the overall load balance policy for MSDSM controlled devices from
+ the registry if it has been set.
+
+Arguements:
+
+ LoadBalanceType - Type of LB policy.
+ PreferredPath - The preferred path for the device.
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully query the registry for the info.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE parametersKey = NULL;
+ NTSTATUS status = STATUS_INVALID_PARAMETER;
+ UNICODE_STRING keyValueName;
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+ ULONG length;
+ struct _preferredPath {
+ KEY_VALUE_PARTIAL_INFORMATION KeyValueInfo;
+ ULONGLONG Data;
+ } preferredPath;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: Entering function.\n"));
+
+ if (!LoadBalanceType || !PreferredPath) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: Invalid parameter.\n"));
+
+ goto __Exit_DsmpQueryDsmLBPolicyFromRegistry;
+ }
+
+ //
+ // Query the overall default Load Balance settings for MSDSM from the registry.
+ // First open the Parameters key under the service key.
+ //
+ status = DsmpOpenDsmServicesParametersKey(KEY_ALL_ACCESS, &parametersKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: Failed to open Parameters key. Status %x.\n",
+ status));
+
+ goto __Exit_DsmpQueryDsmLBPolicyFromRegistry;
+ }
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT |
+ RTL_QUERY_REGISTRY_REQUIRED |
+ RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_LOAD_BALANCE_POLICY;
+ queryTable[0].EntryContext = LoadBalanceType;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ parametersKey,
+ queryTable,
+ parametersKey,
+ NULL);
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: LB Policy is %d.\n",
+ *LoadBalanceType));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: Failed to query LB policy. Status %x.\n",
+ status));
+
+ goto __Exit_DsmpQueryDsmLBPolicyFromRegistry;
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ RtlInitUnicodeString(&keyValueName, DSM_PREFERRED_PATH);
+ status = ZwQueryValueKey(parametersKey,
+ &keyValueName,
+ KeyValuePartialInformation,
+ &preferredPath,
+ sizeof(preferredPath),
+ &length);
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(preferredPath.KeyValueInfo.DataLength == sizeof(ULONGLONG));
+
+ *PreferredPath = *((ULONGLONG UNALIGNED *)&(preferredPath.KeyValueInfo.Data));
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: PreferredPath is %I64x.\n",
+ *PreferredPath));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: Failed to query PreferredPath. Status %x.\n",
+ status));
+ }
+ }
+
+__Exit_DsmpQueryDsmLBPolicyFromRegistry:
+
+ if (parametersKey) {
+ ZwClose(parametersKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryDsmLBPolicyFromRegistry: Exiting function with status %x.\n",
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetDsmLBPolicyInRegistry(
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ )
+/*++
+
+Routine Description:
+
+ Set the overall load balance policy for MSDSM controlled devices in
+ the registry.
+ Note: If the policy specified is 0, remove the currently set values
+ for policy and preferred path.
+
+Arguements:
+
+ LoadBalanceType - Type of LB policy.
+ PreferredPath - The preferred path for devices controlled by DSM.
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully set the info in the registry.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE parametersKey = NULL;
+ NTSTATUS status;
+ UNICODE_STRING lbPolicyValueName;
+ UNICODE_STRING preferredPathValueName;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetDsmLBPolicyInRegistry: Entering function.\n"));
+
+ //
+ // First open the Parameters key under the service key.
+ //
+ status = DsmpOpenDsmServicesParametersKey(KEY_ALL_ACCESS, &parametersKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetDsmLBPolicyInRegistry: Failed to open Parameters key. Status %x.\n",
+ status));
+
+ goto __Exit_DsmpSetDsmLBPolicyInRegistry;
+ }
+
+ RtlInitUnicodeString(&lbPolicyValueName, DSM_LOAD_BALANCE_POLICY);
+ RtlInitUnicodeString(&preferredPathValueName, DSM_PREFERRED_PATH);
+
+ //
+ // If the LB policy is specified as 0, we need to delete the values.
+ //
+ if (LoadBalanceType < DSM_LB_FAILOVER) {
+
+ status = ZwDeleteValueKey(parametersKey, &preferredPathValueName);
+
+ if (NT_SUCCESS(status) || status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ status = ZwDeleteValueKey(parametersKey, &lbPolicyValueName);
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetDsmLBPolicyInRegistry: Failed to delete either preferredPath or lbPolicy. Status %x.\n",
+ status));
+ }
+ } else {
+
+ status = ZwSetValueKey(parametersKey,
+ &lbPolicyValueName,
+ 0,
+ REG_DWORD,
+ &LoadBalanceType,
+ sizeof(ULONG));
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetDsmLBPolicyInRegistry: Failed to set LB policy in registry. Status %x.\n",
+ status));
+
+ goto __Exit_DsmpSetDsmLBPolicyInRegistry;
+ }
+
+ status = ZwSetValueKey(parametersKey,
+ &preferredPathValueName,
+ 0,
+ REG_BINARY,
+ &PreferredPath,
+ sizeof(ULONGLONG));
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetDsmLBPolicyInRegistry: Failed to set preferred path in registry. Status %x.\n",
+ status));
+ }
+ }
+
+__Exit_DsmpSetDsmLBPolicyInRegistry:
+
+ if (parametersKey) {
+ ZwClose(parametersKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetDsmLBPolicyInRegistry: Exiting function with status %x.\n",
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetVidPidLBPolicyInRegistry(
+ _In_ IN PWSTR TargetHardwareId,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _In_ IN ULONGLONG PreferredPath
+ )
+/*++
+
+Routine Description:
+
+ Set the default load balance policy for MSDSM controlled devices for
+ a particular target VID/PID in the registry.
+ Note: If the policy specified is 0, remove the subkey that matches
+ the passed in TargetHardwareId.
+
+Arguements:
+
+ TargetHardwareId - The VID/PID for which a default LB policy is being set.
+ LoadBalanceType - Type of LB policy.
+ PreferredPath - The preferred path for devices controlled by DSM.
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully set the info in the registry.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE targetsLBSettingKey = NULL;
+ HANDLE targetSubKey = NULL;
+ NTSTATUS status;
+ UNICODE_STRING vidPidKeyName;
+ UNICODE_STRING lbPolicyValueName;
+ UNICODE_STRING preferredPathValueName;
+ OBJECT_ATTRIBUTES objectAttributes;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetVidPidLBPolicyInRegistry (%ws): Entering function.\n",
+ TargetHardwareId));
+
+ //
+ // First open the DsmTargetsLoadBalanceSetting key under the service's parameters key.
+ //
+ status = DsmpOpenTargetsLoadBalanceSettingKey(KEY_ALL_ACCESS, &targetsLBSettingKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetVidPidLBPolicyInRegistry (%ws): Failed to open Targets Policy settings key. Status %x.\n",
+ TargetHardwareId,
+ status));
+
+ goto __Exit_DsmpSetVidPidLBPolicyInRegistry;
+ }
+
+ RtlInitUnicodeString(&vidPidKeyName, TargetHardwareId);
+ InitializeObjectAttributes(&objectAttributes,
+ &vidPidKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ targetsLBSettingKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ //
+ // If the LB policy is specified as 0, we need to delete the values.
+ //
+ if (LoadBalanceType < DSM_LB_FAILOVER) {
+
+ //
+ // Open the VID/PID key under DsmTargetsLoadBalanceSetting key.
+ //
+ status = ZwOpenKey(&targetSubKey, KEY_ALL_ACCESS, &objectAttributes);
+
+ if (NT_SUCCESS(status)) {
+
+ status = ZwDeleteKey(targetSubKey);
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetVidPidLBPolicyInRegistry (%ws): Failed to either open or delete. Status %x.\n",
+ TargetHardwareId,
+ status));
+ }
+ } else {
+
+ RtlInitUnicodeString(&lbPolicyValueName, DSM_LOAD_BALANCE_POLICY);
+ RtlInitUnicodeString(&preferredPathValueName, DSM_PREFERRED_PATH);
+
+ status = ZwCreateKey(&targetSubKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetVidPidLBPolicyInRegistry (%ws): Failed to open/create key in registry. Status %x.\n",
+ TargetHardwareId,
+ status));
+
+ goto __Exit_DsmpSetVidPidLBPolicyInRegistry;
+ }
+
+ status = ZwSetValueKey(targetSubKey,
+ &lbPolicyValueName,
+ 0,
+ REG_DWORD,
+ &LoadBalanceType,
+ sizeof(ULONG));
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetVidPidLBPolicyInRegistry (%ws): Failed to set LB policy in registry. Status %x.\n",
+ TargetHardwareId,
+ status));
+
+ goto __Exit_DsmpSetVidPidLBPolicyInRegistry;
+ }
+
+ status = ZwSetValueKey(targetSubKey,
+ &preferredPathValueName,
+ 0,
+ REG_BINARY,
+ &PreferredPath,
+ sizeof(ULONGLONG));
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpSetVidPidLBPolicyInRegistry (%ws): Failed to set preferred path in registry. Status %x.\n",
+ TargetHardwareId,
+ status));
+ }
+ }
+
+__Exit_DsmpSetVidPidLBPolicyInRegistry:
+
+ if (targetSubKey) {
+ ZwClose(targetSubKey);
+ }
+
+ if (targetsLBSettingKey) {
+ ZwClose(targetsLBSettingKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpSetVidPidLBPolicyInRegistry (%ws): Exiting function with status %x.\n",
+ TargetHardwareId,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpOpenLoadBalanceSettingsKey(
+ _In_ IN ACCESS_MASK AccessMask,
+ _Out_ OUT PHANDLE LoadBalanceSettingsKey
+ )
+/*++
+
+Routine Description:
+
+ Open the device key in the registry.
+
+ NOTE: It is the responsibility of the caller to close the returned handle.
+
+Arguements:
+
+ AccessMask - Requested access with which to open key
+ LoadBalanceSettingsKey - handle of the key that is returned to the caller
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully open the registry key.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE serviceKey = NULL;
+ HANDLE parametersKey = NULL;
+ PUNICODE_STRING registryPath = &(gDsmInitData.DsmWmiInfo.RegistryPath);
+ OBJECT_ATTRIBUTES objectAttributes;
+ UNICODE_STRING parametersKeyName;
+ UNICODE_STRING subKeyName;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpOpenLoadBalanceSettingsKey (RegPath %p): Entering function.\n",
+ registryPath));
+
+ *LoadBalanceSettingsKey = NULL;
+
+ //
+ // First check if registry path is available for msdsm.
+ //
+ if (!registryPath->Buffer) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenLoadBalanceSettingsKey (RegPath %p): Registry Path not set.\n",
+ registryPath));
+
+ goto __Exit_DsmpOpenLoadBalanceSettingsKey;
+ }
+
+ //
+ // Open the service key first
+ //
+ InitializeObjectAttributes(&objectAttributes,
+ registryPath,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ NULL,
+ NULL);
+
+ status = ZwOpenKey(&serviceKey,
+ AccessMask,
+ &objectAttributes);
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Open Parameters key under the Service key
+ //
+ RtlInitUnicodeString(&parametersKeyName, DSM_SERVICE_PARAMETERS);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ &parametersKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ serviceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(&parametersKey,
+ AccessMask,
+ &objectAttributes);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Open LoadBalanceSettings key under the Parameters key
+ //
+ RtlInitUnicodeString(&subKeyName, DSM_LOAD_BALANCE_SETTINGS);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ parametersKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwCreateKey(LoadBalanceSettingsKey,
+ AccessMask,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenLoadBalanceSettingsKey (RegPath %p): Failed to open/create LBSettings key. Status %x.\n",
+ registryPath,
+ status));
+
+ *LoadBalanceSettingsKey = NULL;
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenLoadBalanceSettingsKey (RegPath %p): Failed to open parameters key. Status %x.\n",
+ registryPath,
+ status));
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenLoadBalanceSettingsKey (RegPath %p): Failed to open service key %ws. Status %x.\n",
+ registryPath,
+ registryPath->Buffer,
+ status));
+ }
+
+__Exit_DsmpOpenLoadBalanceSettingsKey:
+
+ if (parametersKey) {
+ ZwClose(parametersKey);
+ }
+
+ if (serviceKey) {
+ ZwClose(serviceKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpOpenLoadBalanceSettingsKey (RegPath %p): Exiting function with status %x.\n",
+ registryPath,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpOpenTargetsLoadBalanceSettingKey(
+ _In_ IN ACCESS_MASK AccessMask,
+ _Out_ OUT PHANDLE TargetsLoadBalanceSettingKey
+ )
+/*++
+
+Routine Description:
+
+ Open the target key in the registry.
+
+ NOTE: It is the responsibility of the caller to close the returned handle.
+
+Arguements:
+
+ AccessMask - Requested access with which to open key
+ LoadBalanceSettingsKey - handle of the key that is returned to the caller
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully open the registry key.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE serviceKey = NULL;
+ HANDLE parametersKey = NULL;
+ PUNICODE_STRING registryPath = &(gDsmInitData.DsmWmiInfo.RegistryPath);
+ OBJECT_ATTRIBUTES objectAttributes;
+ UNICODE_STRING parametersKeyName;
+ UNICODE_STRING subKeyName;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpOpenTargetsLoadBalanceSettingKey (RegPath %p): Entering function.\n",
+ registryPath));
+
+ if (!TargetsLoadBalanceSettingKey) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenTargetsLoadBalanceSettingKey (RegPath %p): Invalid parameter.\n",
+ registryPath));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpOpenTargetsLoadBalanceSettingKey;
+ }
+
+ *TargetsLoadBalanceSettingKey = NULL;
+
+ //
+ // First check if registry path is available for msdsm.
+ //
+ if (!registryPath->Buffer) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenTargetsLoadBalanceSettingKey (RegPath %p): Registry Path not set.\n",
+ registryPath));
+
+ goto __Exit_DsmpOpenTargetsLoadBalanceSettingKey;
+ }
+
+ //
+ // Open the service key first
+ //
+ InitializeObjectAttributes(&objectAttributes,
+ registryPath,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ NULL,
+ NULL);
+
+ status = ZwOpenKey(&serviceKey,
+ AccessMask,
+ &objectAttributes);
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Open Parameters key under the Service key
+ //
+ RtlInitUnicodeString(&parametersKeyName, DSM_SERVICE_PARAMETERS);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ &parametersKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ serviceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(&parametersKey,
+ AccessMask,
+ &objectAttributes);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Open LoadBalanceSettings key under the Parameters key
+ //
+ RtlInitUnicodeString(&subKeyName, DSM_TARGETS_LOAD_BALANCE_SETTING);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ parametersKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwCreateKey(TargetsLoadBalanceSettingKey,
+ AccessMask,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenTargetsLoadBalanceSettingKey (RegPath %p): Failed to open/create TargetsLBSetting key. Status %x.\n",
+ registryPath,
+ status));
+
+ *TargetsLoadBalanceSettingKey = NULL;
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenTargetsLoadBalanceSettingKey (RegPath %p): Failed to open parameters key. Status %x.\n",
+ registryPath,
+ status));
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenTargetsLoadBalanceSettingKey (RegPath %p): Failed to open service key %ws. Status %x.\n",
+ registryPath,
+ registryPath->Buffer,
+ status));
+ }
+
+__Exit_DsmpOpenTargetsLoadBalanceSettingKey:
+
+ if (parametersKey) {
+ ZwClose(parametersKey);
+ }
+
+ if (serviceKey) {
+ ZwClose(serviceKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpOpenTargetsLoadBalanceSettingKey (RegPath %p): Exiting function with status %x.\n",
+ registryPath,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpOpenDsmServicesParametersKey(
+ _In_ IN ACCESS_MASK AccessMask,
+ _Out_ OUT PHANDLE ParametersKey
+ )
+/*++
+
+Routine Description:
+
+ Open the DSM's Parameters key in the registry.
+
+ NOTE: It is the responsibility of the caller to close the returned handle.
+
+Arguements:
+
+ AccessMask - Requested access with which to open key
+ ParametersKey - handle of the key that is returned to the caller
+
+Return Value :
+
+ STATUS_SUCCESS if we were able to successfully open the registry key.
+
+ Appropriate NTSTATUS code on failure
+
+--*/
+{
+ HANDLE serviceKey = NULL;
+ PUNICODE_STRING registryPath = &(gDsmInitData.DsmWmiInfo.RegistryPath);
+ OBJECT_ATTRIBUTES objectAttributes;
+ UNICODE_STRING parametersKeyName;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpOpenDsmServicesParametersKey (RegPath %p): Entering function.\n",
+ registryPath));
+
+ if (!ParametersKey) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenDsmServicesParametersKey (RegPath %p): Invalid parameter.\n",
+ registryPath));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpOpenDsmServicesParametersKey;
+ }
+
+ *ParametersKey = NULL;
+
+ //
+ // First check if registry path is available for msdsm.
+ //
+ if (!registryPath->Buffer) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenDsmServicesParametersKey (RegPath %p): Registry Path not set.\n",
+ registryPath));
+
+ goto __Exit_DsmpOpenDsmServicesParametersKey;
+ }
+
+ //
+ // Open the service key first
+ //
+ InitializeObjectAttributes(&objectAttributes,
+ registryPath,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ NULL,
+ NULL);
+
+ status = ZwOpenKey(&serviceKey,
+ AccessMask,
+ &objectAttributes);
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Open Parameters key under the Service key
+ //
+ RtlInitUnicodeString(&parametersKeyName, DSM_SERVICE_PARAMETERS);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ &parametersKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ serviceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(ParametersKey,
+ AccessMask,
+ &objectAttributes);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenDsmServicesParametersKey (RegPath %p): Failed to open parameters key. Status %x.\n",
+ registryPath,
+ status));
+
+ *ParametersKey = NULL;
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpOpenDsmServicesParametersKey (RegPath %p): Failed to open service key %ws. Status %x.\n",
+ registryPath,
+ registryPath->Buffer,
+ status));
+ }
+
+__Exit_DsmpOpenDsmServicesParametersKey:
+
+ if (serviceKey) {
+ ZwClose(serviceKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpOpenDsmServicesParametersKey (RegPath %p): Exiting function with status %x.\n",
+ registryPath,
+ status));
+
+ return status;
+}
+
+NTSTATUS
+DsmpReportTargetPortGroupsSyncCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ UNREFERENCED_PARAMETER(DeviceObject);
+ UNREFERENCED_PARAMETER(Context);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroupsSyncCompletion: IRP %p, Context %p\n",
+ Irp, Context));
+
+ KeSetEvent(Irp->UserEvent, 0, FALSE);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+_Success_(return==0)
+NTSTATUS
+DsmpReportTargetPortGroups(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Outptr_result_buffer_maybenull_(*TargetPortGroupsInfoLength) PUCHAR *TargetPortGroupsInfo,
+ _Out_ PULONG TargetPortGroupsInfoLength
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to send down ReportTargetPortGroups request synchronously.
+ Used if device supports ALUA.
+
+ Note: This routine will allocate memory for the TPG info. It is the
+ responsibility of the caller to free this buffer, but only if the function
+ returns STATUS_SUCCESS.
+
+Arguments:
+
+ DeviceObject - The port PDO to which the command should be sent.
+ TargetPortGroupsInfo - buffer containing the returned data.
+ TargetPortGroupsInfoLength - size of the returned buffer.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ PSPC3_CDB_REPORT_TARGET_PORT_GROUPS cdb;
+ NTSTATUS status = STATUS_SUCCESS;
+ PIRP irp = NULL;
+ PMDL mdl = NULL;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ PSENSE_DATA_EX senseInfoBuffer = NULL;
+ UCHAR senseInfoBufferLength = 0;
+ KEVENT completionEvent;
+ ULONG targetPortGroupsInfoLength = 0;
+ PUCHAR targetPortGroupsInfo = NULL;
+ PIO_STACK_LOCATION irpStack = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Entering function.\n",
+ DeviceObject));
+
+ if (TargetPortGroupsInfoLength == NULL ||
+ TargetPortGroupsInfo == NULL) {
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpReportTargetPortGroups;
+ }
+
+ *TargetPortGroupsInfoLength = 0;
+ *TargetPortGroupsInfo = NULL;
+
+ senseInfoBuffer = (PSENSE_DATA_EX)DsmpAllocatePool(NonPagedPoolNx,
+ SENSE_BUFFER_SIZE_EX,
+ DSM_TAG_SCSI_SENSE_INFO);
+ if (senseInfoBuffer != NULL) {
+
+ senseInfoBufferLength = SENSE_BUFFER_SIZE_EX;
+
+ srb = (PSCSI_REQUEST_BLOCK)DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(SCSI_REQUEST_BLOCK),
+ DSM_TAG_SCSI_REQUEST_BLOCK);
+ if (srb != NULL) {
+
+ SrbSetSenseInfoBufferLength(srb, senseInfoBufferLength);
+ SrbSetSenseInfoBuffer(srb, senseInfoBuffer);
+
+ //
+ // Take care of worst case scenario, which is:
+ // 1. 4-byte header (for allocation length)
+ // 2. 32 8-byte descriptors (for TPGs)
+ // 3. Each descriptor containing 32 4-byte identifiers (for TPs in each TPG)
+ //
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE +
+ (DSM_MAX_PATHS * (sizeof(SPC3_REPORT_TARGET_PORT_GROUP_DESCRIPTOR) +
+ DSM_MAX_PATHS * sizeof(ULONG)));
+
+ targetPortGroupsInfo = (PUCHAR)DsmpAllocatePool(NonPagedPoolNx,
+ targetPortGroupsInfoLength,
+ DSM_TAG_TARGET_PORT_GROUPS);
+
+ if (targetPortGroupsInfo == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Failed to allocate TPG info.\n",
+ DeviceObject));
+ goto __Exit_DsmpReportTargetPortGroups;
+ }
+
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Failed to allocate SRB.\n",
+ DeviceObject));
+ goto __Exit_DsmpReportTargetPortGroups;
+ }
+
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Failed to allocate Sense Info Buffer.\n",
+ DeviceObject));
+ goto __Exit_DsmpReportTargetPortGroups;
+ }
+
+ irp = IoAllocateIrp(DeviceObject->StackSize + 1, FALSE);
+ if (irp == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Failed to allocate IRP.\n",
+ DeviceObject));
+ goto __Exit_DsmpReportTargetPortGroups;
+ }
+
+ mdl = IoAllocateMdl(targetPortGroupsInfo,
+ targetPortGroupsInfoLength,
+ FALSE,
+ FALSE,
+ irp);
+
+ if (mdl == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Failed to allocate MDL.\n",
+ DeviceObject));
+ goto __Exit_DsmpReportTargetPortGroups;
+ }
+
+ MmBuildMdlForNonPagedPool(mdl);
+
+__Retry_DsmpReportTargetPortGroups:
+
+ irp->MdlAddress = mdl;
+
+ //
+ // Set up SRB for execute scsi request. Save SRB address in next stack
+ // for the port driver.
+ //
+ irpStack = IoGetNextIrpStackLocation(irp);
+ irpStack->MajorFunction = IRP_MJ_SCSI;
+ irpStack->MinorFunction = IRP_MN_SCSI_CLASS;
+ irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srb;
+ irpStack->DeviceObject = DeviceObject;
+
+ //
+ // Set the completion event and the completion routine.
+ //
+ KeInitializeEvent(&completionEvent, NotificationEvent, FALSE);
+ irp->UserEvent = &completionEvent;
+ IoSetCompletionRoutine(irp,
+ DsmpReportTargetPortGroupsSyncCompletion,
+ srb,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ SrbSetCdbLength(srb, sizeof(SPC3_CDB_REPORT_TARGET_PORT_GROUPS));
+ cdb = (PSPC3_CDB_REPORT_TARGET_PORT_GROUPS)SrbGetCdb(srb);
+ cdb->OperationCode = SPC3_SCSIOP_REPORT_TARGET_PORT_GROUPS;
+ cdb->ServiceAction = SPC3_SERVICE_ACTION_TARGET_PORT_GROUPS;
+ REVERSE_BYTES(&(cdb->AllocationLength), &targetPortGroupsInfoLength);
+
+ SrbSetTimeOutValue(srb, SPC3_REPORT_TARGET_PORT_GROUPS_TIMEOUT);
+ SrbSetDataTransferLength(srb, targetPortGroupsInfoLength);
+ SrbSetDataBuffer(srb, targetPortGroupsInfo);
+ srb->SrbStatus = 0;
+ SrbSetScsiStatus(srb, 0);
+ SrbSetNextSrb(srb, NULL);
+ SrbSetSrbFlags(srb, SRB_FLAGS_DONT_START_NEXT_PACKET | SRB_FLAGS_QUEUE_ACTION_ENABLE |
+ SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
+ SRB_FLAGS_BYPASS_FROZEN_QUEUE | SRB_FLAGS_NO_QUEUE_FREEZE);
+ SrbSetQueueAction(srb, SRB_HEAD_OF_QUEUE_TAG_REQUEST);
+ SrbSetOriginalRequest(srb, irp);
+
+ ObReferenceObject(DeviceObject);
+
+ //
+ // Finally, send the IRP down and wait for its completion.
+ //
+ status = IoCallDriver(DeviceObject, irp);
+
+ if (status == STATUS_PENDING) {
+ KeWaitForSingleObject(&completionEvent,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+ status = irp->IoStatus.Status;
+ }
+
+ ObDereferenceObject(DeviceObject);
+
+ if ((status == STATUS_BUFFER_OVERFLOW) ||
+ (NT_SUCCESS(status) && (SrbGetScsiStatus(srb) == SCSISTAT_GOOD))) {
+
+ //
+ // The first 4 bytes of the returned data are the Returned Data Length
+ // field of the RTPG header.
+ //
+ ULONG returnedDataLength = 0;
+ REVERSE_BYTES(&returnedDataLength, targetPortGroupsInfo);
+
+ status = STATUS_SUCCESS;
+ if (returnedDataLength > SrbGetDataTransferLength(srb)) {
+
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ }
+
+ if (NT_SUCCESS(status) && SrbGetScsiStatus(srb) == SCSISTAT_GOOD) {
+
+ //
+ // RTPG was successful so return the TPG info to the caller.
+ //
+
+ //
+ // The first 4 bytes of the returned data are the Returned Data Length
+ // field of the RTPG header. We need to return this value plus the header size.
+ //
+ ULONG returnedDataLength = 0;
+ REVERSE_BYTES(&returnedDataLength, targetPortGroupsInfo);
+ *TargetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE + returnedDataLength;
+
+ *TargetPortGroupsInfo = targetPortGroupsInfo;
+
+ } else if (SrbGetScsiStatus(srb) == SCSISTAT_CHECK_CONDITION) {
+
+ if (DsmpShouldRetryTPGRequest(senseInfoBuffer, senseInfoBufferLength)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Retrying request.\n",
+ DeviceObject));
+
+ IoReuseIrp(irp, STATUS_SUCCESS);
+
+ RtlZeroMemory(senseInfoBuffer, senseInfoBufferLength);
+
+ goto __Retry_DsmpReportTargetPortGroups;
+ }
+
+ if (DsmpIsDeviceRemoved(senseInfoBuffer, senseInfoBufferLength)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Device not available.\n",
+ DeviceObject));
+
+ //
+ // Sense key was illegal request. SPC 6.25 says response to TPG should follow Test Unit Ready responses
+ //
+ status = STATUS_NO_SUCH_DEVICE;
+
+ }
+
+ // RTPG was unsuccessful
+ // Here it is possible that status is success, but scsi status is not.
+ // and there was no RTPG retry. If so, set status to unsuccessful.
+ if (NT_SUCCESS(status)) {
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ //
+ // TPG resulted HW to respond with Check Condition but Sense Key indicates it is not for retry or illegal request
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): TPG returned Check Condition, NTStatus 0x%x, ScsiStatus 0x%x.\n",
+ DeviceObject,
+ status,
+ SrbGetScsiStatus(srb)));
+ } else {
+
+ // RTPG was unsuccessful
+ // Here it is possible that status is success, but scsi status is not.
+ // If so, set status to unsuccessful.
+ if (NT_SUCCESS(status)) {
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): NTStatus 0x%x, ScsiStatus 0x%x.\n",
+ DeviceObject,
+ status,
+ SrbGetScsiStatus(srb)));
+ }
+
+__Exit_DsmpReportTargetPortGroups:
+
+ //
+ // The port driver may have allocated its own sense buffer so we need to
+ // make sure we free that here.
+ //
+ if (srb != NULL &&
+ SrbGetSrbFlags(srb) & SRB_FLAGS_PORT_DRIVER_ALLOCSENSE &&
+ SrbGetSrbFlags(srb) & SRB_FLAGS_FREE_SENSE_BUFFER &&
+ SrbGetSenseInfoBuffer(srb) != NULL) {
+ DsmpFreePool(SrbGetSenseInfoBuffer(srb));
+ }
+
+ if (senseInfoBuffer) {
+ DsmpFreePool(senseInfoBuffer);
+ }
+
+ if (srb) {
+ DsmpFreePool(srb);
+ }
+
+ if (irp) {
+ if (irp->MdlAddress) {
+ IoFreeMdl(irp->MdlAddress);
+ }
+ IoFreeIrp(irp);
+ }
+
+ if (!NT_SUCCESS(status) && targetPortGroupsInfo) {
+ DsmpFreePool(targetPortGroupsInfo);
+ *TargetPortGroupsInfoLength = 0;
+ *TargetPortGroupsInfo = NULL;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpReportTargetPortGroups (DevObj %p): Exiting function with status %x.\n",
+ DeviceObject,
+ status));
+
+ return status;
+}
+
+NTSTATUS
+DsmpReportTargetPortGroupsAsync(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PIO_COMPLETION_ROUTINE CompletionRoutine,
+ _Inout_ __drv_aliasesMem IN PDSM_TPG_COMPLETION_CONTEXT CompletionContext,
+ _In_ IN ULONG TargetPortGroupsInfoLength,
+ _Inout_ __drv_aliasesMem IN OUT PUCHAR TargetPortGroupsInfo
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to send down ReportTargetPortGroups request asynchronously.
+ Used if device supports ALUA.
+
+ NOTE: Caller needs to free Irp, system buffer, and passThrough buffer.
+
+Arguments:
+
+ DeviceInfo - The deviceInfo whose corresponding port PDO the command should be sent to.
+ CompletionRoutine - completion routine passed in by the caller.
+ CompletionContext - context to be passed to be completion routine.
+ TargetPortGroupsInfoLength - size of the returned buffer.
+ TargetPortGroupsInfo - preallocated (by caller) buffer that'll contain the returned data.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ PDSM_TPG_COMPLETION_CONTEXT tpgCompletionContext = CompletionContext;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ PSPC3_CDB_REPORT_TARGET_PORT_GROUPS cdb;
+ NTSTATUS status;
+ PIRP irp = NULL;
+ PIO_STACK_LOCATION irpStack;
+ PMDL mdl = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpReportTargetPortGroupsAsync (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ srb = tpgCompletionContext->Srb;
+
+ SrbZeroSrb(srb);
+
+ //
+ // Allocate an irp.
+ //
+ irp = IoAllocateIrp(DeviceInfo->TargetObject->StackSize + 1, FALSE);
+ if (!irp) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpReportTargetPortGroupsAsync (DevInfo %p): Failed to allocate IRP.\n",
+ DeviceInfo));
+ goto __Exit_DsmpReportTargetPortGroupsAsync;
+ }
+
+ mdl = IoAllocateMdl(TargetPortGroupsInfo,
+ TargetPortGroupsInfoLength,
+ FALSE,
+ FALSE,
+ irp);
+ if (!mdl) {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpReportTargetPortGroupsAsync (DevInfo %p): Failed to allocate MDL.\n",
+ DeviceInfo));
+ goto __Exit_DsmpReportTargetPortGroupsAsync;
+ }
+
+ MmBuildMdlForNonPagedPool(irp->MdlAddress);
+
+ //
+ // It is possible that if an implicit access state transition took place,
+ // each I_T nexus will return UA for asymmetric access state changed. So
+ // set the number of retries to be one more than the total number of paths.
+ // Worst case scenario is the it is sent down each path once (assuming every
+ // is a different I_T nexus) and then one more for a retry one one of the
+ // paths.
+ //
+ tpgCompletionContext->NumberRetries = DeviceInfo->Group->NumberDevices + 1;
+
+ //
+ // Set-up the completion routine.
+ //
+ IoSetCompletionRoutine(irp,
+ CompletionRoutine,
+ (PVOID)CompletionContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ //
+ // Get the recipient's irpstack location.
+ //
+ irpStack = IoGetNextIrpStackLocation(irp);
+
+ irpStack->Parameters.Scsi.Srb = srb;
+ irpStack->DeviceObject = DeviceInfo->TargetObject;
+
+ //
+ // Set the major function code to IRP_MJ_SCSI.
+ //
+ irpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
+
+ //
+ // Set the minor function, or many requests will get kicked by by port.
+ //
+ irpStack->MinorFunction = IRP_MN_SCSI_CLASS;
+
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ SrbSetCdbLength(srb, sizeof(SPC3_CDB_REPORT_TARGET_PORT_GROUPS));
+ cdb = (PSPC3_CDB_REPORT_TARGET_PORT_GROUPS)SrbGetCdb(srb);
+ cdb->OperationCode = SPC3_SCSIOP_REPORT_TARGET_PORT_GROUPS;
+ cdb->ServiceAction = SPC3_SERVICE_ACTION_TARGET_PORT_GROUPS;
+ Get4ByteArrayFromUlong(TargetPortGroupsInfoLength, cdb->AllocationLength);
+
+ SrbSetTimeOutValue(srb, SPC3_REPORT_TARGET_PORT_GROUPS_TIMEOUT);
+ SrbSetSenseInfoBuffer(srb, tpgCompletionContext->SenseInfoBuffer);
+ SrbSetSenseInfoBufferLength(srb, tpgCompletionContext->SenseInfoBufferLength);
+ SrbSetDataTransferLength(srb, TargetPortGroupsInfoLength);
+ SrbSetDataBuffer(srb, TargetPortGroupsInfo);
+ srb->SrbStatus = 0;
+ SrbSetScsiStatus(srb, 0);
+ SrbSetNextSrb(srb, NULL);
+ SrbSetSrbFlags(srb, SRB_FLAGS_DONT_START_NEXT_PACKET | SRB_FLAGS_QUEUE_ACTION_ENABLE |
+ SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
+ SRB_FLAGS_BYPASS_FROZEN_QUEUE | SRB_FLAGS_NO_QUEUE_FREEZE);
+ SrbSetQueueAction(srb, SRB_HEAD_OF_QUEUE_TAG_REQUEST);
+ SrbSetOriginalRequest(srb, irp);
+
+ irp->UserBuffer = TargetPortGroupsInfo;
+ irp->Tail.Overlay.Thread = PsGetCurrentThread();
+
+ //
+ // Send the IRP asynchronously
+ //
+ DsmSendRequestEx(((PDSM_CONTEXT)(DeviceInfo->DsmContext))->MPIOContext,
+ DeviceInfo->TargetObject,
+ irp,
+ (PVOID)DeviceInfo,
+ DSM_CALL_COMPLETION_ON_MPIO_ERROR);
+
+ //
+ // We know that the completion routine will always be called.
+ //
+ status = STATUS_PENDING;
+
+__Exit_DsmpReportTargetPortGroupsAsync:
+
+ if (status != STATUS_PENDING) {
+
+ //
+ // This indicates Irp was never sent down to stack (completion routine was never called).
+ // We need to clean up.
+ //
+ if (irp) {
+
+ if (irp->MdlAddress) {
+ IoFreeMdl(irp->MdlAddress);
+ }
+
+ IoFreeIrp(irp);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpReportTargetPortGroupsAsync (DevInfo %p): Exiting function with status %x\n.",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryLBPolicyForDevice(
+ _In_ IN PWSTR RegistryKeyName,
+ _In_ IN ULONGLONG PathId,
+ _In_ IN DSM_LOAD_BALANCE_TYPE LoadBalanceType,
+ _Out_ OUT PULONG PrimaryPath,
+ _Out_ OUT PULONG OptimizedPath,
+ _Out_ OUT PULONG PathWeight
+ )
+/*++
+
+Routine Description:
+
+ This routine opens the device's registry subkey, builds the path subkey from
+ the passed in PathId, then queries that subkey for the value of PrimaryPath,
+ OptimizedPath and PathWeight.
+
+Arguments:
+
+ RegistryKeyName - The device's registry subkey name.
+ PathId - The pathId for this instance of the device.
+ LoadBalanceType - The current load balance policy.
+ PrimaryPath - Output of the queried PrimaryPath value.
+ OptimizedPath - Output of the queried OptimizedPath value.
+ PathWeight - Output of the queried PathWeight value.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ HANDLE lbSettingsKey = NULL;
+ HANDLE deviceKey = NULL;
+ HANDLE dsmPathKey = NULL;
+ UNICODE_STRING subKeyName;
+ WCHAR dsmPathName[128] = {0};
+ OBJECT_ATTRIBUTES objectAttributes;
+ NTSTATUS status;
+ NTSTATUS pathWeightQueryStatus = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Entering function.\n",
+ RegistryKeyName));
+
+ //
+ // Query PrimaryPath and PathWeight for the given path.
+ // These values are stored under DsmPath#Suffix key for
+ // this path. If this key doesn't exist create it and
+ // create PrimaryPath and PathWeight values - use the
+ // values passed in PrimaryPath and PathWeight in this case.
+ //
+ status = DsmpOpenLoadBalanceSettingsKey(KEY_ALL_ACCESS, &lbSettingsKey);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Failed to open LB Settings key. Status %x.\n",
+ RegistryKeyName,
+ status));
+
+ goto __Exit_DsmpQueryLBPolicyForDevice;
+ }
+
+ RtlInitUnicodeString(&subKeyName, RegistryKeyName);
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ lbSettingsKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(&deviceKey, KEY_ALL_ACCESS, &objectAttributes);
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Create or open DsmPath#Suffix key for this path
+ //
+ DsmpGetDSMPathKeyName(PathId, dsmPathName, 128);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Will query %ws for PrimaryPath, OptimizedPath and PathWeight.\n",
+ RegistryKeyName,
+ dsmPathName));
+
+ RtlInitUnicodeString(&subKeyName, dsmPathName);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ deviceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwCreateKey(&dsmPathKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (NT_SUCCESS(status)) {
+
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+
+ //
+ // Query the Path Weight value.
+ //
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT |
+ RTL_QUERY_REGISTRY_REQUIRED |
+ RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_PATH_WEIGHT;
+ queryTable[0].EntryContext = PathWeight;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ pathWeightQueryStatus = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ dsmPathKey,
+ queryTable,
+ dsmPathKey,
+ NULL);
+
+ if (!NT_SUCCESS(pathWeightQueryStatus)) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Failed to query PathWeight. Status %x.\n",
+ RegistryKeyName,
+ pathWeightQueryStatus));
+ }
+
+ //
+ // Query the Primary Path value.
+ //
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT |
+ RTL_QUERY_REGISTRY_REQUIRED |
+ RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_PRIMARY_PATH;
+ queryTable[0].EntryContext = PrimaryPath;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ dsmPathKey,
+ queryTable,
+ dsmPathKey,
+ NULL);
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Query the Optimized Path value.
+ //
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT |
+ RTL_QUERY_REGISTRY_REQUIRED |
+ RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_OPTIMIZED_PATH;
+ queryTable[0].EntryContext = OptimizedPath;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ dsmPathKey,
+ queryTable,
+ dsmPathKey,
+ NULL);
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Failed to query OptimizedPath. Status %x.\n",
+ RegistryKeyName,
+ status));
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Failed to query PrimaryPath. Status %x.\n",
+ RegistryKeyName,
+ status));
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Failed to create DSM Path key %ws. Status %x.\n",
+ RegistryKeyName,
+ dsmPathName,
+ status));
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Failed to open key. Status %x.\n",
+ RegistryKeyName,
+ status));
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): PrimaryPath %d, OptmizedPath %d, PathWeight %d.\n",
+ RegistryKeyName,
+ *PrimaryPath,
+ *OptimizedPath,
+ *PathWeight));
+ }
+
+__Exit_DsmpQueryLBPolicyForDevice:
+
+ if (dsmPathKey) {
+ ZwClose(dsmPathKey);
+ }
+
+ if (deviceKey) {
+ ZwClose(deviceKey);
+ }
+
+ if (lbSettingsKey) {
+ ZwClose(lbSettingsKey);
+ }
+
+ //
+ // If the load balance policy is Weighted Paths and we failed to read in
+ // the path weight value, we need to return the failure status from the
+ // path weight value query.
+ //
+ if (LoadBalanceType == DSM_LB_WEIGHTED_PATHS && !NT_SUCCESS(pathWeightQueryStatus)) {
+ status = pathWeightQueryStatus;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryLBPolicyForDevice (DevName %ws): Exiting function with status %x.\n",
+ RegistryKeyName,
+ status));
+
+ return status;
+}
+
+
+VOID
+DsmpGetDSMPathKeyName(
+ _In_ ULONGLONG DSMPathId,
+ _Out_writes_(DsmPathKeyNameSize) PWCHAR DsmPathKeyName,
+ _In_ ULONG DsmPathKeyNameSize
+ )
+/*++
+
+Routine Description:
+
+ This routine builds the string that corresponds to the device's Path subkey
+ name in the registry.
+
+Arguments:
+
+ DSMPathId - The pathId of this instance of the device.
+ DsmPathKeyName - Output buffer in which the subkey name for path is returned.
+ DsmPathKeyNameSize - size of the output buffer in WCHARs.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ PWCHAR pathPtr;
+ SIZE_T wcharsLeft;
+ SIZE_T size;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetDSMPathKeyName (PathId %I64x): Entering function.\n",
+ DSMPathId));
+
+ //
+ // This routine will build a name for a given DSM Path.
+ // The name is of the format DsmPath#Suffix, where Suffix
+ // is derived from the PathId
+ //
+ pathPtr = DsmPathKeyName;
+
+ wcharsLeft = DsmPathKeyNameSize;
+
+ size = wcslen(DSM_PATH);
+
+ if (size < wcharsLeft) {
+
+ //
+ // First copy the string DsmPath#
+ //
+ if (NT_SUCCESS(RtlStringCchCopyNW(pathPtr, wcharsLeft, DSM_PATH, wcslen(DSM_PATH)))) {
+
+ wcharsLeft -= size;
+ pathPtr += size;
+
+ if (wcharsLeft > 2) {
+
+ RtlStringCchCatW(pathPtr, wcharsLeft, L"#");
+ wcharsLeft--;
+ pathPtr++;
+
+ //
+ // Each nibble in the path id would need 1 WCHAR
+ // upon conversion to WCHAR string. So we'll need
+ // 2 WCHARs for each byte. Include the NULL char also
+ //
+ size = (sizeof(PVOID) + 1) * 2;
+ if (size <= wcharsLeft) {
+
+ PVOID pathId;
+ PUCHAR pathIdPtr;
+ ULONG inx;
+ UCHAR tmpChar;
+
+ //
+ // Convert the ULONGLONG path id to a string and
+ // append that to DsmPath#
+ //
+ pathId = (PVOID) DSMPathId;
+
+ pathIdPtr = (PUCHAR) &pathId;
+
+ for (inx = 0; inx < sizeof(PVOID); inx++) {
+
+ tmpChar = (*pathIdPtr & 0xF0) >> 4;
+ *pathPtr++ = DsmpGetAsciiForBinary(tmpChar);
+
+ tmpChar = (*pathIdPtr & 0x0F);
+ *pathPtr++ = DsmpGetAsciiForBinary(tmpChar);
+
+ pathIdPtr++;
+ }
+
+ *pathPtr = WNULL;
+ }
+ }
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetDSMPathKeyName (PathId %I64x): Exiting function.\n",
+ DSMPathId));
+
+ return;
+}
+
+
+UCHAR
+DsmpGetAsciiForBinary(
+ _In_ UCHAR BinaryChar
+ )
+/*++
+
+Routine Description:
+
+ This routine converts the passed in binary value into ASCII equivalent.
+
+Arguments:
+
+ BinaryChar - The binary value that needs to be converted.
+
+Return Value:
+
+ Corresponding ASCII value.
+
+--*/
+{
+ UCHAR outChar = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetAsciiForBinary (BinaryChar %d): Entering function.\n",
+ BinaryChar));
+
+ //
+ // Convert a binary nibble into an ASCII character.
+ //
+ if ((BinaryChar >= 0) && (BinaryChar <= 9)) {
+ outChar = BinaryChar + '0';
+ } else {
+ outChar = BinaryChar + 'A' - 10;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetAsciiForBinary (BinaryChar %d): Exiting function with outChar %c.\n",
+ BinaryChar,
+ outChar));
+
+ return outChar;
+}
+
+
+NTSTATUS
+DsmpGetDeviceIdList(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _Out_ OUT PSTORAGE_DESCRIPTOR_HEADER *Descriptor
+ )
+/*++
+
+Routine Description:
+
+ This routine will perform a query for the StorageDeviceIdProperty and will
+ allocate a non-paged buffer to store the data in.
+ IMPORTANT: It is the responsibility of the caller to ensure that this buffer is freed.
+
+Arguments:
+
+ DeviceObject - the device to query
+ Descriptor - a location to store a pointer to the buffer we allocate
+
+Return Value:
+
+ status.
+
+--*/
+{
+ STORAGE_PROPERTY_QUERY query;
+ PIO_STATUS_BLOCK ioStatus = NULL;
+ PSTORAGE_DESCRIPTOR_HEADER descriptor = NULL;
+ ULONG length;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceIdList (DevObj %p): Entering function.\n",
+ DeviceObject));
+
+ if (!DeviceObject) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpGetDeviceIdList;
+ }
+
+ //
+ // Poison the passed in descriptor.
+ //
+ *Descriptor = NULL;
+
+ //
+ // Setup the query buffer.
+ //
+ query.PropertyId = StorageDeviceIdProperty;
+ query.QueryType = PropertyStandardQuery;
+ query.AdditionalParameters[0] = 0;
+
+ ioStatus = DsmpAllocatePool(NonPagedPoolNx, sizeof(IO_STATUS_BLOCK), DSM_TAG_IO_STATUS_BLOCK);
+
+ if (!ioStatus) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceIdList (DevObj %p): Failed to allocate an IO_STATUS_BLOCK.\n",
+ DeviceObject));
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpGetDeviceIdList;
+ }
+
+ ioStatus->Status = 0;
+ ioStatus->Information = 0;
+
+ //
+ // On the first call, just need to get the length of the descriptor.
+ //
+ descriptor = (PVOID)&query;
+ DsmSendDeviceIoControlSynchronous(IOCTL_STORAGE_QUERY_PROPERTY,
+ DeviceObject,
+ &query,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ sizeof(STORAGE_DESCRIPTOR_HEADER),
+ FALSE,
+ ioStatus);
+
+ status = ioStatus->Status;
+
+ if(!NT_SUCCESS(status)) {
+
+ descriptor = NULL;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceIdList (DevObj %p): Query failed (%x) on attempt 1.\n",
+ DeviceObject,
+ ioStatus->Status));
+
+ goto __Exit_DsmpGetDeviceIdList;
+ }
+
+ NT_ASSERT(descriptor->Size);
+ if (descriptor->Size == 0) {
+ status = STATUS_UNSUCCESSFUL;
+ goto __Exit_DsmpGetDeviceIdList;
+ }
+
+ //
+ // This time we know how much data there is so we can
+ // allocate a buffer of the correct size
+ //
+ length = descriptor->Size;
+
+ descriptor = DsmpAllocatePool(NonPagedPoolNx, length, DSM_TAG_DEVICE_ID_LIST);
+
+ if(!descriptor) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceIdList (DevObj %p): Couldn't allocate descriptor of %ld.\n",
+ DeviceObject,
+ length));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpGetDeviceIdList;
+ }
+
+ //
+ // setup the query again.
+ //
+ query.PropertyId = StorageDeviceIdProperty;
+ query.QueryType = PropertyStandardQuery;
+ query.AdditionalParameters[0] = 0;
+
+ //
+ // copy the input to the new outputbuffer
+ //
+ RtlCopyMemory(descriptor,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY));
+
+ DsmSendDeviceIoControlSynchronous(IOCTL_STORAGE_QUERY_PROPERTY,
+ DeviceObject,
+ descriptor,
+ descriptor,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ length,
+ 0,
+ ioStatus);
+
+ status = ioStatus->Status;
+
+ if(!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceIdList (DevObj %p): Query Failed (%x) on attempt 2.\n",
+ DeviceObject,
+ ioStatus->Status));
+
+ goto __Exit_DsmpGetDeviceIdList;
+ }
+
+__Exit_DsmpGetDeviceIdList:
+
+ if (ioStatus) {
+ DsmpFreePool(ioStatus);
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ if (descriptor) {
+ DsmpFreePool(descriptor);
+ }
+
+ } else {
+ *Descriptor = descriptor;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetDeviceIdList (DevObj %p): Exiting function with status %x.\n",
+ DeviceObject,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetTargetPortGroups(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _In_reads_bytes_(TargetPortGroupsInfoLength) IN PUCHAR TargetPortGroupsInfo,
+ _In_ IN ULONG TargetPortGroupsInfoLength
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to send down SetTargetPortGroups request.
+
+Arguments:
+
+ DeviceObject - The port PDO to which the command should be sent.
+ TargetPortGroupsInfo - buffer containing the TPG data.
+ TargetPortGroupsInfoLength - size of the TPG buffer.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER passThrough;
+ PSPC3_CDB_SET_TARGET_PORT_GROUPS cdb;
+ IO_STATUS_BLOCK ioStatus;
+ ULONG alignmentMask = DeviceObject->AlignmentRequirement;
+ PUCHAR dataBuffer = NULL;
+ SIZE_T allocatedLength = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetTargetPortGroups (DevObj %p): Entering function.\n",
+ DeviceObject));
+
+ NT_ASSERT(TargetPortGroupsInfoLength && TargetPortGroupsInfo);
+
+ //
+ // Build request.
+ //
+ RtlZeroMemory(&passThrough, sizeof(passThrough));
+
+ dataBuffer = DsmpAllocateAlignedPool(NonPagedPoolNx,
+ TargetPortGroupsInfoLength,
+ alignmentMask,
+ DSM_TAG_PASS_THRU,
+ &allocatedLength);
+ if (!dataBuffer) {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetTargetPortGroups (DevObj %p): Failed to allocate mem for passthrough's databuffer.\n",
+ DeviceObject));
+ goto __Exit_DsmpSetTargetPortGroups;
+ }
+
+__Retry_Request:
+
+ //
+ // Build the cdb.
+ //
+ cdb = (PSPC3_CDB_SET_TARGET_PORT_GROUPS)passThrough.ScsiPassThroughDirect.Cdb;
+
+ cdb->OperationCode = SPC3_SCSIOP_SET_TARGET_PORT_GROUPS;
+ cdb->ServiceAction = SPC3_SERVICE_ACTION_TARGET_PORT_GROUPS;
+ Get4ByteArrayFromUlong(TargetPortGroupsInfoLength, cdb->ParameterListLength);
+
+ passThrough.ScsiPassThroughDirect.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ passThrough.ScsiPassThroughDirect.CdbLength = 12;
+ passThrough.ScsiPassThroughDirect.SenseInfoLength = SPTWB_SENSE_LENGTH;
+ passThrough.ScsiPassThroughDirect.DataIn = 0;
+ passThrough.ScsiPassThroughDirect.DataTransferLength = TargetPortGroupsInfoLength;
+ passThrough.ScsiPassThroughDirect.TimeOutValue = 20;
+ passThrough.ScsiPassThroughDirect.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseInfoBuffer);
+ passThrough.ScsiPassThroughDirect.DataBuffer = dataBuffer;
+ RtlCopyMemory(dataBuffer,
+ TargetPortGroupsInfo,
+ TargetPortGroupsInfoLength);
+
+ DsmSendDeviceIoControlSynchronous(IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ DeviceObject,
+ &passThrough,
+ &passThrough,
+ sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER),
+ sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER),
+ FALSE,
+ &ioStatus);
+
+ if ((passThrough.ScsiPassThroughDirect.ScsiStatus == SCSISTAT_GOOD) &&
+ (NT_SUCCESS(ioStatus.Status))) {
+
+ status = STATUS_SUCCESS;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetTargetPortGroups (DevObj %p): STPG succeeded.\n",
+ DeviceObject));
+
+ } else if (NT_SUCCESS(ioStatus.Status) &&
+ passThrough.ScsiPassThroughDirect.ScsiStatus == SCSISTAT_CHECK_CONDITION &&
+ DsmpShouldRetryTPGRequest((PSENSE_DATA)&passThrough.SenseInfoBuffer, passThrough.ScsiPassThroughDirect.SenseInfoLength)) {
+
+ //
+ // Retry the request
+ //
+ RtlZeroMemory(dataBuffer, TargetPortGroupsInfoLength);
+ goto __Retry_Request;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetTargetPortGroups (DevObj %p): NTStatus 0%x, ScsiStatus 0x%x.\n",
+ DeviceObject,
+ ioStatus.Status,
+ passThrough.ScsiPassThroughDirect.ScsiStatus));
+
+ status = ioStatus.Status;
+ }
+
+__Exit_DsmpSetTargetPortGroups:
+
+ //
+ // Free the passthrough + data buffer.
+ //
+ if (dataBuffer) {
+ DsmpFreePool(dataBuffer);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetTargetPortGroups (DevObj %p): Exiting function with status %x.\n",
+ DeviceObject,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetTargetPortGroupsAsync(
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN PIO_COMPLETION_ROUTINE CompletionRoutine,
+ _In_ __drv_aliasesMem IN PDSM_TPG_COMPLETION_CONTEXT CompletionContext,
+ _In_ IN ULONG TargetPortGroupsInfoLength,
+ _In_ __drv_aliasesMem IN PUCHAR TargetPortGroupsInfo
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to send down SetTargetPortGroups request asynchronously.
+
+ IMPORTANT: Caller needs to free the IRP and allocated system buffer.
+
+Arguments:
+
+ DeviceInfo - The deviceInfo whose corresponding port PDO the command should be sent to.
+ CompletionRoutine - completion routine provided by the caller.
+ CompletionContext - context passed into the completion routine.
+ TargetPortGroupsInfoLength - size of the TPG buffer.
+ TargetPortGroupsInfo - buffer containing the TPG data.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ PDSM_TPG_COMPLETION_CONTEXT tpgCompletionContext = CompletionContext;
+ PSCSI_REQUEST_BLOCK srb;
+ PSPC3_CDB_SET_TARGET_PORT_GROUPS cdb;
+ NTSTATUS status;
+ PIRP irp = NULL;
+ PIO_STACK_LOCATION irpStack;
+ PMDL mdl = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetTargetPortGroupsAsync (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ srb = tpgCompletionContext->Srb;
+
+ SrbZeroSrb(srb);
+
+ //
+ // Allocate an irp.
+ //
+ irp = IoAllocateIrp(DeviceInfo->TargetObject->StackSize + 1, FALSE);
+ if (!irp) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetTargetPortGroupsAsync (DevInfo %p): Failed to allocate IRP.\n",
+ DeviceInfo));
+ goto __Exit_DsmpSetTargetPortGroupsAsync;
+ }
+
+ mdl = IoAllocateMdl(TargetPortGroupsInfo,
+ TargetPortGroupsInfoLength,
+ FALSE,
+ FALSE,
+ irp);
+ if (!mdl) {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_RW,
+ "DsmpSetTargetPortGroupsAsync (DevInfo %p): Failed to allocate MDL.\n",
+ DeviceInfo));
+ goto __Exit_DsmpSetTargetPortGroupsAsync;
+ }
+
+ MmBuildMdlForNonPagedPool(irp->MdlAddress);
+
+ //
+ // It is possible that an implicit state transition may have occurred which
+ // will cause every I_T nexus to return an UA (for asymmetric access state
+ // changed). So set the number of retries to number of paths (worst case of
+ // every path being a separate I_T nexus) plus one for a retry down one of
+ // paths.
+ //
+ tpgCompletionContext->NumberRetries = DeviceInfo->Group->NumberDevices + 1;
+
+ //
+ // Set-up the completion routine.
+ //
+ IoSetCompletionRoutine(irp,
+ CompletionRoutine,
+ (PVOID)CompletionContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ //
+ // Get the recipient's irpstack location.
+ //
+ irpStack = IoGetNextIrpStackLocation(irp);
+
+ irpStack->Parameters.Scsi.Srb = srb;
+ irpStack->DeviceObject = DeviceInfo->TargetObject;
+
+ //
+ // Set the major function code to IRP_MJ_SCSI.
+ //
+ irpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
+
+ //
+ // Set the minor function, or many requests will get kicked by by port.
+ //
+ irpStack->MinorFunction = IRP_MN_SCSI_CLASS;
+
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ SrbSetCdbLength(srb, sizeof(SPC3_CDB_SET_TARGET_PORT_GROUPS));
+ cdb = (PSPC3_CDB_SET_TARGET_PORT_GROUPS)SrbGetCdb(srb);
+ cdb->OperationCode = SPC3_SCSIOP_SET_TARGET_PORT_GROUPS;
+ cdb->ServiceAction = SPC3_SERVICE_ACTION_TARGET_PORT_GROUPS;
+ Get4ByteArrayFromUlong(TargetPortGroupsInfoLength, cdb->ParameterListLength);
+
+ SrbSetTimeOutValue(srb, SPC3_SET_TARGET_PORT_GROUPS_TIMEOUT);
+ SrbSetSenseInfoBuffer(srb, tpgCompletionContext->SenseInfoBuffer);
+ SrbSetSenseInfoBufferLength(srb, tpgCompletionContext->SenseInfoBufferLength);
+ SrbSetDataTransferLength(srb, TargetPortGroupsInfoLength);
+ SrbSetDataBuffer(srb, TargetPortGroupsInfo);
+ srb->SrbStatus = 0;
+ SrbSetScsiStatus(srb, 0);
+ SrbSetNextSrb(srb, NULL);
+ SrbSetSrbFlags(srb, SRB_FLAGS_DONT_START_NEXT_PACKET | SRB_FLAGS_QUEUE_ACTION_ENABLE |
+ SRB_FLAGS_DATA_OUT | SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
+ SRB_FLAGS_BYPASS_FROZEN_QUEUE | SRB_FLAGS_NO_QUEUE_FREEZE);
+ SrbSetQueueAction(srb, SRB_HEAD_OF_QUEUE_TAG_REQUEST);
+ SrbSetOriginalRequest(srb, irp);
+
+ irp->UserBuffer = TargetPortGroupsInfo;
+ irp->Tail.Overlay.Thread = PsGetCurrentThread();
+
+ //
+ // Send the IRP asynchronously
+ //
+ DsmSendRequestEx(((PDSM_CONTEXT)(DeviceInfo->DsmContext))->MPIOContext,
+ DeviceInfo->TargetObject,
+ irp,
+ DeviceInfo,
+ DSM_CALL_COMPLETION_ON_MPIO_ERROR);
+
+ //
+ // We know that the completion routine will always be called.
+ //
+ status = STATUS_PENDING;
+
+
+__Exit_DsmpSetTargetPortGroupsAsync:
+
+ if (status != STATUS_PENDING) {
+
+ //
+ // This indicates Irp was never sent down to stack (completion routine was never called).
+ // We need to clean up.
+ //
+ if (irp) {
+
+ if (irp->MdlAddress) {
+ IoFreeMdl(irp->MdlAddress);
+ }
+
+ IoFreeIrp(irp);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_RW,
+ "DsmpSetTargetPortGroupsAsync (DevInfo %p): Exiting function with status %x.\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+PDSM_LOAD_BALANCE_POLICY_SETTINGS
+DsmpCopyLoadBalancePolicies(
+ _In_ IN PDSM_GROUP_ENTRY GroupEntry,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN PVOID SupportedLBPolicies
+ )
+/*+++
+
+Routine Description:
+
+ This routine copies the LB Policies that needs to be persisted in registry.
+ This is done because registry routines can be called at PASSIVE IRQL only.
+ So a spinlock cannot be held while accessing registry. So hold a spinlock,
+ save the values in a temp buffer, release spinlock, and save data to registry
+ from the temp buffer.
+
+ NOTE: This routine MUST be called with DSM_CONTEXT lock held.
+
+Arguements:
+
+ GroupEntry - Group entry
+ DsmWmiVersion - version of the MPIO_DSM_Path class to use
+ SupportedLBPolicies - LB policy for the group
+
+ Return Value:
+
+ Pointer to LOAD_BALANCE_POLICY_SETTINGS if successful. Else, NULL
+--*/
+{
+ PDSM_LOAD_BALANCE_POLICY_SETTINGS lbSettings = NULL;
+ ULONG sizeNeeded;
+ ULONG inx;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpCopyLoadBalancePolicies (Group %p): Entering function.\n",
+ GroupEntry));
+
+ if (((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSMPathCount == 0) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpCopyLoadBalancePolicies (Group %p): No paths specified in Set LB policies.\n",
+ GroupEntry));
+
+ goto __Exit_DsmpCopyLoadBalancePolicies;
+ }
+
+ sizeNeeded = sizeof(DSM_LOAD_BALANCE_POLICY_SETTINGS) +
+ ((((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSMPathCount - 1) * sizeof(MPIO_DSM_Path_V2));;
+
+ lbSettings = DsmpAllocatePool(NonPagedPoolNx,
+ sizeNeeded,
+ DSM_TAG_LB_POLICY);
+
+ if (!lbSettings) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpCopyLoadBalancePolicies (Group %p): Failed to allocate memory for LBSettings.\n",
+ GroupEntry));
+ goto __Exit_DsmpCopyLoadBalancePolicies;
+ }
+
+ //
+ // Copy the registry key name used to store the LB policies.
+ //
+ RtlStringCchCopyNW(lbSettings->RegistryKeyName,
+ sizeof(lbSettings->RegistryKeyName) / sizeof(lbSettings->RegistryKeyName[0]),
+ GroupEntry->RegistryKeyName,
+ ((sizeof(lbSettings->RegistryKeyName) - sizeof(WCHAR))/sizeof(WCHAR)));
+
+ //
+ // Copy the Load Balance settings for this group
+ //
+ lbSettings->LoadBalancePolicy = ((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->LoadBalancePolicy;
+
+ lbSettings->PathCount = ((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSMPathCount;
+
+ for (inx = 0; inx < ((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSMPathCount; inx++) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ RtlCopyMemory(&(lbSettings->DsmPath[inx]),
+ &(((PDSM_Load_Balance_Policy)SupportedLBPolicies)->DSM_Paths[inx]),
+ sizeof(MPIO_DSM_Path));
+
+ //
+ // DSM_WMI_VERSION_1 supports only active and standby states
+ //
+ (lbSettings->DsmPath[inx]).OptimizedPath = TRUE;
+
+ } else {
+
+ RtlCopyMemory(&(lbSettings->DsmPath[inx]),
+ &(((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSM_Paths[inx]),
+ sizeof(MPIO_DSM_Path_V2));
+ }
+ }
+
+__Exit_DsmpCopyLoadBalancePolicies:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpCopyLoadBalancePolicies (Group %p): Exiting function with lbSettings %p.\n",
+ GroupEntry,
+ lbSettings));
+
+ return lbSettings;
+}
+
+
+NTSTATUS
+DsmpPersistLBSettings(
+ _In_ IN PDSM_LOAD_BALANCE_POLICY_SETTINGS LoadBalanceSettings
+ )
+/*+++
+
+Routine Description:
+
+ This routine will save the Load Balance settings from LoadBalanceSettings
+ to registry.
+
+ NOTE: This routine MUST be called at PASSIVE IRQL
+
+ The format of the registry tree is :
+
+ Services\MSDSM\LoadBalanceSettings ->
+
+ DeviceName -> LoadBalancePolicy REG_DWORD <LB Value>
+
+ DsmPath#Suffix -> PrimaryPath REG_DWORD <Value>
+ OptimizedPath REG_DWORD <Value>
+ PathWeight REG_DWORD <Value>
+
+ The device name is the one built in DsmpBuildDeviceName
+
+ The Suffix in DsmPath#Suffix is built from the PathId. It is built in
+ the routine DsmpGetDSMPathKeyName.
+
+Arguements:
+
+ LoadBalanceSettings - Load Balance settings to be persisted in registry
+
+Return Value:
+
+ STATUS_SUCCESS if the data could be successfully stored in the registry
+ Appropriate NT Status code on failure.
+--*/
+{
+ PMPIO_DSM_Path_V2 dsmPath;
+ HANDLE lbSettingsKey = NULL;
+ HANDLE deviceKey = NULL;
+ HANDLE dsmPathKey = NULL;
+ UNICODE_STRING subKeyName;
+ WCHAR dsmPathName[128];
+ OBJECT_ATTRIBUTES objectAttributes;
+ NTSTATUS status;
+ ULONG inx;
+ PMPIO_DSM_Path_V2 preferredPath = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Entering function.\n",
+ LoadBalanceSettings->RegistryKeyName));
+
+ //
+ // First open LoadBalanceSettings key under the Service key
+ //
+ status = DsmpOpenLoadBalanceSettingsKey(KEY_ALL_ACCESS, &lbSettingsKey);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to open LB Settings key. Status %x.\n",
+ LoadBalanceSettings->RegistryKeyName,
+ status));
+
+ goto __Exit_DsmpPersistLBSettings;
+ }
+
+ //
+ // Now open the key under which the LB settings for the given device is stored
+ //
+ RtlInitUnicodeString(&subKeyName, LoadBalanceSettings->RegistryKeyName);
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ lbSettingsKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(&deviceKey, KEY_ALL_ACCESS, &objectAttributes);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Remove all LB policy information as we are going to rewrite it.
+ // We do this in case there is stale information about a path that
+ // no longer exists
+ //
+ status = DsmpRegDeleteTree(deviceKey);
+
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Deleted key along with its subkeys.\n",
+ LoadBalanceSettings->RegistryKeyName));
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to delete key. Status %x\n",
+ LoadBalanceSettings->RegistryKeyName,
+ status));
+
+ }
+
+ ZwClose(deviceKey);
+ deviceKey = NULL;
+
+ }
+
+ status = ZwCreateKey(&deviceKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (NT_SUCCESS(status)) {
+
+ PDSM_DEVICE_INFO devInfo;
+
+ for (inx = 0; inx < LoadBalanceSettings->PathCount; inx++) {
+
+ dsmPath = &(LoadBalanceSettings->DsmPath[inx]);
+
+ if (dsmPath->DsmPathId == 0) {
+
+ continue;
+ }
+
+ RtlZeroMemory(dsmPathName, sizeof(dsmPathName));
+
+ //
+ // Get the sub key name under which the LB settings for
+ // the given path is stored.
+ //
+ DsmpGetDSMPathKeyName(dsmPath->DsmPathId, dsmPathName, 128);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Will open subkey %ws.\n",
+ LoadBalanceSettings->RegistryKeyName,
+ dsmPathName));
+
+ RtlInitUnicodeString(&subKeyName, dsmPathName);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ deviceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwCreateKey(&dsmPathKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (NT_SUCCESS(status)) {
+
+ if (dsmPath->PreferredPath) {
+
+ preferredPath = dsmPath;
+ }
+
+ devInfo = (PDSM_DEVICE_INFO)dsmPath->Reserved;
+
+ //
+ // Save PrimaryPath, PathWeight and OptimizedPath values for this path
+ //
+ if (devInfo->DesiredState != DSM_DEV_UNDETERMINED) {
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ dsmPathKey,
+ DSM_PRIMARY_PATH,
+ REG_DWORD,
+ &(dsmPath->PrimaryPath),
+ sizeof(ULONG));
+
+ if (NT_SUCCESS(status)) {
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ dsmPathKey,
+ DSM_OPTIMIZED_PATH,
+ REG_DWORD,
+ &(dsmPath->OptimizedPath),
+ sizeof(ULONG));
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to save OptimizedPath. Status %x.\n",
+ LoadBalanceSettings->RegistryKeyName,
+ status));
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to save Primary Path. Status %x.\n",
+ LoadBalanceSettings->RegistryKeyName,
+ status));
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ if (LoadBalanceSettings->LoadBalancePolicy == DSM_LB_WEIGHTED_PATHS) {
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ dsmPathKey,
+ DSM_PATH_WEIGHT,
+ REG_DWORD,
+ &(dsmPath->PathWeight),
+ sizeof(ULONG));
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to save PathWeight. Status %x.\n",
+ LoadBalanceSettings->RegistryKeyName,
+ status));
+ }
+ }
+ }
+
+ ZwClose(dsmPathKey);
+ dsmPathKey = NULL;
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to open DSM Path key. Status %x.\n",
+ LoadBalanceSettings->RegistryKeyName,
+ status));
+ }
+
+ if (!NT_SUCCESS(status)) {
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Save the new Load Balance Policy value,
+ //
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ DSM_LOAD_BALANCE_POLICY,
+ REG_DWORD,
+ &(LoadBalanceSettings->LoadBalancePolicy),
+ sizeof(ULONG));
+ if (NT_SUCCESS(status)) {
+
+ UCHAR explicitlySet = TRUE;
+
+ //
+ // Write out that the policy has been explicitly set
+ //
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ DSM_POLICY_EXPLICITLY_SET,
+ REG_BINARY,
+ &explicitlySet,
+ sizeof(UCHAR));
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // If FailOver-Only policy, set the PreferredPath, if specified
+ //
+ if (preferredPath) {
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ DSM_PREFERRED_PATH,
+ REG_BINARY,
+ &(preferredPath->DsmPathId),
+ sizeof(ULONGLONG));
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to save LB Settings (ES).\n",
+ LoadBalanceSettings->RegistryKeyName));
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Failed to save LB Settings (LBP).\n",
+ LoadBalanceSettings->RegistryKeyName));
+ }
+ }
+ }
+
+__Exit_DsmpPersistLBSettings:
+
+ if (dsmPathKey) {
+ ZwClose(dsmPathKey);
+ }
+
+ if (deviceKey) {
+ ZwClose(deviceKey);
+ }
+
+ if (lbSettingsKey) {
+ ZwClose(lbSettingsKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpPersistLBSettings (DevName %ws): Exiting function with status %x.\n",
+ LoadBalanceSettings->RegistryKeyName,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetDeviceALUAState(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_ IN DSM_DEVICE_STATE DevState
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to build the STPG info and send it down to modify the passed in
+ devInfo's state.
+
+Arguments:
+
+ DsmContext - DSM context.
+ DeviceInfo - DevInfo whose state needs to be changed.
+ DevState - New state to be set.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ PUCHAR targetPortGroupsInfo = NULL;
+ ULONG targetPortGroupsInfoLength;
+ PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR tpgDescriptor = NULL;
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetDeviceALUAState (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ //
+ // Send down SetTPG to set the appropriate access state
+ // (The TPG block will contain the header and a SetTPG descriptor).
+ //
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE +
+ sizeof(SPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR);
+
+ targetPortGroupsInfo = DsmpAllocatePool(NonPagedPoolNx,
+ targetPortGroupsInfoLength,
+ DSM_TAG_TARGET_PORT_GROUPS);
+
+ if (targetPortGroupsInfo) {
+
+ tpgDescriptor = (PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR)(targetPortGroupsInfo + SPC3_TARGET_PORT_GROUPS_HEADER_SIZE);
+ tpgDescriptor->AsymmetricAccessState = DevState;
+ REVERSE_BYTES_SHORT(&tpgDescriptor->TPG_Identifier, &DeviceInfo->TargetPortGroup->Identifier);
+
+ status = DsmpSetTargetPortGroups(DeviceInfo->TargetObject,
+ targetPortGroupsInfo,
+ targetPortGroupsInfoLength);
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // An explicit transition may cause changes to some other TPGs.
+ // So we need to query for the states of all the TPGs and update
+ // our internal list and its elements.
+ //
+ status = DsmpGetDeviceALUAState(DsmContext,
+ DeviceInfo,
+ NULL);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetDeviceALUAState (DevInfo %p): Failed to SetTPG with %x.\n",
+ DeviceInfo,
+ status));
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetDeviceALUAState (DevInfo %p): Failed to allocate TPG.\n",
+ DeviceInfo));
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (targetPortGroupsInfo) {
+ DsmpFreePool(targetPortGroupsInfo);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpSetDeviceALUAState (DevInfo %p): Exiting function with status %x\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpAdjustDeviceStatesALUA(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_opt_ IN PDSM_DEVICE_INFO PreferredActiveDeviceInfo,
+ _In_ IN ULONG SpecialHandlingFlag
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to build the adjust every device state in the group taking
+ the following into consideration:
+ 1. PreferredActiveDeviceInfo
+ 2. DeviceInfo's TPG state
+ 3. Preferred Path
+ 4. LB Policy
+
+Arguments:
+
+ Group - Pseudo-LUN whose path states need to be adjusted.
+ PreferredActiveDeviceInfo - DevInfo whose state needs to preferrably made
+ A/O, if possible. This parameter is optional.
+
+ SpecialHandlingFlag - Flags to indicate any special handling requirement
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ ULONG index;
+ PDSM_DEVICE_INFO deviceInfo;
+ PDSM_DEVICE_INFO activeDevice = NULL;
+ DSM_DEVICE_STATE devState;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): Entering function with preferred active devInfo %p.\n",
+ Group,
+ PreferredActiveDeviceInfo));
+
+ //
+ // Ensure that:
+ // 1. All devices match their ALUA state.
+ // 2. For RRWS, if a device's desired state is non-A/O, but ALUA state is A/O, mask it.
+ // 3. For FOO there must be only one A/O device. Preferably the preferred path.
+ //
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ deviceInfo = Group->DeviceList[index];
+
+ if (deviceInfo) {
+
+ devState = deviceInfo->State;
+
+ if (!DsmpIsDeviceFailedState(deviceInfo->State) &&
+ DsmpIsDeviceInitialized(deviceInfo) &&
+ DsmpIsDeviceUsable(deviceInfo) &&
+ DsmpIsDeviceUsablePR(deviceInfo)) {
+
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = deviceInfo->ALUAState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (its ALUA state).\n",
+ Group,
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+
+ if (deviceInfo->ALUAState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ //
+ // In FOO and RRWS, we need to mask states.
+ //
+ switch (Group->LoadBalanceType) {
+ case DSM_LB_FAILOVER: {
+
+ //
+ // Cache the first available devInfo that is in A/O
+ //
+ if (!activeDevice) {
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p choosen as the active device.\n",
+ Group,
+ activeDevice));
+
+ break;
+ }
+
+ //
+ // Check if this deviceInfo is the preferred path. If yes,
+ // mask the active device's state and make this the new
+ // active device.
+ //
+ if (Group->PreferredPath == (ULONGLONG)((ULONG_PTR)deviceInfo->FailGroup->PathId)) {
+
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = (activeDevice->DesiredState == DSM_DEV_UNDETERMINED ||
+ activeDevice->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) ? DSM_DEV_ACTIVE_UNOPTIMIZED : activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): Previous active devInfo %p transitioning from %u to %u.\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p is now the new active device (preferred path).\n",
+ Group,
+ activeDevice));
+
+ break;
+ }
+
+ //
+ // If active device's desired state is not A/O but this
+ // deviceInfo's is, then mask the active device's state
+ // and make this one the new active device.
+ //
+ if (activeDevice->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED &&
+ activeDevice->DesiredState != DSM_DEV_UNDETERMINED) {
+
+ //
+ // The exception though is if the current active device
+ // is the preferred path
+ //
+ if (Group->PreferredPath == (ULONGLONG)((ULONG_PTR)activeDevice->FailGroup->PathId)) {
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = (deviceInfo->DesiredState == DSM_DEV_UNDETERMINED ||
+ deviceInfo->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) ? DSM_DEV_ACTIVE_UNOPTIMIZED : deviceInfo->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (active DI is PrefPath).\n",
+ Group,
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+ } else {
+
+ //
+ // If this is the devInfo that is preferred to be A/O, make it such
+ //
+ if (PreferredActiveDeviceInfo &&
+ PreferredActiveDeviceInfo == deviceInfo) {
+
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (found a preferred active DI).\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p is now the new active DI (preferred).\n",
+ Group,
+ activeDevice));
+ } else {
+
+ //
+ // Check if this devInfo desires to be in A/O, since the currently
+ // active one doesn't want to be.
+ //
+ if (deviceInfo->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED &&
+ deviceInfo->DesiredState != DSM_DEV_UNDETERMINED) {
+
+ //
+ // This deviceInfo's desire is also not to be in A/O,
+ // so just leave the current one active.
+ //
+ if (devState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ //
+ // Exception is if we're processing the device whose state before
+ // RTPG was sent was already A/O, it is best to leave this device
+ // in A/O state.
+ //
+
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = (activeDevice->DesiredState == DSM_DEV_UNDETERMINED ||
+ activeDevice->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) ? DSM_DEV_ACTIVE_UNOPTIMIZED : activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u. Found a DI that was previously active.\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p now the new active device (previously A/O).\n",
+ Group,
+ activeDevice));
+ } else {
+
+ //
+ // This device wasn't in A/O state before, so just leave
+ // the currently selected active device as is.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = deviceInfo->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (active device already exists).\n",
+ Group,
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+ }
+ } else {
+
+ //
+ // Current devInfo wants (or doesn't) mind being in
+ // A/O, whereas the current active device doesn't, so
+ // mask the active device and make this devInfo the
+ // active device.
+ //
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u. Current DI prefers being A/O.\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p is now the new active device (desired state).\n",
+ Group,
+ activeDevice));
+ }
+ }
+ }
+ } else {
+
+ //
+ // The single overriding factor is always the preferred path.
+ // Everything else is secondary, so first check if the currently
+ // active device can even be overridden by another one.
+ //
+ if (Group->PreferredPath != (ULONGLONG)((ULONG_PTR)activeDevice->FailGroup->PathId)) {
+
+ //
+ // It can't be overridden, so we're done.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = (deviceInfo->DesiredState == DSM_DEV_UNDETERMINED ||
+ deviceInfo->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) ? DSM_DEV_ACTIVE_UNOPTIMIZED : deviceInfo->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (current active DI is PrefPath).\n",
+ Group,
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+ } else {
+
+ //
+ // Active device's desired state is A/O but it isn't the preferred
+ // path. Check if this devInfo is preferred as A/O.
+ //
+ if (PreferredActiveDeviceInfo &&
+ PreferredActiveDeviceInfo == deviceInfo) {
+
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = (activeDevice->DesiredState == DSM_DEV_UNDETERMINED ||
+ activeDevice->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) ? DSM_DEV_ACTIVE_UNOPTIMIZED : activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u. New DI is preferred active.\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p is now the new active device (this DI is preferred active).\n",
+ Group,
+ activeDevice));
+ } else {
+
+ //
+ // Active device's desired state is A/O but it isn't the
+ // preferred path. Check if this devInfo's desired state
+ // is also A/O. If yes, we'll need to make certain decisions.
+ //
+ if (deviceInfo->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED &&
+ deviceInfo->DesiredState != DSM_DEV_UNDETERMINED) {
+
+ //
+ // Since this device doesn't desire to be in
+ // A/O and we already have an active device, just
+ // mask its state.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = deviceInfo->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (prefers being in non-A/O).\n",
+ Group,
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+ } else {
+
+ //
+ // Active device is in A/O and this device desires to be in
+ // A/O too. Make this the new active device only if its state
+ // before the RTPG was already A/O.
+ //
+ if (devState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = (activeDevice->DesiredState == DSM_DEV_UNDETERMINED ||
+ activeDevice->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) ? DSM_DEV_ACTIVE_UNOPTIMIZED : activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (new DI was already in A/O previously).\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p is new active device (since it was in A/O previously too).\n",
+ Group,
+ activeDevice));
+ } else {
+
+ //
+ // Just leave the currently active one alone.
+ //
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = (deviceInfo->DesiredState == DSM_DEV_UNDETERMINED ||
+ deviceInfo->DesiredState == DSM_DEV_ACTIVE_OPTIMIZED) ? DSM_DEV_ACTIVE_UNOPTIMIZED : deviceInfo->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (leave current active DI alone).\n",
+ Group,
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case DSM_LB_ROUND_ROBIN_WITH_SUBSET: {
+
+ //
+ // At least one path needs to be in A/O state, so
+ // cache the first available devInfo that is in A/O
+ //
+ if (!activeDevice) {
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): We have atleast one A/O DevInfo %p.\n",
+ Group,
+ activeDevice));
+
+ break;
+ }
+
+ //
+ // Check if this device is preferred to be in A/O
+ //
+ if (PreferredActiveDeviceInfo &&
+ PreferredActiveDeviceInfo == deviceInfo) {
+
+ //
+ // If the currently active device, doesn't desire to be in
+ // A/O state, mask its state.
+ //
+ if (activeDevice->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED &&
+ activeDevice->DesiredState != DSM_DEV_UNDETERMINED) {
+
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (desired state non-A/O).\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+ }
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p now the new active device (preferred active DI).\n",
+ Group,
+ activeDevice));
+ } else {
+
+ //
+ // If this device's desired state is specified and not A/O,
+ // mask its path state.
+ //
+ if (deviceInfo->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED &&
+ deviceInfo->DesiredState != DSM_DEV_UNDETERMINED) {
+
+ deviceInfo->PreviousState = deviceInfo->State;
+ deviceInfo->State = deviceInfo->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (desires to be in non-A/O).\n",
+ Group,
+ deviceInfo,
+ deviceInfo->PreviousState,
+ deviceInfo->State));
+ } else {
+
+ //
+ // Since this devInfo desires to be in A/O, we are assured
+ // of at least one path in A/O. So check to see if the
+ // currently active device doesn't desire to be in A/O.
+ //
+ if (activeDevice->DesiredState != DSM_DEV_ACTIVE_OPTIMIZED &&
+ activeDevice->DesiredState != DSM_DEV_UNDETERMINED) {
+
+ activeDevice->PreviousState = activeDevice->State;
+ activeDevice->State = activeDevice->DesiredState;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p transitioning from %u to %u (new DI desires to be in A/O).\n",
+ Group,
+ activeDevice,
+ activeDevice->PreviousState,
+ activeDevice->State));
+
+ activeDevice = deviceInfo;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): DevInfo %p now the new active DI (desires to be in A/O).\n",
+ Group,
+ activeDevice));
+ }
+ }
+ }
+
+ break;
+ }
+
+ default: {
+
+ //
+ // For RR, LQD and WP, paths must be in the same
+ // state as their corresponding TPG. Preferably
+ // all should be A/O.
+ //
+ if (deviceInfo->State != DSM_DEV_ACTIVE_OPTIMIZED) {
+ DSM_ASSERT(deviceInfo->State == deviceInfo->ALUAState);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // There may have been a change to the device states.
+ // DsmpGetPath() will pick these changes for RR, RRWS and LQD.
+ // However, it won't for FOO and WP, so update PTBU if needed.
+ //
+ if (Group->LoadBalanceType == DSM_LB_FAILOVER ||
+ Group->LoadBalanceType == DSM_LB_WEIGHTED_PATHS) {
+
+ deviceInfo = DsmpGetActivePathToBeUsed(Group, FALSE, SpecialHandlingFlag);
+
+ if (deviceInfo) {
+
+ InterlockedExchangePointer(&(Group->PathToBeUsed), deviceInfo->FailGroup);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpAdjustDeviceStatesALUA (Group %p): Exiting function with status %x\n",
+ Group,
+ status));
+
+ return status;
+}
+
+
+PDSM_WORKITEM
+DsmpAllocateWorkItem(
+ _In_ IN PDEVICE_OBJECT DeviceObject,
+ _In_ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ Allocates a work item to handle reservation failover.
+
+Arguments:
+
+ DeviceObject - Target device.
+ Context - Workitem context
+
+Return Value:
+
+ Allocated workitem or NULL (if low memory).
+
+--*/
+{
+ PDSM_WORKITEM dsmWorkItem = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpAllocateWorkItem (DevObj %p): Entering function.\n",
+ DeviceObject));
+
+ dsmWorkItem = DsmpAllocatePool(NonPagedPoolNx,
+ sizeof(DSM_WORKITEM),
+ DSM_TAG_WORKITEM);
+ if (dsmWorkItem != NULL) {
+
+ dsmWorkItem->WorkItem = IoAllocateWorkItem(DeviceObject);
+ if (dsmWorkItem->WorkItem != NULL) {
+
+ dsmWorkItem->Context = Context;
+ } else {
+
+ DsmpFreePool(dsmWorkItem);
+ dsmWorkItem = NULL;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpAllocateWorkItem (DevObj %p): Exiting function. dsmWorkItem %p.\n",
+ DeviceObject,
+ dsmWorkItem));
+
+ return dsmWorkItem;
+}
+
+
+VOID
+DsmpFreeWorkItem(
+ _In_ IN PDSM_WORKITEM DsmWorkItem
+ )
+{
+ PVOID temp = DsmWorkItem;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpFreeWorkItem (WorkItem %p): Entering function.\n",
+ DsmWorkItem));
+
+ if (DsmWorkItem != NULL) {
+
+ if (DsmWorkItem->WorkItem != NULL) {
+ IoFreeWorkItem(DsmWorkItem->WorkItem);
+ }
+
+ DsmpFreePool(DsmWorkItem);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "DsmpFreeWorkItem (WorkItem %p): Exiting function.\n",
+ temp));
+
+ return;
+}
+
+
+VOID
+DsmpFreeZombieGroupList(
+ _In_ IN PDSM_FAILOVER_GROUP FailGroup
+ )
+{
+ PLIST_ENTRY zombieEntry = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFreeZombieGroupList (FailGroup %p): Entering function.\n",
+ FailGroup));
+
+ while (!IsListEmpty(&FailGroup->ZombieGroupList)) {
+
+ zombieEntry = RemoveHeadList(&FailGroup->ZombieGroupList);
+
+ if (zombieEntry) {
+
+ DsmpFreePool(zombieEntry);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpFreeZombieGroupList (FailGroup %p): Exiting function.\n",
+ FailGroup));
+}
+
+
+NTSTATUS
+DsmpGetDeviceALUAState(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_DEVICE_INFO DeviceInfo,
+ _In_opt_ IN PDSM_DEVICE_STATE DevState
+ )
+/*++
+
+Routine Description:
+
+ Helper routine to build the RTPG info and send it down to retrieve the
+ devInfo's current state.
+
+Arguments:
+
+ DsmContext - DSM context.
+ DeviceInfo - DevInfo whose state needs to be changed.
+ DevState - Current state of passed in DeviceInfo.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate failure code.
+
+--*/
+{
+ PUCHAR targetPortGroupsInfo = NULL;
+ ULONG targetPortGroupsInfoLength = 0;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup = NULL;
+ KIRQL irql;
+ NTSTATUS status;
+ ULONG index;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetDeviceALUAState (DevInfo %p): Entering function.\n",
+ DeviceInfo));
+
+ status = DsmpReportTargetPortGroups(DeviceInfo->TargetObject,
+ &targetPortGroupsInfo,
+ &targetPortGroupsInfoLength);
+
+
+ if (NT_SUCCESS(status) && targetPortGroupsInfo != NULL) {
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ status = DsmpParseTargetPortGroupsInformation(DsmContext,
+ DeviceInfo->Group,
+ targetPortGroupsInfo,
+ targetPortGroupsInfoLength);
+
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ targetPortGroup = DeviceInfo->Group->TargetPortGroupList[index];
+
+ if (targetPortGroup) {
+
+ DsmpUpdateTargetPortGroupDevicesStates(targetPortGroup, targetPortGroup->AsymmetricAccessState);
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ if (DevState) {
+
+ *DevState = DeviceInfo->State;
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetDeviceALUAState (DevInfo %p): ReportTPG failed with %x.\n",
+ DeviceInfo,
+ status));
+ }
+
+ if (targetPortGroupsInfo) {
+
+ DsmpFreePool(targetPortGroupsInfo);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_GENERAL,
+ "DsmpGetDeviceALUAState (DevInfo %p): Exiting function with status %x\n",
+ DeviceInfo,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpRegCopyTree(
+ _In_ IN HANDLE SourceKey,
+ _In_ IN HANDLE DestKey
+ )
+/*++
+
+Routine Description:
+
+ Copies a reg subtree from source key to destination key.
+ This routine will first copy over all the key's values, and then
+ copy the subkeys, each time recursively handling the subkey's
+ values and its subtree.
+
+Arguments:
+
+ SourceKey - Handle to the root of the subtree to copy over.
+ DestKey - Handle to the root of the new tree.
+
+Return Value:
+
+ STATUS_SUCCESS upon successfully coping over the tree.
+ Appropriate NT error code in case of failure.
+
+--*/
+{
+ ULONG numValues = 0;
+ ULONG numSubKeys = 0;
+ ULONG lengthOfValueName = 0;
+ ULONG lengthOfValueData = 0;
+ ULONG lengthOfKeyName = 0;
+ LPWSTR valueBuf = NULL;
+ BYTE *valueDataBuf = NULL;
+ ULONG valueDataType;
+ ULONG titleIndex;
+ HANDLE srcSubKey = NULL;
+ HANDLE destSubKey = NULL;
+ LPWSTR subKey = NULL;
+ NTSTATUS status;
+ PKEY_FULL_INFORMATION keyFullInfo = NULL;
+ ULONG length = sizeof(KEY_FULL_INFORMATION);
+ ULONG index = 0;
+ PKEY_VALUE_FULL_INFORMATION keyValueFullInfo = NULL;
+ PKEY_BASIC_INFORMATION keyBasicInfo = NULL;
+ OBJECT_ATTRIBUTES objectAttributes;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Entering function.\n",
+ SourceKey));
+
+ if (!SourceKey || !DestKey) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // Query the source key for information about number of subkeys, number of values, etc.
+ //
+ do {
+ if (keyFullInfo) {
+
+ DsmpFreePool(keyFullInfo);
+ }
+
+ keyFullInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned, length, DSM_TAG_REG_KEY_RELATED);
+
+ if (!keyFullInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to allocate resources for key full info.\n",
+ SourceKey));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ status = ZwQueryKey(SourceKey,
+ KeyFullInformation,
+ keyFullInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to query key. Status %x.\n",
+ SourceKey,
+ status));
+
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ numSubKeys = keyFullInfo->SubKeys;
+ numValues = keyFullInfo->Values;
+ lengthOfKeyName = keyFullInfo->MaxNameLen + sizeof(WCHAR);
+ lengthOfValueName = keyFullInfo->MaxValueNameLen + sizeof(WCHAR);
+ lengthOfValueData = keyFullInfo->MaxValueDataLen + sizeof(WCHAR);
+
+ //
+ // Allocate a buffer for the name of the value
+ //
+ valueBuf = DsmpAllocatePool(NonPagedPoolNxCacheAligned,
+ lengthOfValueName,
+ DSM_TAG_REG_KEY_RELATED);
+ if (!valueBuf) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to allocate resources for value name.\n",
+ SourceKey));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // Allocate a buffer for the value data
+ //
+ valueDataBuf = DsmpAllocatePool(NonPagedPoolNxCacheAligned,
+ lengthOfValueData,
+ DSM_TAG_REG_KEY_RELATED);
+
+ if (!valueDataBuf) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to allocate resources for value's data.\n",
+ SourceKey));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // First enumerate all of the values
+ //
+ status = STATUS_SUCCESS;
+ for (index = 0; index < numValues && NT_SUCCESS(status); index++) {
+
+ UNICODE_STRING valueName;
+
+ length = sizeof(KEY_VALUE_FULL_INFORMATION);
+
+ do {
+
+ if (keyValueFullInfo) {
+
+ DsmpFreePool(keyValueFullInfo);
+ }
+
+ keyValueFullInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned, length, DSM_TAG_REG_KEY_RELATED);
+
+ if (!keyValueFullInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to allocate resources for value full info.\n",
+ SourceKey));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // Get the information of the index'th value
+ //
+ status = ZwEnumerateValueKey(SourceKey,
+ index,
+ KeyValueFullInformation,
+ keyValueFullInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to enumerate key's value information. Status %x.\n",
+ SourceKey,
+ status));
+
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // Capture the data type, data value, and value name.
+ //
+ titleIndex = keyValueFullInfo->TitleIndex;
+ valueDataType = keyValueFullInfo->Type;
+
+ RtlZeroMemory(valueDataBuf, lengthOfValueData);
+ RtlCopyMemory(valueDataBuf,
+ (PUCHAR)keyValueFullInfo + keyValueFullInfo->DataOffset,
+ keyValueFullInfo->DataLength);
+
+ RtlZeroMemory(valueBuf, lengthOfValueName);
+ RtlStringCbCopyNW(valueBuf, lengthOfValueName, keyValueFullInfo->Name, keyValueFullInfo->NameLength);
+ RtlInitUnicodeString(&valueName, valueBuf);
+
+ //
+ // Copy the value over to the new key
+ //
+ status = ZwSetValueKey(DestKey,
+ &valueName,
+ titleIndex,
+ valueDataType,
+ valueDataBuf,
+ keyValueFullInfo->DataLength);
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to allocate set new key's value information. Status %x.\n",
+ SourceKey,
+ status));
+
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // Allocate buffer for subkey name
+ //
+ subKey = DsmpAllocatePool(NonPagedPoolNxCacheAligned,
+ lengthOfKeyName,
+ DSM_TAG_REG_KEY_RELATED);
+
+ if(!subKey) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to allocate resources for sub key name.\n",
+ SourceKey));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // Now Enumerate all of the subkeys
+ //
+ length = sizeof(KEY_BASIC_INFORMATION);
+ for(index = 0; index < numSubKeys && NT_SUCCESS(status); index++) {
+
+ UNICODE_STRING subKeyName;
+
+ do {
+ if (keyBasicInfo) {
+
+ DsmpFreePool(keyBasicInfo);
+ }
+
+ keyBasicInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned,
+ length,
+ DSM_TAG_REG_KEY_RELATED);
+
+ if (!keyBasicInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to allocate resources for key basic info.\n",
+ SourceKey));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // Enumerate the index'th subkey
+ //
+ status = ZwEnumerateKey(SourceKey,
+ index,
+ KeyBasicInformation,
+ keyBasicInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to enumerate sub key's info. Status %x.\n",
+ SourceKey,
+ status));
+
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ RtlZeroMemory(subKey, lengthOfKeyName);
+ RtlStringCbCopyNW(subKey, lengthOfKeyName, keyBasicInfo->Name, keyBasicInfo->NameLength);
+ RtlInitUnicodeString(&subKeyName, subKey);
+
+ //
+ // Open a handle to the the subkey on the old device.
+ //
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ SourceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ if (srcSubKey) {
+ ZwClose(srcSubKey);
+ srcSubKey = NULL;
+ }
+
+ status = ZwOpenKey(&srcSubKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to open reg key %ws. Status %x.\n",
+ SourceKey,
+ subKey,
+ status));
+
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ DestKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ if (destSubKey) {
+ ZwClose(destSubKey);
+ destSubKey = NULL;
+ }
+
+ //
+ // Create the subkey on the new device.
+ //
+ status = ZwCreateKey(&destSubKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Failed to create reg key %ws. Status %x.\n",
+ SourceKey,
+ subKey,
+ status));
+
+ goto __Exit_DsmpRegCopyTree;
+ }
+
+ //
+ // That's it. We've got everything we need (ie. handles to the two new
+ // subtrees' roots. Call recursively.
+ //
+ status = DsmpRegCopyTree(srcSubKey, destSubKey);
+ }
+
+__Exit_DsmpRegCopyTree:
+
+ if (keyFullInfo) {
+ DsmpFreePool(keyFullInfo);
+ }
+
+ if (valueBuf) {
+ DsmpFreePool(valueBuf);
+ }
+
+ if (valueDataBuf) {
+ DsmpFreePool(valueDataBuf);
+ }
+
+ if (keyValueFullInfo) {
+ DsmpFreePool(keyValueFullInfo);
+ }
+
+ if (subKey) {
+ DsmpFreePool(subKey);
+ }
+
+ if (keyBasicInfo) {
+ DsmpFreePool(keyBasicInfo);
+ }
+
+ if (srcSubKey) {
+ ZwClose(srcSubKey);
+ }
+
+ if (destSubKey) {
+ ZwClose(destSubKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRegCopyTree (SrcKey %p): Exiting function with status %x.\n",
+ SourceKey,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpRegDeleteTree(
+ _In_ IN HANDLE KeyRoot
+ )
+/*++
+Routine Description:
+
+ This routine is a recursive worker that enumerates the subkeys
+ of a given key, applies itself to each one, then deletes itself.
+
+Arguments:
+
+ KeyRoot - Supplies a handle to the root of subtree to be deleted.
+
+Return Value:
+
+ STATUS_SUCCESS - upon successful deletion of subtree.
+ Appropriate NT error code upon failure.
+
+--*/
+{
+ NTSTATUS status;
+ PKEY_FULL_INFORMATION keyFullInfo = NULL;
+ ULONG length = sizeof(KEY_FULL_INFORMATION);
+ ULONG numSubKeys;
+ ULONG lengthOfKeyName;
+ LPWSTR subKey = NULL;
+ PKEY_BASIC_INFORMATION keyBasicInfo = NULL;
+ ULONG index = 0;
+ HANDLE srcSubKey = NULL;
+ OBJECT_ATTRIBUTES objectAttributes;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRegDeleteTree (SrcKey %p): Entering function.\n",
+ KeyRoot));
+
+ if (!KeyRoot) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpRegDeleteTree;
+ }
+
+ //
+ // Query the source key for information about number of subkeys and max
+ // length needed for subkey name.
+ //
+ do {
+ if (keyFullInfo) {
+
+ DsmpFreePool(keyFullInfo);
+ }
+
+ keyFullInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned, length, DSM_TAG_REG_KEY_RELATED);
+
+ if (!keyFullInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegDeleteTree (SrcKey %p): Failed to allocate resources for key full info.\n",
+ KeyRoot));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegDeleteTree;
+ }
+
+ status = ZwQueryKey(KeyRoot,
+ KeyFullInformation,
+ keyFullInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegDeleteTree (SrcKey %p): Failed to query key. Status %x.\n",
+ KeyRoot,
+ status));
+
+ goto __Exit_DsmpRegDeleteTree;
+ }
+
+ numSubKeys = keyFullInfo->SubKeys;
+ lengthOfKeyName = keyFullInfo->MaxNameLen + sizeof(WCHAR);
+
+ if (numSubKeys) {
+
+ //
+ // Allocate buffer for subkey name
+ //
+ subKey = DsmpAllocatePool(NonPagedPoolNxCacheAligned,
+ lengthOfKeyName,
+ DSM_TAG_REG_KEY_RELATED);
+
+ if(!subKey) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegDeleteTree (SrcKey %p): Failed to allocate resources for sub key.\n",
+ KeyRoot));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegDeleteTree;
+ }
+
+ //
+ // Now Enumerate all of the subkeys
+ //
+ index = numSubKeys - 1;
+ length = sizeof(KEY_BASIC_INFORMATION);
+ do {
+
+ UNICODE_STRING subKeyName;
+
+ do {
+ if (keyBasicInfo) {
+
+ DsmpFreePool(keyBasicInfo);
+ }
+
+ keyBasicInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned,
+ length,
+ DSM_TAG_REG_KEY_RELATED);
+
+ if (!keyBasicInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegDeleteTree (SrcKey %p): Failed to allocate resources for key basic info.\n",
+ KeyRoot));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpRegDeleteTree;
+ }
+
+ //
+ // Enumerate the index'th subkey
+ //
+ status = ZwEnumerateKey(KeyRoot,
+ index,
+ KeyBasicInformation,
+ keyBasicInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (NT_SUCCESS(status)) {
+
+ RtlZeroMemory(subKey, lengthOfKeyName);
+ RtlStringCbCopyNW(subKey, lengthOfKeyName, keyBasicInfo->Name, keyBasicInfo->NameLength);
+ RtlInitUnicodeString(&subKeyName, subKey);
+
+ //
+ // Open a handle to the the current root's subkey.
+ //
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ KeyRoot,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(&srcSubKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpRegDeleteTree (SrcKey %p): Failed to open key %ws. Status %x.\n",
+ KeyRoot,
+ subKey,
+ status));
+
+ goto __Exit_DsmpRegDeleteTree;
+ }
+
+ //
+ // Delete this key's subtree (recursively).
+ //
+ status = DsmpRegDeleteTree(srcSubKey);
+
+ ZwClose(srcSubKey);
+ srcSubKey = NULL;
+ }
+
+ index--;
+
+ } while (status != STATUS_NO_MORE_ENTRIES && (LONG)index >= 0);
+
+ if (status == STATUS_NO_MORE_ENTRIES) {
+
+ status = STATUS_SUCCESS;
+ }
+ }
+
+ ZwDeleteKey(KeyRoot);
+
+__Exit_DsmpRegDeleteTree:
+
+ if (srcSubKey) {
+ ZwClose(srcSubKey);
+ }
+
+ if (keyFullInfo) {
+ DsmpFreePool(keyFullInfo);
+ }
+
+ if (subKey) {
+ DsmpFreePool(subKey);
+ }
+
+ if (keyBasicInfo) {
+ DsmpFreePool(keyBasicInfo);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpRegDeleteTree (SrcKey %p): Exiting function with status %x.\n",
+ KeyRoot,
+ status));
+
+ return status;
+}
+
+
+#if defined (_WIN64)
+VOID
+DsmpPassThroughPathTranslate32To64(
+ _In_ IN PMPIO_PASS_THROUGH_PATH32 MpioPassThroughPath32,
+ _Inout_ IN OUT PMPIO_PASS_THROUGH_PATH MpioPassThroughPath64
+ )
+/*++
+
+Routine Description:
+
+ On WIN64, the SCSI_PASS_THROUGH field of the MPIO_PASS_THROUGH_PATH structure
+ sent down by a 32-bit application must be marshaled into a 64-bit version
+ of the structure. This function performs that marshaling.
+
+Arguments:
+
+ MpioPassThroughPath32 - Supplies a pointer to a 32-bit MPIO_PASS_THROUGH_PATH
+ struct.
+
+ MpioPassThroughPath64 - Supplies a pointer to a 64-bit MPIO_PASS_THROUGH_PATH
+ structure, into which we'll copy the marshaled
+ 32-bit data.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ //
+ // Copy the first set of fields out of the 32-bit structure. These
+ // fields all line up between the 32 & 64 bit versions.
+ //
+ // Note that we do NOT adjust the length in the SrbControl. This is to
+ // allow the calling routine to compare the length of the actual
+ // control area against the offsets embedded within. If we adjusted the
+ // length then requests with the sense area backed against the control
+ // area would be rejected because the 64-bit control area is 4 bytes
+ // longer.
+ //
+ RtlCopyMemory(MpioPassThroughPath64,
+ MpioPassThroughPath32,
+ FIELD_OFFSET(SCSI_PASS_THROUGH, DataBufferOffset));
+
+ //
+ // Copy over the CDB.
+ //
+ RtlCopyMemory(MpioPassThroughPath64->PassThrough.Cdb,
+ MpioPassThroughPath32->PassThrough.Cdb,
+ 16 * sizeof(UCHAR)
+ );
+
+ //
+ // Copy over the rest of the fields of the structure.
+ //
+ MpioPassThroughPath64->Version = MpioPassThroughPath32->Version;
+ MpioPassThroughPath64->Length = MpioPassThroughPath32->Length;
+ MpioPassThroughPath64->Flags = MpioPassThroughPath32->Flags;
+ MpioPassThroughPath64->PortNumber = MpioPassThroughPath32->PortNumber;
+ MpioPassThroughPath64->MpioPathId = MpioPassThroughPath32->MpioPathId;
+
+ //
+ // Copy the fields that follow the ULONG_PTR.
+ //
+ MpioPassThroughPath64->PassThrough.DataBufferOffset = (ULONG_PTR)MpioPassThroughPath32->PassThrough.DataBufferOffset;
+ MpioPassThroughPath64->PassThrough.SenseInfoOffset = MpioPassThroughPath32->PassThrough.SenseInfoOffset;
+
+ return;
+}
+
+
+VOID
+DsmpPassThroughPathTranslate64To32(
+ _In_ IN PMPIO_PASS_THROUGH_PATH MpioPassThroughPath64,
+ _Inout_ IN OUT PMPIO_PASS_THROUGH_PATH32 MpioPassThroughPath32
+ )
+/*++
+
+Routine Description:
+
+ On WIN64, the SCSI_PASS_THROUGH field of MPIO_PASS_THROUGH_PATH structure
+ sent down by a 32-bit application must be marshaled into a 64-bit version
+ of the structure. This function marshals a 64-bit version of the structure
+ back into a 32-bit version.
+
+Arguments:
+
+ MpioPassThroughPath64 - Supplies a pointer to a 64-bit MPIO_PASS_THROUGH_PATH
+ struct.
+
+ MpioPassThroughPath32 - Supplies the address of a pointer to a 32-bit
+ MPIO_PASS_THROUGH_PATH structure, into which we'll
+ copy the marshaled 64-bit data.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ //
+ // Copy back the fields through the data offsets.
+ //
+ RtlCopyMemory(MpioPassThroughPath32,
+ MpioPassThroughPath64,
+ FIELD_OFFSET(SCSI_PASS_THROUGH, DataBufferOffset));
+
+
+ //
+ // Copy over the CDB.
+ //
+ RtlCopyMemory(MpioPassThroughPath32->PassThrough.Cdb,
+ MpioPassThroughPath64->PassThrough.Cdb,
+ 16 * sizeof(UCHAR)
+ );
+
+ //
+ // Copy over the rest of the fields of the structure.
+ //
+ MpioPassThroughPath32->Version = MpioPassThroughPath64->Version;
+ MpioPassThroughPath32->Length = MpioPassThroughPath64->Length;
+ MpioPassThroughPath32->Flags = MpioPassThroughPath64->Flags;
+ MpioPassThroughPath32->PortNumber = MpioPassThroughPath64->PortNumber;
+ MpioPassThroughPath32->MpioPathId = MpioPassThroughPath64->MpioPathId;
+
+ return;
+}
+#endif
+
+
+NTSTATUS
+DsmpGetMaxPRRetryTime(
+ _In_ IN PDSM_CONTEXT Context,
+ _Out_ OUT PULONG RetryTime
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to get the max time period for which a PR request failing
+ with a retry-able unit attention should be retried before failing back to MSCS.
+ The value is determined by querying the value found at
+ "msdsm\Parameters\DsmMaximumStateTransitionTime"
+
+Arguments:
+
+ Context - The DSM Context value.
+ RetryTime - The output parameter that will receive the value to be used.
+
+Return Value:
+
+ Status of the RtlQueryRegistryValues call.
+
+--*/
+{
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+ WCHAR registryKeyName[56] = {0};
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetMaxPRRetryTime (DsmCtxt %p): Entering function.\n",
+ Context));
+
+ NT_ASSERT(RetryTime);
+ *RetryTime = DSM_MAX_PR_UNIT_ATTENTION_RETRY_TIME;
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ //
+ // Build the key value name that we want as the base of the query.
+ //
+ RtlStringCbPrintfW(registryKeyName,
+ sizeof(registryKeyName),
+ DSM_PARAMETER_PATH_W);
+
+ //
+ // The query table has two entries. One for the state transition time and
+ // the second which is the 'NULL' terminator.
+ //
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_MAX_STATE_TRANSITION_TIME_VALUE_NAME;
+ queryTable[0].EntryContext = RetryTime;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
+ registryKeyName,
+ queryTable,
+ registryKeyName,
+ NULL);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpGetMaxPRRetryTime (DsmCtxt %p): Exiting function with status %x.\n",
+ Context,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryCacheInformationFromRegistry(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _Out_ OUT PBOOLEAN UseCacheForLeastBlocks,
+ _Out_ OUT PULONGLONG CacheSizeForLeastBlocks
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to get the information about whether sequential IO
+ should use the same path when employing Least Blocks policy.
+ It also queries the size of cache set by the administrator.
+ The value is determined by querying the value found at
+ "msdsm\Parameters\DsmUseCacheForLeastBlocks" and
+ "msdsm\Parameters\DsmCacheSizeForLeastBlocks"
+
+Arguments:
+
+ Context - The DSM Context value.
+ UseCacheForLeastBlocks - Returns the flag that indicates whether or not to
+ use same path for sequential IO when LB policy
+ is Least Blocks.
+ CacheSizeForLeastBlocks - Returns the size of the cache (in bytes) set by
+ the Admin to indicate the amount of sequential
+ data that should be use the same path when LB
+ policy is Least Blocks.
+
+Return Value:
+
+ Status of the RtlQueryRegistryValues call.
+
+--*/
+{
+ RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0};
+ WCHAR registryKeyName[56] = {0};
+ HANDLE parametersKey = NULL;
+ UNICODE_STRING keyValueName;
+ NTSTATUS status;
+ struct _cacheSizeForLeastBlocks {
+ KEY_VALUE_PARTIAL_INFORMATION KeyValueInfo;
+ ULONGLONG Data;
+ } cacheSizeForLeastBlocks;
+ ULONG length = 0;
+ BOOLEAN useCacheForLeastBlocksDefault = FALSE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryCacheInformationFromRegistry (DsmCtxt %p): Entering function.\n",
+ DsmContext));
+
+ NT_ASSERT(UseCacheForLeastBlocks);
+ NT_ASSERT(CacheSizeForLeastBlocks);
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ //
+ // Build the key value name that we want as the base of the query.
+ //
+ RtlStringCbPrintfW(registryKeyName,
+ sizeof(registryKeyName),
+ DSM_PARAMETER_PATH_W);
+
+ //
+ // The query table has two entries. One for whether to use cache, and
+ // and the second which is the 'NULL' terminator.
+ //
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_USE_CACHE_FOR_LEAST_BLOCKS;
+ queryTable[0].EntryContext = UseCacheForLeastBlocks;
+ queryTable[0].DefaultType = (REG_BINARY << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_BINARY;
+ queryTable[0].DefaultLength = sizeof(BOOLEAN);
+ queryTable[0].DefaultData = &useCacheForLeastBlocksDefault;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
+ registryKeyName,
+ queryTable,
+ registryKeyName,
+ NULL);
+
+ if (NT_SUCCESS(status)) {
+
+ status = DsmpOpenDsmServicesParametersKey(KEY_QUERY_VALUE, &parametersKey);
+
+ if (NT_SUCCESS(status)) {
+
+ RtlInitUnicodeString(&keyValueName, DSM_CACHE_SIZE_FOR_LEAST_BLOCKS);
+
+ status = ZwQueryValueKey(parametersKey,
+ &keyValueName,
+ KeyValuePartialInformation,
+ &cacheSizeForLeastBlocks,
+ sizeof(cacheSizeForLeastBlocks),
+ &length);
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(cacheSizeForLeastBlocks.KeyValueInfo.DataLength == sizeof(ULONGLONG));
+ *CacheSizeForLeastBlocks = *((ULONGLONG UNALIGNED *)&(cacheSizeForLeastBlocks.KeyValueInfo.Data));
+ }
+ }
+
+ if (parametersKey) {
+ ZwClose(parametersKey);
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "DsmpQueryCacheInformationFromRegistry (DsmCtxt %p): Exiting function with status %x.\n",
+ DsmContext,
+ status));
+
+ return status;
+}
+
+BOOLEAN
+DsmpConvertSharedSpinLockToExclusive(
+ _Inout_ _Requires_lock_held_(*_Curr_) PEX_SPIN_LOCK SpinLock
+ )
+/*++
+
+Routine Description:
+
+ This routine is a wrapper around ExTryConvertSharedSpinLockExclusive() that
+ guarantees the given EX_SPIN_LOCK will be acquired in Exclusive mode once
+ this function returns.
+
+ It's possible the lock may be released and re-acquired within this function
+ so the caller should be very careful about the use of this function.
+
+ N.B. The caller MUST have acquired the given lock in Shared mode before
+ calling this function.
+
+Arguments:
+
+ SpinLock - The EX_SPIN_LOCK to convert from Shared to Exclusive mode.
+
+Return Value:
+
+ Status of the ExTryConvertSharedSpinLockExclusive() call. This function
+ will always return with the lock acquired in Exclusive mode. The FALSE is
+ returned, then the lock had to be released and re-acquired.
+
+--*/
+{
+ BOOLEAN converted = FALSE;
+
+ converted = (BOOLEAN)ExTryConvertSharedSpinLockExclusive(SpinLock);
+
+ //
+ // If the conversion attempt failed, then we should release the lock from
+ // Shared mode and try to pick it back up in Exclusive mode to guarantee
+ // this function will always return with the lock in Exclusive mode.
+ //
+ if (converted == FALSE) {
+ ExReleaseSpinLockSharedFromDpcLevel(SpinLock);
+ ExAcquireSpinLockExclusiveAtDpcLevel(SpinLock);
+ }
+
+ return converted;
+}
+
+
diff --git a/tests/projects/wdk/kmdf/msdsm/wmi.c b/tests/projects/wdk/kmdf/msdsm/wmi.c
new file mode 100644
index 000000000..0ee882192
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/wmi.c
@@ -0,0 +1,3822 @@
+
+/*++
+
+Copyright (C) 2004-2010 Microsoft Corporation
+
+Module Name:
+
+ wmi.c
+
+Abstract:
+
+ This driver is the Microsoft Device Specific Module (DSM).
+ It exports behaviours that mpio.sys will use to determine how to
+ multipath SPC-3 compliant devices.
+
+ This file contains WMI related functions.
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+--*/
+
+
+
+#include "precomp.h"
+#include "msdsmwmi.h"
+#include "msdsmdsm.h"
+
+#ifdef DEBUG_USE_WPP
+#include "wmi.tmh"
+#endif
+
+#pragma warning (disable:4305)
+
+extern BOOLEAN DoAssert;
+
+#define USE_BINARY_MOF_RESOURCE
+
+#define DSM_INVALID_LOAD_BALANCE_POLICY STATUS_INVALID_PARAMETER
+#define DSM_UNSUPPORTED_VERSION STATUS_NOT_SUPPORTED
+
+//
+// Max length for each of the DeviceId strings (supported device list)
+// NOTE: This must be kept in sync with msdsmdsm.mof
+//
+#define MSDSM_MAX_DEVICE_ID_LENGTH 31
+#define MSDSM_MAX_DEVICE_ID_SIZE (MSDSM_MAX_DEVICE_ID_LENGTH * sizeof(WCHAR))
+
+//
+// List of supported DSM-centric guids
+//
+GUID MSDSM_SUPPORTED_DEVICES_LISTGUID = MSDSM_SUPPORTED_DEVICES_LISTGuid;
+GUID MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICYGUID = MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICYGuid;
+GUID MSDSM_DEFAULT_LOAD_BALANCE_POLICYGUID = MSDSM_DEFAULT_LOAD_BALANCE_POLICYGuid;
+
+//
+// Symbolic names for the DSM-centric guid indexes
+//
+#define MSDSM_SUPPORTED_DEVICES_LISTGUID_Index 0
+#define MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICYGUID_Index 1
+#define MSDSM_DEFAULT_LOAD_BALANCE_POLICYGUID_Index 2
+
+WMIGUIDREGINFO MSDsmGuidList[] = {
+ {
+ &MSDSM_SUPPORTED_DEVICES_LISTGUID,
+ 1,
+ 0
+ },
+
+ {
+ &MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICYGUID,
+ 1,
+ 0
+ },
+
+ {
+ &MSDSM_DEFAULT_LOAD_BALANCE_POLICYGUID,
+ 1,
+ 0
+ }
+};
+
+#define MSDsmGuidCount (sizeof(MSDsmGuidList) / sizeof(WMIGUIDREGINFO))
+
+//
+// List of supported Device-centric guids
+//
+GUID DSM_LBOperationsGUID = DSM_LB_OperationsGuid;
+GUID DSM_QueryLBPolicyGUID = DSM_QueryLBPolicyGuid;
+GUID DSM_QuerySupportedLBPoliciesGUID = DSM_QuerySupportedLBPoliciesGuid;
+GUID DSM_QueryDsmUniqueIdGUID = DSM_QueryUniqueIdGuid;
+GUID DSM_QueryLBPolicyV2GUID = DSM_QueryLBPolicy_V2Guid;
+GUID DSM_QuerySupportedLBPoliciesV2GUID = DSM_QuerySupportedLBPolicies_V2Guid;
+GUID MSDSM_DEVICE_PERFGUID = MSDSM_DEVICE_PERFGuid;
+GUID MSDSM_WMI_METHODSGUID = MSDSM_WMI_METHODSGuid;
+
+//
+// Symbolic names for the Device-centric guid indexes
+//
+#define DSM_LBOperationsGUID_Index 0
+#define DSM_QueryLBPolicyGUID_Index 1
+#define DSM_QuerySupportedLBPoliciesGUID_Index 2
+#define DSM_QueryDsmUniqueIdGUID_Index 3
+#define DSM_QueryLBPolicyV2GUID_Index 4
+#define DSM_QuerySupportedLBPoliciesV2GUID_Index 5
+#define MSDSM_DEVICE_PERFGuidIndex 6
+#define MSDSM_WMI_METHODSGuidIndex 7
+
+WMIGUIDREGINFO DsmGuidList[] = {
+ {
+ &DSM_LBOperationsGUID,
+ 1,
+ 0
+ },
+
+ {
+ &DSM_QueryLBPolicyGUID,
+ 1,
+ 0
+ },
+
+ {
+ &DSM_QuerySupportedLBPoliciesGUID,
+ 1,
+ 0
+ },
+
+ {
+ &DSM_QueryDsmUniqueIdGUID,
+ 1,
+ 0
+ },
+
+ {
+ &DSM_QueryLBPolicyV2GUID,
+ 1,
+ 0
+ },
+
+ {
+ &DSM_QuerySupportedLBPoliciesV2GUID,
+ 1,
+ 0
+ },
+
+ {
+ &MSDSM_DEVICE_PERFGUID,
+ 1,
+ 0
+ },
+
+ {
+ &MSDSM_WMI_METHODSGUID,
+ 1,
+ 0
+ }
+};
+
+#define DsmGuidCount (sizeof(DsmGuidList) / sizeof(WMIGUIDREGINFO))
+
+VOID
+DsmpDsmWmiInitialize(
+ _In_ IN PDSM_WMILIB_CONTEXT WmiGlobalInfo,
+ _In_ IN PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+
+ This routine intializes the DSM-specific WmiGlobalInfo structure that is passed
+ back to MPIO during DriverEntry.
+
+Arguments:
+
+ WmiGlobalInfo - WMI information structure to initialize.
+ RegistryPath - Registry path to the service key for this driver.
+
+Return Value:
+
+ None
+
+--*/
+{
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmpDsmWmiInitialize (RegPath %ws): Entering function.\n",
+ RegistryPath->Buffer));
+
+ RtlZeroMemory(WmiGlobalInfo, sizeof(DSM_WMILIB_CONTEXT));
+
+ //
+ // Build the mof resource name. This tells wmi via the busdriver,
+ // where to find the mof data. This is found in the .rc.
+ //
+ RtlInitUnicodeString(&WmiGlobalInfo->MofResourceName, L"DsmMofResourceName");
+
+ //
+ // This will jam in the entry points and guids for supported WMI
+ // operations. SetDataBlock, SetDataItem, ExecuteMethod and FunctionControl are
+ // currently not needed, so leave them set to zero.
+ //
+ WmiGlobalInfo->GuidCount = MSDsmGuidCount;
+ WmiGlobalInfo->GuidList = MSDsmGuidList;
+
+ WmiGlobalInfo->QueryWmiDataBlockEx = DsmGlobalQueryData;
+ WmiGlobalInfo->SetWmiDataBlockEx = DsmGlobalSetData;
+
+ //
+ // Allocate a buffer for the reg. path.
+ //
+ WmiGlobalInfo->RegistryPath.Buffer = DsmpAllocatePool(NonPagedPoolNx,
+ RegistryPath->MaximumLength,
+ DSM_TAG_REG_PATH);
+ if (WmiGlobalInfo->RegistryPath.Buffer) {
+
+ //
+ // Set maximum length of the new string and copy it.
+ //
+ WmiGlobalInfo->RegistryPath.MaximumLength = RegistryPath->MaximumLength;
+
+ RtlCopyUnicodeString(&WmiGlobalInfo->RegistryPath, RegistryPath);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_INIT,
+ "DsmpDsmWmiInitialize (RegPath %ws): Failed to allocate memory for Registry path in WmiGlobalInfo.\n",
+ RegistryPath->Buffer));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmpDsmWmiInitialize (RegPath %ws): Exiting function.\n",
+ RegistryPath->Buffer));
+
+ return;
+}
+
+
+NTSTATUS
+DsmGlobalQueryData(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG InstanceCount,
+ _Inout_ IN OUT PULONG InstanceLengthArray,
+ _In_ IN ULONG BufferAvail,
+ _Out_writes_to_(BufferAvail, *DataLength) OUT PUCHAR Buffer,
+ _Out_ OUT PULONG DataLength,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ This is the WMI query entry point for DSM-specific GUIDs. The index into the
+ GUID array is found and assuming the buffer is large enough, the data will be
+ copied over.
+
+Arguments:
+
+ DsmContext - Global DSM Context
+ DsmIds - Dsm Ids
+ Irp - The WMI Irp
+ GuidIndex - Index into the WMIGUIDINFO array
+ InstanceIndex - Index of the data instance
+ InstanceCount - Number of instances
+ InstanceLengthArray - Array of ULONGs that indicate per-instance data lengths.
+ BufferAvail - Size of the buffer in which data is returned.
+ Buffer - Buffer in which the data is returned.
+ DataLength - Storage for the actual data length written.
+
+Return Value:
+
+ STATUS_BUFFER_TOO_SMALL - If output buffer is not big enough to
+ to return all the available data.
+ STATUS_WMI_GUID_NOT_FOUND - If GuidIndex doesn't correspond to an actual entry
+ in the reginfo array.
+ STATUS_SUCCESS - On success.
+
+--*/
+{
+ NTSTATUS status = STATUS_WMI_GUID_NOT_FOUND;
+ UNREFERENCED_PARAMETER(DsmContext);
+ UNREFERENCED_PARAMETER(InstanceLengthArray);
+ UNREFERENCED_PARAMETER(InstanceCount);
+ UNREFERENCED_PARAMETER(InstanceIndex);
+ UNREFERENCED_PARAMETER(Irp);
+ UNREFERENCED_PARAMETER(DsmIds);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmGlobalQueryData (DsmContext %p): Entering function - GuidIndex %u.\n",
+ DsmContext,
+ GuidIndex));
+
+ //
+ // Check the GuidIndex - the index into the DsmGuildList array - to see
+ // whether this is a supported GUID or not.
+ //
+ switch(GuidIndex) {
+
+ case MSDSM_SUPPORTED_DEVICES_LISTGUID_Index: {
+
+ *DataLength = BufferAvail;
+
+ status = DsmpQuerySupportedDevicesList(DsmContext,
+ BufferAvail,
+ DataLength,
+ Buffer);
+
+ break;
+ }
+
+ case MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICYGUID_Index: {
+
+ *DataLength = BufferAvail;
+
+ status = DsmpQueryTargetsDefaultPolicy(DsmContext,
+ BufferAvail,
+ DataLength,
+ Buffer);
+
+ break;
+ }
+
+ case MSDSM_DEFAULT_LOAD_BALANCE_POLICYGUID_Index: {
+
+ *DataLength = BufferAvail;
+
+ status = DsmpQueryDsmDefaultPolicy(DsmContext,
+ BufferAvail,
+ DataLength,
+ Buffer);
+
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalQueryData (DsmContext %p): Unknown GuidIndex %d.\n",
+ DsmContext,
+ GuidIndex));
+
+ *DataLength = 0;
+
+ break;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmGlobalQueryData (DsmContext %p): Exiting function with status 0x%x.\n",
+ DsmContext,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmGlobalSetData(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG BufferAvail,
+ _In_reads_bytes_(BufferAvail) IN PUCHAR Buffer,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ This is the WMI set entry point for DSM-specific GUIDs. The index into the
+ GUID array is found and the contents of the buffer are set to the passed in
+ instance index.
+
+Arguments:
+
+ DsmContext - Global DSM Context
+ DsmIds - Dsm Ids
+ Irp - The WMI Irp
+ GuidIndex - Index into the WMIGUIDINFO array
+ InstanceIndex - Index of the data instance
+ BufferAvail - Size of the buffer in which data is returned.
+ Buffer - Buffer in which the data is returned.
+
+Return Value:
+
+ STATUS_BUFFER_TOO_SMALL - If output buffer is not big enough to
+ to return all the available data.
+ STATUS_WMI_GUID_NOT_FOUND - If GuidIndex doesn't correspond to an actual entry
+ in the reginfo array.
+ STATUS_SUCCESS - On success.
+
+--*/
+{
+ NTSTATUS status = STATUS_WMI_GUID_NOT_FOUND;
+ ULONG dataLength;
+ PDSM_CONTEXT dsmContext = (PDSM_CONTEXT)DsmContext;
+
+ UNREFERENCED_PARAMETER(DsmIds);
+ UNREFERENCED_PARAMETER(Irp);
+ UNREFERENCED_PARAMETER(InstanceIndex);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): Entering function - GuidIndex %u.\n",
+ DsmContext,
+ GuidIndex));
+
+ switch (GuidIndex) {
+
+ case MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICYGUID_Index: {
+
+ PMSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICY targetsPolicyInfo = (PMSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICY)Buffer;
+ PMSDSM_TARGET_DEFAULT_POLICY_INFO targetPolicyInfo;
+ PWSTR vidpidIndex;
+ DSM_LOAD_BALANCE_TYPE loadBalancePolicy;
+ ULONGLONG preferredPath;
+ DWORD index;
+ NTSTATUS errorStatus = STATUS_SUCCESS;
+
+ //
+ // Determine the correct buffer size.
+ //
+ dataLength = AlignOn8Bytes(FIELD_OFFSET(MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICY, TargetDefaultPolicyInfo));
+
+ if (BufferAvail < dataLength) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): GuidIndex %u. Incorrect buffer size. Status %x\n",
+ DsmContext,
+ GuidIndex,
+ status));
+ break;
+ }
+
+ dataLength += targetsPolicyInfo->NumberDevices * sizeof(MSDSM_TARGET_DEFAULT_POLICY_INFO);
+
+ if (BufferAvail < dataLength) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): GuidIndex %u. Incorrect buffer size for %u targets. Status %x\n",
+ DsmContext,
+ GuidIndex,
+ targetsPolicyInfo->NumberDevices,
+ status));
+ break;
+ }
+
+ targetPolicyInfo = targetsPolicyInfo->TargetDefaultPolicyInfo;
+
+ for (index = 0; index < targetsPolicyInfo->NumberDevices; index++, targetPolicyInfo++) {
+
+ size_t stringLength = 0;
+
+ //
+ // First ensure that these values make sense. The VID/PID should be
+ // a string of 8+16 chars and the LB policy must be one that MSDSM
+ // supports.
+ //
+ // The WMI string is like a unicode string with the first USHORT
+ // containing the size.
+ //
+ vidpidIndex = targetPolicyInfo->HardwareId;
+ vidpidIndex++;
+
+ if (!NT_SUCCESS(RtlStringCchLengthW(vidpidIndex, DSM_VENDPROD_ID_LEN + 1, &stringLength)) || (stringLength != DSM_VENDPROD_ID_LEN)) {
+
+ errorStatus = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): GuidIndex %u. Ignoring incorrect VID/PID %ws. Status %x\n",
+ DsmContext,
+ GuidIndex,
+ vidpidIndex,
+ errorStatus));
+ continue;
+ }
+
+ if (targetPolicyInfo->LoadBalancePolicy >= DSM_LB_VENDOR_SPECIFIC) {
+
+ errorStatus = STATUS_INVALID_PARAMETER;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): GuidIndex %u. Ignoring policy %u for %ws. Status %x\n",
+ DsmContext,
+ GuidIndex,
+ targetPolicyInfo->LoadBalancePolicy,
+ vidpidIndex,
+ errorStatus));
+ continue;
+ }
+
+ loadBalancePolicy = targetPolicyInfo->LoadBalancePolicy;
+ preferredPath = (ULONGLONG)((ULONG_PTR)targetPolicyInfo->PreferredPath);
+
+ //
+ // Now update/create the key in the registry with the LB policy info.
+ // If the LB policy is specified as 0, delete the key.
+ //
+ status = DsmpSetVidPidLBPolicyInRegistry(vidpidIndex, loadBalancePolicy, preferredPath);
+
+ //
+ // If above was successful, find the group that corresponds to this
+ // targetId and update its LB policy as well as the states of the paths
+ //
+ if (NT_SUCCESS(status)) {
+
+ DsmpSetLBForVidPidPolicyAdjustment(dsmContext, vidpidIndex, loadBalancePolicy, preferredPath);
+ } else {
+ errorStatus = status;
+ }
+ }
+
+ //
+ // If any error occurred, return the last error.
+ //
+ if (!NT_SUCCESS(errorStatus)) {
+ status = errorStatus;
+ }
+
+ break;
+ }
+
+ case MSDSM_DEFAULT_LOAD_BALANCE_POLICYGUID_Index: {
+
+ PMSDSM_DEFAULT_LOAD_BALANCE_POLICY dsmPolicyInfo = (PMSDSM_DEFAULT_LOAD_BALANCE_POLICY)Buffer;
+ DSM_LOAD_BALANCE_TYPE loadBalancePolicy;
+ ULONGLONG preferredPath;
+
+ //
+ // Determine the correct buffer size.
+ //
+ dataLength = sizeof(MSDSM_DEFAULT_LOAD_BALANCE_POLICY);
+
+ if (BufferAvail < dataLength) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): GuidIndex %u. Incorrect buffer size. Status %x\n",
+ DsmContext,
+ GuidIndex,
+ status));
+ break;
+ }
+
+ loadBalancePolicy = dsmPolicyInfo->LoadBalancePolicy;
+ preferredPath = (ULONGLONG)((ULONG_PTR)dsmPolicyInfo->PreferredPath);
+
+ //
+ // First ensure that the values make sense.
+ //
+ if (loadBalancePolicy >= DSM_LB_VENDOR_SPECIFIC) {
+
+ status = STATUS_INVALID_PARAMETER;
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): GuidIndex %u. Invalid policy %u specified. Status %x\n",
+ DsmContext,
+ GuidIndex,
+ loadBalancePolicy,
+ status));
+
+ } else {
+
+ //
+ // Update/create the values in the registry with the LB policy info.
+ // If the LB policy is specified as 0, delete the values.
+ //
+ status = DsmpSetDsmLBPolicyInRegistry(loadBalancePolicy, preferredPath);
+
+ //
+ // If above is successful, find the groups that haven't had their LB policy
+ // explicitly set or haven't had their policy set in accordance with target
+ // hardware id. For each of these, adjust the states of the paths as well.
+ //
+ if (NT_SUCCESS(status)) {
+
+ DsmpSetLBForDsmPolicyAdjustment(dsmContext, loadBalancePolicy, preferredPath);
+ }
+ }
+
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): Unknown GuidIndex %d.\n",
+ DsmContext,
+ GuidIndex));
+
+ break;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmGlobalSetData (DsmContext %p): Exiting function with status 0x%x.\n",
+ DsmContext,
+ status));
+
+ return status;
+}
+
+
+VOID
+DsmpWmiInitialize(
+ _In_ IN PDSM_WMILIB_CONTEXT WmiInfo,
+ _In_ IN PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+
+ This routine intializes the Device-specific WmiInfo structure that is passed
+ back to MPIO during DriverEntry.
+
+Arguments:
+
+ WmiInfo - WMI information structure to initialize.
+ RegistryPath - Registry path to the service key for this driver.
+
+Return Value:
+
+ None
+
+--*/
+{
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmpWmiInitialize (RegPath %ws): Entering function.\n",
+ RegistryPath->Buffer));
+
+ RtlZeroMemory(WmiInfo, sizeof(DSM_WMILIB_CONTEXT));
+
+ //
+ // Build the mof resource name. This tells wmi via the busdriver,
+ // where to find the mof data. This is found in the .rc.
+ //
+ RtlInitUnicodeString(&WmiInfo->MofResourceName, L"MofResourceName");
+
+ //
+ // This will jam in the entry points and guids for supported WMI
+ // operations. SetDataBlock, SetDataItem, and FunctionControl are
+ // currently not needed, so leave them set to zero.
+ //
+ WmiInfo->GuidCount = DsmGuidCount;
+ WmiInfo->GuidList = DsmGuidList;
+
+ WmiInfo->QueryWmiDataBlockEx = DsmQueryData;
+ WmiInfo->ExecuteWmiMethodEx = DsmExecuteMethod;
+
+ //
+ // Allocate a buffer for the reg. path.
+ //
+ WmiInfo->RegistryPath.Buffer = DsmpAllocatePool(NonPagedPoolNx,
+ RegistryPath->MaximumLength,
+ DSM_TAG_REG_PATH);
+ if (WmiInfo->RegistryPath.Buffer) {
+
+ //
+ // Set maximum length of the new string and copy it.
+ //
+ WmiInfo->RegistryPath.MaximumLength = RegistryPath->MaximumLength;
+
+ RtlCopyUnicodeString(&WmiInfo->RegistryPath, RegistryPath);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_INIT,
+ "DsmpWmiInitialize (RegPath %ws): Failed to allocate memory for Registry path in WMIInfo.\n",
+ RegistryPath->Buffer));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_INIT,
+ "DsmpWmiInitialize (RegPath %ws): Exiting function.\n",
+ RegistryPath->Buffer));
+
+ return;
+}
+
+
+NTSTATUS
+DsmQueryData(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG InstanceCount,
+ _Inout_ IN OUT PULONG InstanceLengthArray,
+ _In_ IN ULONG BufferAvail,
+ _When_(GuidIndex == DSM_LBOperationsGUID_Index || GuidIndex == MSDSM_WMI_METHODSGuidIndex, _Pre_notnull_ _Const_)
+ _When_(!(GuidIndex == DSM_LBOperationsGUID_Index || GuidIndex == MSDSM_WMI_METHODSGuidIndex), _Out_writes_to_(BufferAvail, *DataLength))
+ OUT PUCHAR Buffer,
+ _Out_ OUT PULONG DataLength,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ This is the main WMI query entry point. The index into the GUID array is found
+ and assuming the buffer is large enough, the data will be copied over.
+
+Arguments:
+
+ DsmContext - Global DSM Context
+ DsmIds - Dsm Ids
+ Irp - The WMI Irp
+ GuidIndex - Index into the WMIGUIDINFO array
+ InstanceIndex - Index of the data instance
+ InstanceCount - Number of instances
+ InstanceLengthArray - Array of ULONGs that indicate per-instance data lengths.
+ BufferAvail - Size of the buffer in which data is returned.
+ Buffer - Buffer in which the data is returned.
+ DataLength - Storage for the actual data length written.
+
+Return Value:
+
+ STATUS_BUFFER_TOO_SMALL - If output buffer is not big enough to
+ to return all the available data.
+ STATUS_WMI_GUID_NOT_FOUND - If GuidIndex doesn't correspond to an actual entry
+ in the reginfo array.
+ STATUS_SUCCESS - On success.
+
+--*/
+{
+ ULONG sizeNeeded;
+ NTSTATUS status = STATUS_WMI_GUID_NOT_FOUND;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+ UNREFERENCED_PARAMETER(InstanceCount);
+ UNREFERENCED_PARAMETER(InstanceIndex);
+ UNREFERENCED_PARAMETER(Irp);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmQueryData (DsmIds %p): Entering function - GuidIndex %u.\n",
+ DsmIds,
+ GuidIndex));
+
+ //
+ // Check the GuidIndex - the index into the DsmGuildList array - to see
+ // whether this is a supported GUID or not.
+ //
+ switch(GuidIndex) {
+
+ case DSM_LBOperationsGUID_Index: {
+
+ //
+ // Even though this class only has methods, we need to respond
+ // to any queries for it since WMI expects that there is an actual
+ // instance of the class on which to execute the method
+ //
+
+ sizeNeeded = sizeof(ULONG);
+
+ *DataLength = sizeNeeded;
+
+ if (BufferAvail >= sizeNeeded) {
+
+ *InstanceLengthArray = sizeNeeded;
+ status = STATUS_SUCCESS;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_WMI,
+ "DsmQueryData (DsmIds %p): Buffer too small in query data. Needed %d, Given %d.\n",
+ DsmIds,
+ sizeNeeded,
+ BufferAvail));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ }
+
+ break;
+ }
+
+ case DSM_QueryLBPolicyGUID_Index:
+ case DSM_QueryLBPolicyV2GUID_Index: {
+
+ *DataLength = BufferAvail;
+
+ status = DsmpQueryLoadBalancePolicy(DsmContext,
+ DsmIds,
+ ((GuidIndex == DSM_QueryLBPolicyGUID_Index) ? DSM_WMI_VERSION_1 : DSM_WMI_VERSION_2),
+ BufferAvail,
+ DataLength,
+ Buffer);
+ break;
+ }
+
+ case DSM_QuerySupportedLBPoliciesGUID_Index:
+ case DSM_QuerySupportedLBPoliciesV2GUID_Index: {
+
+ *DataLength = BufferAvail;
+
+ status = DsmpQuerySupportedLBPolicies(DsmContext,
+ DsmIds,
+ BufferAvail,
+ ((GuidIndex == DSM_QuerySupportedLBPoliciesGUID_Index) ? DSM_WMI_VERSION_1 : DSM_WMI_VERSION_2),
+ DataLength,
+ Buffer);
+ break;
+ }
+
+ case DSM_QueryDsmUniqueIdGUID_Index: {
+
+ PDSM_QueryUniqueId dsmQueryUniqueId;
+
+ *DataLength = sizeof(DSM_QueryUniqueId);
+
+ if (BufferAvail >= sizeof(DSM_QueryUniqueId)) {
+
+ dsmQueryUniqueId = (PDSM_QueryUniqueId) Buffer;
+ dsmQueryUniqueId->DsmUniqueId = (ULONGLONG)((ULONG_PTR)DsmContext);
+ status = STATUS_SUCCESS;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmQueryData (DsmIds %p): Buffersize %d too small for Query Unique Id.\n",
+ DsmIds,
+ BufferAvail));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ }
+
+ break;
+ }
+
+ case MSDSM_DEVICE_PERFGuidIndex: {
+
+ *DataLength = BufferAvail;
+
+ status = DsmpQueryDevicePerf(DsmContext,
+ DsmIds,
+ BufferAvail,
+ DataLength,
+ Buffer);
+
+ break;
+ }
+
+ case MSDSM_WMI_METHODSGuidIndex: {
+
+ //
+ // Even though this class only has methods, we need to respond
+ // to any queries for it since WMI expects that there is an actual
+ // instance of the class on which to execute the method
+ //
+
+ sizeNeeded = sizeof(ULONG);
+
+ *DataLength = sizeNeeded;
+
+ if (BufferAvail >= sizeNeeded) {
+
+ *InstanceLengthArray = sizeNeeded;
+ status = STATUS_SUCCESS;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_WMI,
+ "DsmQueryData (DsmIds %p): Buffer too small in query data. Needed %d, Given %d.\n",
+ DsmIds,
+ sizeNeeded,
+ BufferAvail));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ }
+
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmQueryData (DsmIds %p): Unknown GuidIndex %d in DsmQueryData.\n",
+ DsmIds,
+ GuidIndex));
+
+ *DataLength = 0;
+
+ break;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmQueryData (DsmIds %p): Exiting function with status 0x%x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryLoadBalancePolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN ULONG InBufferSize,
+ _In_ IN PULONG OutBufferSize,
+ _Out_writes_bytes_(*OutBufferSize) OUT PVOID Buffer
+ )
+/*+++
+
+Routine Description:
+
+ This routine returns the current Load Balance policy settings
+ for the given device.
+
+Arguements:
+
+ DsmContext - Global DSM context
+ DsmIds - DSM Ids for the given device
+ DsmWmiVersion - version of the MPIO_DSM_Path class to use
+ InBufferSize - Size of the input buffer
+ OutBufferSize - Size of the output buffer
+ Buffer - Buffer in which the current Load Balance policy settings
+ is returned, if the buffer is big enough
+
+Return Value:
+
+ STATUS_SUCCESS on success
+ Appropriate error code on error.
+
+--*/
+{
+ PDSM_GROUP_ENTRY groupEntry;
+ PDSM_DEVICE_INFO devInfo;
+ PDSM_DEVICE_INFO rtpgDeviceInfo = NULL;
+ ULONG inx;
+ ULONG sizeNeeded;
+ NTSTATUS status = STATUS_SUCCESS;
+ KIRQL irql;
+ PDSM_Load_Balance_Policy_V2 supportedLBPolicies;
+ PMPIO_DSM_Path_V2 dsmPath;
+ PDSM_FAILOVER_GROUP foGroup;
+ ULONG SpecialHandlingFlag = 0;
+
+ UNREFERENCED_PARAMETER(InBufferSize);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryLoadBalancePolicy (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // At least one device should be given
+ //
+ if (DsmIds->Count == 0) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryLoadBalancePolicy (DsmIds %p): No DSM Ids given in DsmpQueryLoadBalancePolicy.\n",
+ DsmIds));
+
+ *OutBufferSize = 0;
+ status = STATUS_INVALID_PARAMETER;
+
+ goto __Exit_DsmpQueryLoadBalancePolicy;
+ }
+
+ //
+ // Compute the size needed for returning LoadBalance policy information
+ //
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ sizeNeeded = AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy, DSM_Paths));
+ sizeNeeded += (DsmIds->Count) * sizeof(MPIO_DSM_Path);
+
+ } else {
+
+ sizeNeeded = AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy_V2, DSM_Paths));
+ sizeNeeded += (DsmIds->Count * sizeof(MPIO_DSM_Path_V2));
+ }
+
+ if (*OutBufferSize < sizeNeeded) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryLoadBalancePolicy (DsmIds %p): Output buffer too small for QueryLBPolicy.\n",
+ DsmIds));
+
+ *OutBufferSize = sizeNeeded;
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ goto __Exit_DsmpQueryLoadBalancePolicy;
+ }
+
+ //
+ // Set the size of the data returned to user
+ //
+ *OutBufferSize = sizeNeeded;
+
+ //
+ // Zero out the output buffer first
+ //
+ RtlZeroMemory(Buffer, sizeNeeded);
+
+ devInfo = DsmIds->IdList[0];
+ DSM_ASSERT(devInfo && devInfo->DeviceSig == DSM_DEVICE_SIG);
+ groupEntry = devInfo->Group;
+
+ //
+ // Send down an RTPG to get the current state info if implicit transitions
+ // are supported, since the states may have changed from under us.
+ // Storages that support both implicit and explicit transitions that haven't
+ // allowed us to turn OFF their implicit transitions, may have also changed
+ // TPG states from under us. So do this for such storages also.
+ //
+ if (!DsmpIsSymmetricAccess(devInfo) &&
+ devInfo->ALUASupport != DSM_DEVINFO_ALUA_EXPLICIT) {
+
+ rtpgDeviceInfo = DsmpGetActivePathToBeUsed(groupEntry, FALSE, SpecialHandlingFlag);
+
+ if (!rtpgDeviceInfo) {
+
+ BOOLEAN sendTPG = FALSE;
+
+ rtpgDeviceInfo = DsmpFindStandbyPathToActivateALUA(groupEntry, &sendTPG, SpecialHandlingFlag);
+ }
+
+ if (rtpgDeviceInfo) {
+
+ status = DsmpGetDeviceALUAState(DsmContext, rtpgDeviceInfo, NULL);
+ }
+ }
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // If an RTPG was sent down, update all the devInfo states.
+ //
+ if (NT_SUCCESS(status) && rtpgDeviceInfo) {
+
+ DsmpAdjustDeviceStatesALUA(groupEntry, NULL, SpecialHandlingFlag);
+ }
+
+ supportedLBPolicies = &(((PDSM_QueryLBPolicy_V2)Buffer)->LoadBalancePolicy);
+ supportedLBPolicies->Version = DSM_WMI_VERSION;
+ supportedLBPolicies->LoadBalancePolicy = groupEntry->LoadBalanceType;
+ supportedLBPolicies->DSMPathCount = DsmIds->Count;
+ dsmPath = supportedLBPolicies->DSM_Paths;
+
+ //
+ // Indicate which path is active and which path(s) are standby paths
+ //
+ inx = 0;
+ while (inx < DsmIds->Count) {
+
+ devInfo = (PDSM_DEVICE_INFO)DsmIds->IdList[inx];
+
+ dsmPath->PathWeight = devInfo->PathWeight;
+ dsmPath->Reserved = DSM_STATE_ACTIVE_OPTIMIZED_SUPPORTED;
+
+ if (devInfo->ALUASupport == DSM_DEVINFO_ALUA_NOT_SUPPORTED) {
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ dsmPath->TargetPortGroup_State = DSM_DEV_NOT_USED_STATE;
+ }
+
+ dsmPath->Reserved |= DSM_STATE_STANDBY_SUPPORTED;
+
+ } else {
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ dsmPath->TargetPortGroup_State = devInfo->TargetPortGroup->AsymmetricAccessState;
+ dsmPath->TargetPortGroup_Preferred = devInfo->TargetPortGroup->Preferred;
+ dsmPath->TargetPortGroup_Identifier = devInfo->TargetPortGroup->Identifier;
+
+ if (devInfo->TargetPort) {
+
+ dsmPath->TargetPort_Identifier = devInfo->TargetPort->Identifier;
+ }
+
+ if (groupEntry->Symmetric) {
+
+ //
+ // For certain policies like FOO and RRWS, we need to be able to put
+ // path in standby.
+ //
+ dsmPath->Reserved |= DSM_STATE_STANDBY_SUPPORTED;
+ }
+ }
+
+ dsmPath->Reserved |= devInfo->TargetPortGroup->ActiveUnoptimizedSupported ? DSM_STATE_ACTIVE_UNOPTIMIZED_SUPPORTED : 0;
+ dsmPath->Reserved |= devInfo->TargetPortGroup->StandBySupported ? DSM_STATE_STANDBY_SUPPORTED : 0;
+ dsmPath->Reserved |= devInfo->TargetPortGroup->UnavailableSupported ? DSM_STATE_UNAVAILABLE_SUPPORTED : 0;
+ }
+
+ groupEntry = devInfo->Group;
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ dsmPath->SymmetricLUA = groupEntry->Symmetric;
+ dsmPath->ALUASupport = devInfo->ALUASupport;
+
+ }
+
+ if (DsmpIsDeviceFailedState(devInfo->State) || !DsmpIsDeviceInitialized(devInfo)) {
+
+ dsmPath->PrimaryPath = FALSE;
+ dsmPath->DsmPathId = 0;
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ dsmPath->OptimizedPath = dsmPath->PreferredPath = FALSE;
+ dsmPath->FailedPath = TRUE;
+ }
+
+ } else {
+
+ foGroup = devInfo->FailGroup;
+ dsmPath->DsmPathId = (ULONGLONG)((ULONG_PTR)foGroup->PathId);
+
+ if (DsmpIsDeviceStateActive(devInfo->State)) {
+
+ dsmPath->PrimaryPath = TRUE;
+ }
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ if (devInfo->State == DSM_DEV_ACTIVE_OPTIMIZED ||
+ devInfo->State == DSM_DEV_STANDBY) {
+
+ dsmPath->OptimizedPath = TRUE;
+ }
+
+ if (((ULONGLONG)((ULONG_PTR)(foGroup->PathId))) == (devInfo->Group->PreferredPath)) {
+
+ dsmPath->PreferredPath = TRUE;
+ }
+ }
+ }
+
+#if DBG
+ if (!dsmPath->PrimaryPath &&
+ !dsmPath->FailedPath) {
+ NT_ASSERT(groupEntry->LoadBalanceType != DSM_LB_ROUND_ROBIN &&
+ groupEntry->LoadBalanceType != DSM_LB_WEIGHTED_PATHS &&
+ groupEntry->LoadBalanceType != DSM_LB_DYN_LEAST_QUEUE_DEPTH &&
+ groupEntry->LoadBalanceType != DSM_LB_LEAST_BLOCKS);
+ }
+#endif
+
+ dsmPath = DsmWmiVersion == DSM_WMI_VERSION_1 ?
+ (PVOID)((PUCHAR)dsmPath + sizeof(MPIO_DSM_Path)) :
+ (PVOID)((PUCHAR)dsmPath + sizeof(MPIO_DSM_Path_V2));
+
+ inx++;
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+__Exit_DsmpQueryLoadBalancePolicy:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryLoadBalancePolicy (DsmIds %p): Exiting with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQuerySupportedLBPolicies(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG BufferAvail,
+ _In_ IN ULONG DsmWmiVersion,
+ _Out_ OUT PULONG OutBufferSize,
+ _Out_writes_to_(BufferAvail, *OutBufferSize) OUT PUCHAR Buffer
+ )
+/*+++
+
+Routine Description:
+
+ This routine returns the load balance policies supported by this DSM for the
+ given LUN (specified by the DsmIds).
+
+Arguements:
+
+ DsmContext - Global DSM context
+ DsmIds - DSM Ids for the given device
+ BufferAvail - Size of buffer available.
+ DsmWmiVersion - Indicates which version of MPIO_DSMPath to use.
+ OutBufferSize - Size of the output buffer.
+ Buffer - Buffer in which the supported Load Balance policies are
+ returned, if the buffer is big enough.
+
+Return Value:
+
+ STATUS_SUCCESS on success
+ Appropriate error code on error.
+
+--*/
+{
+ PDSM_QuerySupportedLBPolicies_V2 supportedLBPolicies;
+ PDSM_Load_Balance_Policy_V2 dsmLBPolicy;
+ ULONG sizeNeeded;
+ ULONG policyCount;
+ ULONG inx;
+ NTSTATUS status = STATUS_SUCCESS;
+ BOOLEAN skipRR = FALSE;
+ PDSM_DEVICE_INFO devInfo = NULL;
+ PUCHAR endOfBuffer;
+
+ UNREFERENCED_PARAMETER(DsmContext);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,"DsmpQuerySupportedLBPolicies (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // At least one device should be given
+ //
+ if (DsmIds->Count == 0) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQuerySupportedLBPolicies (DsmIds %p): No DSM Ids given in DsmpQuerySupportedLBPolicies.\n",
+ DsmIds));
+
+ *OutBufferSize = 0;
+ status = STATUS_INVALID_PARAMETER;
+
+ goto __Exit_DsmpQuerySupportedLBPolicies;
+ }
+
+ devInfo = DsmIds->IdList[0];
+ DSM_ASSERT(devInfo && devInfo->DeviceSig == DSM_DEVICE_SIG);
+
+ policyCount = DSM_NUMBER_OF_LB_POLICIES;
+
+ //
+ // Round Robin policy is not supported for arrays that are AAA.
+ //
+ if (!DsmpIsSymmetricAccess(devInfo)) {
+
+ skipRR = TRUE;
+ policyCount--;
+ }
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ sizeNeeded = AlignOn8Bytes(FIELD_OFFSET(DSM_QuerySupportedLBPolicies, Supported_LB_Policies));
+ sizeNeeded += policyCount * AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy, DSM_Paths));
+
+ } else {
+
+ sizeNeeded = AlignOn8Bytes(FIELD_OFFSET(DSM_QuerySupportedLBPolicies_V2, Supported_LB_Policies));
+ sizeNeeded += policyCount * AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy_V2, DSM_Paths));
+ }
+
+ //
+ // Set the size of the data returned to user or needed but not provided.
+ //
+ *OutBufferSize = sizeNeeded;
+
+ if (sizeNeeded > BufferAvail) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQuerySupportedLBPolicies (Buffer %p): Output buffer too small. Size needed = %u.\n",
+ Buffer,
+ sizeNeeded));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ goto __Exit_DsmpQuerySupportedLBPolicies;
+ }
+
+ endOfBuffer = Buffer + sizeNeeded - 1;
+
+ //
+ // Zero out the output buffer first
+ //
+ supportedLBPolicies = (PDSM_QuerySupportedLBPolicies_V2)Buffer;
+ RtlZeroMemory(Buffer, sizeNeeded);
+
+ supportedLBPolicies->SupportedLBPoliciesCount = policyCount;
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ dsmLBPolicy = &(supportedLBPolicies->Supported_LB_Policies[0]);
+
+ } else {
+
+ dsmLBPolicy = (PVOID)&(((PDSM_QuerySupportedLBPolicies)supportedLBPolicies)->Supported_LB_Policies[0]);
+ }
+
+ //
+ // All Load Balance policies are supported in Windows Server 2003
+ // and above.
+ //
+ for (inx = 0; inx < DSM_NUMBER_OF_LB_POLICIES; inx++) {
+
+ //
+ // Skip reporting Round Robin for AAA arrays.
+ //
+ if (((inx + 1) == DSM_LB_ROUND_ROBIN) && skipRR) {
+
+ continue;
+ }
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ if ((PUCHAR)dsmLBPolicy + AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy_V2, DSM_Paths)) - 1 > endOfBuffer) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+ } else {
+
+ if ((PUCHAR)dsmLBPolicy + AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy, DSM_Paths)) - 1 > endOfBuffer) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+ }
+
+ dsmLBPolicy->Version = DSM_WMI_VERSION;
+
+ //
+ // The value set for LoadBalancePolicy is based on
+ // the #define for LB policies in LBPolicy.h
+ //
+ dsmLBPolicy->LoadBalancePolicy = inx + 1;
+
+ //
+ // Point to the next DSM_Load_Balance_Policy area
+ //
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ dsmLBPolicy = (PDSM_Load_Balance_Policy_V2)((PUCHAR)dsmLBPolicy + AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy_V2, DSM_Paths)));
+
+ } else {
+
+ dsmLBPolicy = (PVOID)((PUCHAR)dsmLBPolicy + AlignOn8Bytes(FIELD_OFFSET(DSM_Load_Balance_Policy, DSM_Paths)));
+ }
+ }
+
+__Exit_DsmpQuerySupportedLBPolicies:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQuerySupportedLBPolicies (DsmIds %p): Exiting function with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmExecuteMethod(
+ _In_ IN PVOID DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN PIRP Irp,
+ _In_ IN ULONG GuidIndex,
+ _In_ IN ULONG InstanceIndex,
+ _In_ IN ULONG MethodId,
+ _In_ IN ULONG InBufferSize,
+ _In_ IN PULONG OutBufferSize,
+ _Inout_ IN OUT PUCHAR Buffer,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ This routine handles the invocation of WMI methods defined in the DSM mof.
+
+Arguments:
+
+ DsmContext - Global DSM context
+ DsmIds - DSM Ids
+ Irp - The WMI Irp
+ GuidIndex - Index into the WMIGUIDINFO array
+ InstanceIndex - Index value indicating for which instance data should be returned.
+ MethodId - Specifies which method to invoke.
+ InBufferSize - Buffer size, in bytes, of input parameter data.
+ OutBufferSize - Buffer size, in bytes, of output data.
+ Buffer - Buffer to which the data is read/written.
+
+Return Value:
+
+ Status of the method, or STATUS_WMI_ITEMID_NOT_FOUND
+
+--*/
+{
+ NTSTATUS status = STATUS_WMI_GUID_NOT_FOUND;
+ UNREFERENCED_PARAMETER(DsmContext);
+ UNREFERENCED_PARAMETER(InstanceIndex);
+ UNREFERENCED_PARAMETER(Irp);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmExecuteMethod (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // This should be the index for ExecMethod Index
+ //
+ if (GuidIndex == DSM_LBOperationsGUID_Index) {
+
+ switch (MethodId) {
+
+ case DsmSetLoadBalancePolicy:
+ case DsmSetLoadBalancePolicyALUA: {
+
+ status = DsmpSetLoadBalancePolicy(DsmContext,
+ DsmIds,
+ (MethodId == DsmSetLoadBalancePolicy) ? DSM_WMI_VERSION_1 : DSM_WMI_VERSION_2,
+ InBufferSize,
+ OutBufferSize,
+ Buffer);
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmExecuteMethod (DsmIds %p): Unknown MethodId %d in DsmExecuteMethod.\n",
+ DsmIds,
+ MethodId));
+
+ status = STATUS_WMI_ITEMID_NOT_FOUND;
+
+ break;
+ }
+ }
+ } else if (GuidIndex == MSDSM_WMI_METHODSGuidIndex) {
+
+ if (MethodId == MSDsmClearCounters) {
+
+ status = DsmpClearPerfCounters(DsmContext, DsmIds);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmExecuteMethod (DsmIds %p): Unknown MethodId %d for GuidIndex %d in DsmExecuteMethod.\n",
+ DsmIds,
+ MethodId,
+ GuidIndex));
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmExecuteMethod (DsmIds %p): Unknown GuidIndex %d in DsmExecuteMethod.\n",
+ DsmIds,
+ GuidIndex));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmExecuteMethod (DsmIds %p): Exiting function with status 0x%x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+NTSTATUS
+DsmpClearLoadBalancePolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds
+ )
+/*++
+
+Routine Description:
+
+ This routine is called to clear the LUN-specific load balance policy for the given device.
+
+ First, the routine will try to clear the "explicitly set" registry key for the device. If
+ this fails, the whole routine is aborted.
+
+ If the registry key is successfully cleared, the following happens:
+ 1. Check to see if there is a target-wide load balance policy set for this device's VID/PID.
+ If yes, we set the device's load balance policy accordingly and return.
+ 2. Check to see if there is an MSDSM-wide load balance policy set.
+ If yes, we set the device's load balance policy accordingly and return.
+ 3. If steps 1 and 2 fall through, we set the device's load balance policy to RR, or RRWS if
+ ALUA is enabled.
+
+Arguements:
+
+ DsmContext - Global DSM context
+ DsmIds - DSM Ids for the given device
+
+Return Value:
+
+ Appropriate status indicating the error if the input is malformed or
+ if the function was unable to clear the load balance policy.
+ STATUS_SUCCESS on success
+
+--*/
+
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSM_DEVICE_INFO deviceInfo = NULL;
+ PDSM_GROUP_ENTRY group = NULL;
+ HANDLE lbSettingsKey = NULL;
+ HANDLE deviceKey = NULL;
+ UNICODE_STRING subKeyName;
+ OBJECT_ATTRIBUTES objectAttributes;
+ DSM_LOAD_BALANCE_TYPE loadBalanceType;
+ ULONGLONG preferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+ ULONG devInfoIndex;
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpClearLoadBalancePolicy (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // There should be at least one device
+ //
+ if (DsmIds->Count == 0) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_WMI,
+ "DsmpClearLoadBalancePolicy (DsmIds %p): No DSM Ids given.\n",
+ DsmIds));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpClearLoadBalancePolicy;
+ }
+
+ deviceInfo = (PDSM_DEVICE_INFO)DsmIds->IdList[0];
+ group = deviceInfo->Group;
+
+ //
+ // First open LoadBalanceSettings key under the Services key
+ //
+ status = DsmpOpenLoadBalanceSettingsKey(KEY_ALL_ACCESS, &lbSettingsKey);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpClearLoadBalancePolicy (DevName %ws): Failed to open LB Settings key. Status %x.\n",
+ group->RegistryKeyName,
+ status));
+
+ goto __Exit_DsmpClearLoadBalancePolicy;
+ }
+
+ //
+ // Now open the key under which the LB settings for the given device is stored
+ // and clear the DsmLoadBalancePolicyExplicitlySet key.
+ //
+ RtlInitUnicodeString(&subKeyName, group->RegistryKeyName);
+
+ InitializeObjectAttributes(&objectAttributes,
+ &subKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ lbSettingsKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(&deviceKey, KEY_ALL_ACCESS, &objectAttributes);
+
+ if (NT_SUCCESS(status)) {
+
+ UCHAR explicitlySet = FALSE;
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,
+ deviceKey,
+ DSM_POLICY_EXPLICITLY_SET,
+ REG_BINARY,
+ &explicitlySet,
+ sizeof(UCHAR));
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpClearLoadBalancePolicy (DevName %ws): Failed to clear DsmLoadBalancePolicyExplicitlySet key.\n",
+ group->RegistryKeyName));
+
+ goto __Exit_DsmpClearLoadBalancePolicy;
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpClearLoadBalancePolicy (DevName %ws): Failed to open device subkey.\n",
+ group->RegistryKeyName));
+
+ goto __Exit_DsmpClearLoadBalancePolicy;
+ }
+
+
+
+ //
+ // Set the defaults. These will be used if no target-wide or MSDSM-wide
+ // load balance policies are set.
+ //
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_ALUA_CAPABILITY;
+ loadBalanceType = DSM_LB_ROUND_ROBIN;
+ preferredPath = 0;
+
+ //
+ // Check to see if target-wide (VID/PID) LB policy is set for this device.
+ //
+ status = DsmpQueryTargetLBPolicyFromRegistry(deviceInfo,
+ &loadBalanceType,
+ &preferredPath);
+ if (NT_SUCCESS(status)) {
+
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_VID_PID;
+
+ } else if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
+
+ //
+ // Since the policy hasn't been set for this VID/PID, check if
+ // overall MSDSM-wide policy has been set.
+ //
+ status = DsmpQueryDsmLBPolicyFromRegistry(&loadBalanceType,
+ &preferredPath);
+ if (NT_SUCCESS(status)) {
+
+ group->LBPolicySelection = DSM_DEFAULT_LB_POLICY_DSM_WIDE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpClearLoadBalancePolicy (DevInfo %p): Failed to query Dsm overall LB policy from registry. Status %x.\n",
+ deviceInfo,
+ status));
+
+ NT_ASSERT(status == STATUS_OBJECT_NAME_NOT_FOUND);
+ status = STATUS_SUCCESS;
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "DsmpClearLoadBalancePolicy (DevInfo %p): Failed to query VID/PID LB policy from registry. Status %x.\n",
+ deviceInfo,
+ status));
+
+ NT_ASSERT(status == STATUS_OBJECT_NAME_NOT_FOUND);
+ status = STATUS_SUCCESS;
+ }
+
+
+ //
+ // If the storage is ALUA enabled and we specified Round Robin, change
+ // it to Round Robin with Subset instead.
+ //
+ if (!DsmpIsSymmetricAccess(deviceInfo) && loadBalanceType == DSM_LB_ROUND_ROBIN) {
+
+ loadBalanceType = DSM_LB_ROUND_ROBIN_WITH_SUBSET;
+ }
+
+ //
+ // Finally set the load balance policy and the preferred path.
+ //
+ group->LoadBalanceType = loadBalanceType;
+ group->PreferredPath = preferredPath;
+
+ //
+ // Update the path states in accordance with the new policy.
+ //
+ for (devInfoIndex = 0; devInfoIndex < DSM_MAX_PATHS; devInfoIndex++) {
+
+ DsmpSetNewDefaultLBPolicy(DsmContext,
+ group->DeviceList[devInfoIndex],
+ group->LoadBalanceType,
+ SpecialHandlingFlag);
+ }
+
+__Exit_DsmpClearLoadBalancePolicy:
+
+ if (deviceKey) {
+ ZwClose(deviceKey);
+ }
+
+ if (lbSettingsKey) {
+ ZwClose(lbSettingsKey);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpClearLoadBalancePolicy (DsmIds %p): Exiting function with status 0x%x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpSetLoadBalancePolicy(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN ULONG InBufferSize,
+ _In_ IN PULONG OutBufferSize,
+ _In_ IN PVOID Buffer
+ )
+/*++
+
+Routine Description:
+
+ This routine is called to set the load balance policy for the given device.
+
+ If zero is passed in as the load balance policy, the LUN-specific load balance
+ policy will attempt to be cleared. See DsmpClearLoadBalancePolicy for more details.
+
+Arguements:
+
+ DsmContext - Global DSM context
+ DsmIds - DSM Ids for the given device
+ DsmWmiVersion - version of the MPIO_DSM_Path class to use
+ InBufferSize - Size of the input buffer
+ OutBufferSize - Size of the output buffer
+ Buffer - Buffer for input\output data
+
+Return Value:
+
+ STATUS_BUFFER_TOO_SMALL - If the input buffer is too small
+ Appropriate status indicating the error if the input is malformed.
+ STATUS_SUCCESS on success
+
+--*/
+
+{
+ PDsmSetLoadBalancePolicyALUA_IN setLoadBalancePolicyIN = (PDsmSetLoadBalancePolicyALUA_IN) Buffer;
+ PDsmSetLoadBalancePolicyALUA_OUT setLoadBalancePolicyOUT = (PDsmSetLoadBalancePolicyALUA_OUT) Buffer;
+ PVOID supportedLBPolicies;
+ PMPIO_DSM_Path_V2 dsmPath;
+ ULONG inx = 0;
+ ULONG jnx;
+ NTSTATUS status = STATUS_SUCCESS;
+ BOOLEAN lengthOkay = TRUE;
+ PDSM_DEVICE_INFO devInfo = NULL;
+ PDSM_DEVICE_INFO tempDevInfo = NULL;
+ PDSM_GROUP_ENTRY groupEntry;
+ PDSM_LOAD_BALANCE_POLICY_SETTINGS savedLBSettings = NULL;
+ KIRQL irql;
+ BOOLEAN optimized = TRUE;
+ BOOLEAN preferred = FALSE;
+ ULONG activePaths = 0;
+ ULONG activeTPGs = 0;
+ ULONG numberDevInfoChanged = 0;
+ ULONG numberPreferredPaths = 0;
+ DSM_LOAD_BALANCE_TYPE loadBalancePolicy;
+ BOOLEAN sendSTPG = FALSE;
+ ULONGLONG preferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+ ULONG SpecialHandlingFlag = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // There should be at least one device
+ //
+ if (DsmIds->Count == 0) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): No DSM Ids given.\n",
+ DsmIds));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpSetLoadBalancePolicy;
+ }
+
+ groupEntry = ((PDSM_DEVICE_INFO)DsmIds->IdList[0])->Group;
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ if (*OutBufferSize < sizeof(DsmSetLoadBalancePolicy_OUT)) {
+
+ *OutBufferSize = sizeof(DsmSetLoadBalancePolicy_OUT);
+ lengthOkay = FALSE;
+ }
+ } else {
+
+ if (*OutBufferSize < sizeof(DsmSetLoadBalancePolicyALUA_OUT)) {
+
+ *OutBufferSize = sizeof(DsmSetLoadBalancePolicyALUA_OUT);
+ lengthOkay = FALSE;
+ }
+ }
+
+ if (!lengthOkay) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Buffer too small for SetLBPolicy.\n",
+ DsmIds));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto __Exit_DsmpSetLoadBalancePolicy;
+ }
+
+ *OutBufferSize = (DsmWmiVersion == DSM_WMI_VERSION_1) ? sizeof(DsmSetLoadBalancePolicy_OUT) : sizeof(DsmSetLoadBalancePolicyALUA_OUT);
+
+ //
+ // If the user specified zero as the load balance policy, we need to clear the
+ // LUN-specific load balance policy.
+ //
+ if (setLoadBalancePolicyIN->LoadBalancePolicy.LoadBalancePolicy == 0) {
+ status = DsmpClearLoadBalancePolicy(DsmContext, DsmIds);
+ goto __Exit_DsmpSetLoadBalancePolicy;
+ }
+
+ status = DsmpValidateSetLBPolicyInput(DsmContext,
+ DsmIds,
+ DsmWmiVersion,
+ Buffer,
+ InBufferSize);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Failed to validate input. Status %x.\n",
+ DsmIds,
+ status));
+
+ goto __Exit_DsmpSetLoadBalancePolicy;
+ }
+
+ //
+ // At this point the Reserved field in each MPIO_DSM_Path should
+ // contain the respective Device Info
+ //
+ supportedLBPolicies = &(setLoadBalancePolicyIN->LoadBalancePolicy);
+ loadBalancePolicy = ((PDSM_Load_Balance_Policy_V2)supportedLBPolicies)->LoadBalancePolicy;
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // Cache each DeviceInfo's current state.
+ // This will be used to rollback in case of errors.
+ //
+ DsmpSaveDeviceState(supportedLBPolicies, DsmWmiVersion);
+
+ while (inx < ((PDSM_Load_Balance_Policy_V2)supportedLBPolicies)->DSMPathCount) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath = (PVOID)&(((PDSM_Load_Balance_Policy)supportedLBPolicies)->DSM_Paths[inx]);
+
+ optimized = TRUE;
+ preferred = FALSE;
+
+ } else {
+
+ dsmPath = &(((PDSM_Load_Balance_Policy_V2)supportedLBPolicies)->DSM_Paths[inx]);
+
+ optimized = dsmPath->OptimizedPath ? TRUE : FALSE;
+ preferred = dsmPath->PreferredPath ? TRUE : FALSE;
+
+ if (preferred && loadBalancePolicy == DSM_LB_FAILOVER) {
+
+ preferredPath = dsmPath->DsmPathId;
+
+ if (preferredPath != 0) {
+
+ numberPreferredPaths++;
+ }
+
+ if (numberPreferredPaths > 1) {
+
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+
+ //
+ // Reserved field in MPIO_DSM_Path is set to DeviceInfo in
+ // DsmpValidateSetLBPolicyInput routine.
+ //
+ devInfo = (PDSM_DEVICE_INFO)dsmPath->Reserved;
+
+ if (!devInfo) {
+
+ inx++;
+ continue;
+
+ } else {
+
+ if (!tempDevInfo) {
+
+ tempDevInfo = devInfo;
+
+ if (loadBalancePolicy == DSM_LB_ROUND_ROBIN ||
+ loadBalancePolicy == DSM_LB_ROUND_ROBIN_WITH_SUBSET) {
+
+ InterlockedExchangePointer(&(groupEntry->PathToBeUsed), NULL);
+ }
+ }
+ }
+
+ if (!DsmpIsDeviceFailedState(devInfo->State)) {
+
+ if (devInfo->ALUAState == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ activeTPGs++;
+ }
+
+ if (dsmPath->PrimaryPath) {
+
+ //
+ // Optimized flag decides between AO and AU
+ //
+ if (optimized) {
+
+ //
+ // For implicit-only ALUA, state cannot be explicitly changed to A/O
+ //
+ if (!DsmpIsSymmetricAccess(devInfo) && devInfo->ALUASupport == DSM_DEVINFO_ALUA_IMPLICIT) {
+
+ //
+ // While we can mask off acutal A/O to be A/U, there is no
+ // way to explicitly make non-A/O state A/O
+ //
+ if (devInfo->ALUAState != DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Can't make non-AO path A/O for Implicit-only transitions.\n",
+ DsmIds));
+
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ numberDevInfoChanged++;
+
+ devInfo->State = DSM_DEV_ACTIVE_OPTIMIZED;
+ activePaths++;
+
+ //
+ // Check to see if the actual making of this path state A/O
+ // will require an STPG to be sent down.
+ //
+ if (devInfo->TargetPortGroup &&
+ devInfo->ALUAState != DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ sendSTPG = TRUE;
+ }
+
+ if (loadBalancePolicy == DSM_LB_FAILOVER) {
+
+ //
+ // Only ONE path can be specified as AO for FailOverOnly policy.
+ //
+ if (activePaths > 1) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): More than one AO node given for FO Only.\n",
+ DsmIds));
+
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ if (loadBalancePolicy == DSM_LB_ROUND_ROBIN ||
+ loadBalancePolicy == DSM_LB_ROUND_ROBIN_WITH_SUBSET) {
+
+ if (!groupEntry->PathToBeUsed) {
+ InterlockedExchangePointer(&(groupEntry->PathToBeUsed), (PVOID)devInfo->FailGroup);
+ }
+ }
+ } else {
+
+ //
+ // This is an ActiveUnoptimized path
+ //
+ devInfo->State = DSM_DEV_ACTIVE_UNOPTIMIZED;
+
+ //
+ // For LB policy RR, WP, LB and LQD, all paths must be in A/O
+ // state. However, this is not possible for ALUA storages.
+ // For these storages, A/U is allowable only if that is the
+ // access state that the TPG is in.
+ //
+ if (loadBalancePolicy == DSM_LB_ROUND_ROBIN ||
+ loadBalancePolicy == DSM_LB_WEIGHTED_PATHS ||
+ loadBalancePolicy == DSM_LB_DYN_LEAST_QUEUE_DEPTH ||
+ loadBalancePolicy == DSM_LB_LEAST_BLOCKS) {
+
+ if (devInfo->TargetPortGroup && devInfo->ALUAState != DSM_DEV_ACTIVE_UNOPTIMIZED) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Path (%u) specified in A/U state when TPG is in %u state (for LB %u).\n",
+ DsmIds,
+ inx,
+ devInfo->ALUAState,
+ loadBalancePolicy));
+
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+ } else {
+
+ if (optimized) {
+
+ //
+ // This is a standby path
+ //
+ devInfo->State = DSM_DEV_STANDBY;
+
+ } else {
+
+ //
+ // This is unavailable path
+ //
+ devInfo->State = DSM_DEV_UNAVAILABLE;
+ }
+
+ //
+ // For RR, LQD, LB and WP, all paths must be in A/O state for non-ALUA
+ // storage. For ALUA storage, the only time path states can be in
+ // S/B or U/A is if the TPG itself is in that state.
+ //
+ if (loadBalancePolicy == DSM_LB_ROUND_ROBIN ||
+ loadBalancePolicy == DSM_LB_WEIGHTED_PATHS ||
+ loadBalancePolicy == DSM_LB_DYN_LEAST_QUEUE_DEPTH ||
+ loadBalancePolicy == DSM_LB_LEAST_BLOCKS) {
+
+ if ((!devInfo->TargetPortGroup) ||
+ (devInfo->TargetPortGroup && devInfo->State != devInfo->ALUAState)) {
+
+ //
+ // No paths can be in SB or UA unless its TPG is in that state.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Path (%u) specified in non-active state for LB %u.\n",
+ DsmIds,
+ inx,
+ loadBalancePolicy));
+
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ } else if (loadBalancePolicy == DSM_LB_ROUND_ROBIN_WITH_SUBSET) {
+
+ //
+ // It is okay to set a path to be in S/B or U/A state in RRWS
+ // if either the storage is non-ALUA, or if the storage is
+ // ALUA but the TPG is in A/O (where it can be masked) or the
+ // TPG is in the state that the path is being set to.
+ //
+ if ((devInfo->TargetPortGroup) &&
+ (devInfo->ALUAState != DSM_DEV_ACTIVE_OPTIMIZED && devInfo->State != devInfo->ALUAState)) {
+
+ //
+ // No paths can be in SB or UA unless its TPG is in that state.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Path (%u) (in TPG state %u) can't be specified in non-active state for LB %u.\n",
+ DsmIds,
+ inx,
+ devInfo->ALUAState,
+ loadBalancePolicy));
+
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+ }
+
+ inx++;
+ }
+
+ if (NT_SUCCESS(status)) {
+
+
+ //
+ // If we arrive here, that means DsmpValidateSetLBPolicyInput already returned success.
+ // The device info is found.
+ //
+ _Analysis_assume_(tempDevInfo != NULL);
+
+ //
+ // There must be at least one AO path. Unless there are no A/O TPGs.
+ // eg. During a controller failover, it is possible that the TPG through
+ // the TPG through other controller is still in non-A/O state and the
+ // storage supports implicit transitions and is still in the midst of
+ // making the transition of the non-A/O TPG to A/O. During such windows
+ // the states for all paths will be non-A/O and there's nothing that can
+ // be done about it. This is not an error condition.
+ //
+ if (!activePaths) {
+
+ if ((tempDevInfo->ALUASupport == DSM_DEVINFO_ALUA_NOT_SUPPORTED) ||
+ (tempDevInfo->ALUASupport != DSM_DEVINFO_ALUA_NOT_SUPPORTED && activeTPGs)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): No active node given for LB %u.\n",
+ DsmIds,
+ loadBalancePolicy));
+
+ //
+ // Roll back to DeviceState to the state it was before
+ // processing this SetLB policy request
+ //
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+
+ status = STATUS_INVALID_PARAMETER;
+ }
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // If we arrive here, that means DsmpValidateSetLBPolicyInput already returned success.
+ // The device info is found.
+ //
+ _Analysis_assume_(tempDevInfo != NULL);
+
+ //
+ // If device supports explicit transitions, we need to send down an
+ // STPG to enforce A/O path selection if we need to make a path in a
+ // non-A/O TPG active/optimized.
+ //
+ if (tempDevInfo->ALUASupport >= DSM_DEVINFO_ALUA_EXPLICIT && sendSTPG) {
+
+ PUCHAR targetPortGroupsInfo = NULL;
+ ULONG targetPortGroupsInfoLength = 0;
+ PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR tpgDescriptor = NULL;
+
+ //
+ // Build the target port groups info to set the new states.
+ // Send down an STPG for TPG descriptors for those devInfos' TPGs
+ // that need to be in AO state. If this causes side-effects in
+ // state transitions (these can't be considered implicit according
+ // to the spec), fake the devInfo states to what was selected.
+ //
+ targetPortGroupsInfoLength = SPC3_TARGET_PORT_GROUPS_HEADER_SIZE +
+ activePaths * sizeof(SPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR);
+
+ targetPortGroupsInfo = DsmpAllocatePool(NonPagedPoolNx,
+ targetPortGroupsInfoLength,
+ DSM_TAG_TARGET_PORT_GROUPS);
+
+ if (targetPortGroupsInfo) {
+
+ PDSM_DEVICE_INFO devInfoToUse = NULL;
+
+ //
+ // Set the new asymmetric access states for the the devices' target port groups
+ //
+ tpgDescriptor = (PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR)(targetPortGroupsInfo + SPC3_TARGET_PORT_GROUPS_HEADER_SIZE);
+
+ for (inx = 0, jnx = 0;
+ inx < ((PDSM_Load_Balance_Policy_V2)supportedLBPolicies)->DSMPathCount;
+ inx++) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath = (PVOID)&(((PDSM_Load_Balance_Policy)supportedLBPolicies)->DSM_Paths[inx]);
+
+ } else {
+
+ dsmPath = &(((PDSM_Load_Balance_Policy_V2)supportedLBPolicies)->DSM_Paths[inx]);
+ }
+
+ devInfo = (PDSM_DEVICE_INFO)dsmPath->Reserved;
+
+ if (!devInfo) {
+
+ continue;
+ }
+
+ if (devInfo->State == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ tpgDescriptor->AsymmetricAccessState = devInfo->State;
+ REVERSE_BYTES_SHORT(&tpgDescriptor->TPG_Identifier, &devInfo->TargetPortGroup->Identifier);
+
+ tpgDescriptor = (PSPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR)((PUCHAR)tpgDescriptor + sizeof(SPC3_SET_TARGET_PORT_GROUP_DESCRIPTOR));
+
+ jnx++;
+ }
+
+ if (devInfo->TempPreviousStateForLB == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ devInfoToUse = devInfo;
+ }
+ }
+
+ NT_ASSERT(jnx == numberDevInfoChanged);
+ NT_ASSERT(devInfoToUse);
+
+ if (devInfoToUse) {
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ status = DsmpSetTargetPortGroups(devInfoToUse->TargetObject,
+ targetPortGroupsInfo,
+ targetPortGroupsInfoLength);
+
+ if (NT_SUCCESS(status)) {
+
+ DsmpFreePool(targetPortGroupsInfo);
+ targetPortGroupsInfo = NULL;
+ targetPortGroupsInfoLength = 0;
+ status = DsmpReportTargetPortGroups(devInfoToUse->TargetObject,
+ &targetPortGroupsInfo,
+ &targetPortGroupsInfoLength);
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): STPG failed with status %x.\n",
+ DsmIds,
+ status));
+
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ }
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ ULONG index;
+ PDSM_TARGET_PORT_GROUP_ENTRY targetPortGroup;
+
+ status = DsmpParseTargetPortGroupsInformation(DsmContext,
+ groupEntry,
+ targetPortGroupsInfo,
+ targetPortGroupsInfoLength);
+
+ NT_ASSERT(NT_SUCCESS(status));
+
+ for (index = 0; index < DSM_MAX_PATHS; index++) {
+
+ targetPortGroup = groupEntry->TargetPortGroupList[index];
+
+ if (targetPortGroup) {
+
+ DsmpUpdateTargetPortGroupDevicesStates(targetPortGroup, targetPortGroup->AsymmetricAccessState);
+ }
+ }
+
+ //
+ // Update TPGs with new state
+ //
+ for (inx = 0;
+ inx < ((PDSM_Load_Balance_Policy_V2)supportedLBPolicies)->DSMPathCount;
+ inx++) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath = (PVOID)&(((PDSM_Load_Balance_Policy)supportedLBPolicies)->DSM_Paths[inx]);
+
+ } else {
+
+ dsmPath = &(((PDSM_Load_Balance_Policy_V2)supportedLBPolicies)->DSM_Paths[inx]);
+ }
+
+ devInfo = (PDSM_DEVICE_INFO)dsmPath->Reserved;
+
+ if (devInfo) {
+
+ //
+ // An explicit state transition can cause TPGs that were not specified
+ // in the parameter list to also change (this is not considered to be
+ // an implicit transition. It is SPC3 behavior and we must take
+ // this into consideration and update the devInfo states.
+ // This is an unfortunate side-effect in that the Admin may not get
+ // the paths to be in the exact states that he has set.
+ //
+ if (devInfo->State == DSM_DEV_ACTIVE_OPTIMIZED) {
+
+ if (devInfo->ALUAState == DSM_DEV_ACTIVE_UNOPTIMIZED ||
+ devInfo->ALUAState == DSM_DEV_STANDBY ||
+ devInfo->ALUAState == DSM_DEV_UNAVAILABLE) {
+
+ //
+ // An A/O TPG's devInfos can be masked as A/U.
+ // However, the reverse the is not true (ie. we can't
+ // mark a non-A/O TPG's devInfo(s) to be in A/O state.
+ //
+ devInfo->State = devInfo->ALUAState;
+ }
+ }
+
+ //
+ // The devInfo->State has already been set. Update its previous state.
+ //
+ devInfo->PreviousState = devInfo->TempPreviousStateForLB;
+ }
+ }
+
+ NT_ASSERT(jnx == numberDevInfoChanged);
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Failed to allocate targetPortGroupsInfo.\n",
+ DsmIds));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ groupEntry->LoadBalanceType = loadBalancePolicy;
+
+ if (loadBalancePolicy == DSM_LB_FAILOVER) {
+
+ groupEntry->PreferredPath = preferredPath;
+ }
+
+ savedLBSettings = DsmpCopyLoadBalancePolicies(groupEntry,
+ DsmWmiVersion,
+ supportedLBPolicies);
+
+ } else {
+
+ //
+ // Roll back to DeviceState to the state it was before
+ // processing this SetLB policy request
+ //
+ DsmpRestorePreviousDeviceState(supportedLBPolicies, DsmWmiVersion);
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // LUN's LB policy has been explicitly set by Admin
+ //
+ groupEntry->LBPolicySelection = DSM_DEFAULT_LB_POLICY_LUN_EXPLICIT;
+
+ //
+ // Update the states and if appropriate, the path weight
+ //
+ DsmpUpdateDesiredStateAndWeight(groupEntry,
+ DsmWmiVersion,
+ supportedLBPolicies);
+
+ //
+ // Update the next path to be used for the group
+ //
+ devInfo = DsmpGetActivePathToBeUsed(groupEntry,
+ DsmpIsSymmetricAccess(tempDevInfo),
+ SpecialHandlingFlag);
+ if (devInfo != NULL) {
+
+ InterlockedExchangePointer(&(groupEntry->PathToBeUsed), (PVOID)devInfo->FailGroup);
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): After setting LB policy No FOG available for group %p\n",
+ DsmIds,
+ groupEntry));
+
+ InterlockedExchangePointer(&(groupEntry->PathToBeUsed), NULL);
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ if (NT_SUCCESS(status) && savedLBSettings) {
+
+ DsmpPersistLBSettings(savedLBSettings);
+
+ DsmpFreePool(savedLBSettings);
+ }
+
+__Exit_DsmpSetLoadBalancePolicy:
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ ((PDsmSetLoadBalancePolicy_OUT)setLoadBalancePolicyOUT)->Status = status;
+
+ } else {
+
+ setLoadBalancePolicyOUT->Status = status;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSetLoadBalancePolicy (DsmIds %p): Exiting function with status 0x%x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpValidateSetLBPolicyInput(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN PVOID SetLoadBalancePolicyIN,
+ _In_ IN ULONG InBufferSize
+ )
+/*++
+
+Routine Description:
+
+ This routine validates the input buffer given for setting
+ Load Balance policy
+
+Arguements:
+
+ DsmContext - DSM Global Context
+ DsmIds - DSM Ids for the given device
+ DsmWmiVersion - version of the MPIO_DSM_Path class to use
+ SetLoadBalancePolicyIN - Describes the load balance policy to be set
+ InBufferSize - Number of bytes in SetLoadBalancePolicyIN
+
+Return Value:
+
+ STATUS_SUCCESS - if the input buffer is well formed
+ Appropriate error status if the input buffer is malformed.
+
+--*/
+{
+ PDSM_Load_Balance_Policy_V2 supportedLBPolicies;
+ PMPIO_DSM_Path_V2 dsmPath0;
+ PMPIO_DSM_Path_V2 dsmPath1;
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG inx;
+ ULONG jnx;
+ ULONG sizeNeeded;
+ KIRQL irql;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // Validate the input buffer for setting Load Balance policy
+ //
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ sizeNeeded = FIELD_OFFSET(DSM_Load_Balance_Policy_V2, DSM_Paths);
+
+ } else {
+
+ sizeNeeded = FIELD_OFFSET(DSM_Load_Balance_Policy, DSM_Paths);
+ }
+
+ if (InBufferSize < sizeNeeded) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Insufficient buffer in SetLB. Expected %d, Given %d.\n",
+ DsmIds,
+ sizeNeeded,
+ InBufferSize));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto __Exit_DsmpValidateSetLBPolicyInput;
+ }
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ supportedLBPolicies = (PVOID)&(((PDsmSetLoadBalancePolicy_IN)SetLoadBalancePolicyIN)->LoadBalancePolicy);
+
+ sizeNeeded += supportedLBPolicies->DSMPathCount * sizeof(MPIO_DSM_Path);
+
+ } else {
+
+ supportedLBPolicies = &(((PDsmSetLoadBalancePolicyALUA_IN)SetLoadBalancePolicyIN)->LoadBalancePolicy);
+
+ sizeNeeded += supportedLBPolicies->DSMPathCount * sizeof(MPIO_DSM_Path_V2);
+ }
+
+ if (InBufferSize < sizeNeeded) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Insufficient buffer in SetLB. Expected %d, Given %d.\n",
+ DsmIds,
+ sizeNeeded,
+ InBufferSize));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto __Exit_DsmpValidateSetLBPolicyInput;
+ }
+
+ if (supportedLBPolicies->Version > DSM_WMI_VERSION) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): WMI Version mismatch. Expected %d, Given %d.\n",
+ DsmIds,
+ DSM_WMI_VERSION,
+ supportedLBPolicies->Version));
+
+ status = DSM_UNSUPPORTED_VERSION;
+ goto __Exit_DsmpValidateSetLBPolicyInput;
+
+ } else if (supportedLBPolicies->Version < DSM_WMI_VERSION) {
+
+ ULONG dsmWmiVersion = DSM_WMI_VERSION;
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Use of older management app (WMI-Version %x) with newer DSM (WMI-Version %x).\n",
+ DsmIds,
+ supportedLBPolicies->Version,
+ dsmWmiVersion));
+
+ NT_ASSERT(supportedLBPolicies->Version == DSM_WMI_VERSION);
+ }
+
+ if ((supportedLBPolicies->LoadBalancePolicy < DSM_LB_FAILOVER) ||
+ (supportedLBPolicies->LoadBalancePolicy > DSM_LB_LEAST_BLOCKS)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Invalid LB Policy %d.\n",
+ DsmIds,
+ supportedLBPolicies->LoadBalancePolicy));
+
+ status = DSM_INVALID_LOAD_BALANCE_POLICY;
+ goto __Exit_DsmpValidateSetLBPolicyInput;
+ }
+
+ //
+ // It is expected that the user provide LB policy settings
+ // for all the paths and not just a subset of the paths.
+ //
+ if (supportedLBPolicies->DSMPathCount != DsmIds->Count) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Path Count %d not equal to DSM IDs count %d.\n",
+ DsmIds,
+ supportedLBPolicies->DSMPathCount,
+ DsmIds->Count));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __Exit_DsmpValidateSetLBPolicyInput;
+ }
+
+ //
+ // Make sure user did not provide duplicate path ids
+ //
+ for (inx = 0; inx < supportedLBPolicies->DSMPathCount && NT_SUCCESS(status); inx++) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath0 = (PVOID)&(((PDSM_Load_Balance_Policy)supportedLBPolicies)->DSM_Paths[inx]);
+
+ } else {
+
+ dsmPath0 = &(supportedLBPolicies->DSM_Paths[inx]);
+ }
+
+ dsmPath0->Reserved = 0;
+
+ for (jnx = 0; jnx < supportedLBPolicies->DSMPathCount; jnx++) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath1 = (PVOID)&(((PDSM_Load_Balance_Policy)supportedLBPolicies)->DSM_Paths[jnx]);
+
+ } else {
+
+ dsmPath1 = &(supportedLBPolicies->DSM_Paths[jnx]);
+ }
+
+ if ((inx != jnx) &&
+ ((dsmPath0->DsmPathId == dsmPath1->DsmPathId) && (dsmPath1->DsmPathId != 0))) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Duplicate path id %I64x at %d and %d.\n",
+ DsmIds,
+ dsmPath0->DsmPathId,
+ inx,
+ jnx));
+
+ status = STATUS_INVALID_PARAMETER;
+
+ break;
+ }
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ PDSM_DEVICE_INFO devInfo;
+ PDSM_FAILOVER_GROUP foGroup;
+ PVOID pathId;
+ BOOLEAN foundPath;
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ //
+ // Make sure the user has provided path id corresponding
+ // to all the DSM IDs given to us.
+ //
+ for (inx = 0; inx < DsmIds->Count; inx++) {
+
+ devInfo = DsmIds->IdList[inx];
+
+ if (!DsmpIsDeviceInitialized(devInfo)) {
+
+ continue;
+ }
+
+ foGroup = devInfo->FailGroup;
+ if (!foGroup) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): FO Group NULL for %p at index %d.\n",
+ DsmIds,
+ devInfo,
+ inx));
+
+ status = STATUS_INVALID_PARAMETER;
+
+ break;
+ }
+
+ foundPath = FALSE;
+
+ for (jnx = 0; jnx < supportedLBPolicies->DSMPathCount; jnx++) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath0 = (PVOID)&(((PDSM_Load_Balance_Policy)supportedLBPolicies)->DSM_Paths[jnx]);
+
+ } else {
+
+ dsmPath0 = &(supportedLBPolicies->DSM_Paths[jnx]);
+ }
+
+ pathId = (PVOID) dsmPath0->DsmPathId;
+ if (foGroup->PathId == pathId) {
+
+ //
+ // Found the device info corresponding to the given path.
+ // Use the reserved field in MPIO_DSM_Path to store
+ // the pointer to the device info. Device Info is used
+ // later on to set the load balance policy for the device.
+ //
+ foundPath = TRUE;
+
+ dsmPath0->Reserved = (ULONG_PTR) devInfo;
+
+ //
+ // If ALUA, RoundRobin is not an allowed LB policy since not all paths can
+ // be in A/O state. RRWS must be used instead.
+ //
+ if (supportedLBPolicies->LoadBalancePolicy == DSM_LB_ROUND_ROBIN && !DsmpIsSymmetricAccess(devInfo)) {
+
+ status = DSM_INVALID_LOAD_BALANCE_POLICY;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Invalid LB policy for ALUA. Status %x.\n",
+ DsmIds,
+ status));
+ }
+
+ break;
+ }
+ }
+
+ if (!foundPath) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Failed to find path %p for %p at index %d.\n",
+ DsmIds,
+ foGroup->PathId,
+ devInfo,
+ inx));
+
+ status = STATUS_INVALID_PARAMETER;
+
+ break;
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+ }
+
+__Exit_DsmpValidateSetLBPolicyInput:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpValidateSetLBPolicyInput (DsmIds %p): Exiting function with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+VOID
+DsmpSaveDeviceState(
+ _In_ IN PVOID SupportedLBPolicies,
+ _In_ IN ULONG DsmWmiVersion
+ )
+/*+++
+
+Routine Description:
+
+ This routine saves the current Load Balance policy settings.
+ If there is any error while setting the new policy given
+ by the user, the saved values will be used to restore
+ the old state.
+
+ Note: This routine MUST be called with DsmContextLock held in Exclusive mode.
+
+Arguements:
+
+ SupportedLBPolicies - New Load Balance policy values
+ DsmWmiVersion - version of the MPIO_DSM_Path class to use
+
+Return Value:
+
+ None
+--*/
+{
+ PDSM_DEVICE_INFO devInfo;
+ PMPIO_DSM_Path_V2 dsmPath;
+ ULONG inx;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSaveDeviceState (LBP %p): Entering function.\n",
+ SupportedLBPolicies));
+
+ inx = 0;
+
+ while (inx < ((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSMPathCount) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath = (PVOID)&(((PDSM_Load_Balance_Policy)SupportedLBPolicies)->DSM_Paths[inx]);
+
+ } else {
+
+ dsmPath = &(((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSM_Paths[inx]);
+ }
+
+ devInfo = (PDSM_DEVICE_INFO)dsmPath->Reserved;
+
+ if (devInfo) {
+
+ devInfo->TempPreviousStateForLB = devInfo->State;
+ }
+
+ inx++;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpSaveDeviceState (LBP %p): Exiting function.\n",
+ SupportedLBPolicies));
+
+ return;
+}
+
+
+VOID
+DsmpRestorePreviousDeviceState(
+ _In_ IN PVOID SupportedLBPolicies,
+ _In_ IN ULONG DsmWmiVersion
+ )
+/*++
+
+Routine Description:
+
+ This routine restores the old Load Balance policy settings.
+ If there is any error while setting the new policy given
+ by the user, the old state is restored from the saved state.
+
+ Note: This routine MUST be called with DsmContextLock held in Exclusive mode.
+
+Arguements:
+
+ SupportedLBPolicies - New Load Balance policy values
+ DsmWmiVersion - version of the MPIO_DSM_Path class to use
+
+Return Value:
+
+ None
+--*/
+{
+ PDSM_DEVICE_INFO devInfo;
+ PMPIO_DSM_Path_V2 dsmPath;
+ ULONG inx;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpRestorePreviousDeviceState (LBP %p): Entering function.\n",
+ SupportedLBPolicies));
+
+ inx = 0;
+
+ while (inx < ((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSMPathCount) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath = (PVOID)&(((PDSM_Load_Balance_Policy)SupportedLBPolicies)->DSM_Paths[inx]);
+
+ } else {
+
+ dsmPath = &(((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSM_Paths[inx]);
+ }
+
+ devInfo = (PDSM_DEVICE_INFO)dsmPath->Reserved;
+
+ if (devInfo) {
+
+ devInfo->State = devInfo->TempPreviousStateForLB;
+ }
+
+ inx++;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpRestorePreviousDeviceState (LBP %p): Exiting function.\n",
+ SupportedLBPolicies));
+
+ return;
+}
+
+
+VOID
+DsmpUpdateDesiredStateAndWeight(
+ _In_ IN PDSM_GROUP_ENTRY Group,
+ _In_ IN ULONG DsmWmiVersion,
+ _In_ IN PVOID SupportedLBPolicies
+ )
+/*++
+
+Routine Description:
+
+ This routine updates the desired state and path weights
+ based on admin's LB selection.
+
+ Note: This routine MUST be called with DsmContextLock held in Exclusive mode.
+
+Arguements:
+
+ Group - The group entry correponding to the pseudo-LUN.
+ SupportedLBPolicies - New Load Balance policy values
+ DsmWmiVersion - version of the MPIO_DSM_Path class to use
+
+Return Value:
+
+ None
+--*/
+{
+ PMPIO_DSM_Path_V2 dsmPath;
+ PDSM_DEVICE_INFO devInfo;
+ ULONG inx;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpUpdatedDesiredState (Group %p): Entering function.\n",
+ Group));
+
+ inx = 0;
+ while (inx < ((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSMPathCount) {
+
+ if (DsmWmiVersion == DSM_WMI_VERSION_1) {
+
+ dsmPath = (PVOID)&(((PDSM_Load_Balance_Policy)SupportedLBPolicies)->DSM_Paths[inx]);
+
+ } else {
+
+ dsmPath = &(((PDSM_Load_Balance_Policy_V2)SupportedLBPolicies)->DSM_Paths[inx]);
+ }
+
+ devInfo = (PDSM_DEVICE_INFO) dsmPath->Reserved;
+
+ if (!devInfo) {
+
+ inx++;
+ continue;
+ }
+
+ DSM_ASSERT(devInfo->DeviceSig == DSM_DEVICE_SIG);
+ NT_ASSERT(devInfo->Group == Group);
+
+ //
+ // We'll honor the chosen path for FOO for ALUA storage
+ // since we know for a fact that the Admin has chosen the path.
+ // We'll also honor path state in RRWS if it is different from TPG state
+ // as that too is an indication that it was explicitly selected.
+ //
+ if ((DsmpIsSymmetricAccess(devInfo)) ||
+ (Group->LoadBalanceType == DSM_LB_FAILOVER) ||
+ (!DsmpIsSymmetricAccess(devInfo) && Group->LoadBalanceType == DSM_LB_ROUND_ROBIN_WITH_SUBSET && devInfo->State != devInfo->ALUAState)) {
+
+ //
+ // Check if this is the primary path or a standby path
+ //
+ if (dsmPath->PrimaryPath) {
+
+ devInfo->DesiredState = DSM_DEV_ACTIVE_OPTIMIZED;
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ if (!dsmPath->OptimizedPath) {
+
+ devInfo->DesiredState = DSM_DEV_ACTIVE_UNOPTIMIZED;
+ }
+ }
+
+ } else {
+
+ devInfo->DesiredState = DSM_DEV_STANDBY;
+
+ if (DsmWmiVersion > DSM_WMI_VERSION_1) {
+
+ if (!dsmPath->OptimizedPath) {
+
+ devInfo->DesiredState = DSM_DEV_UNAVAILABLE;
+ }
+ }
+ }
+ } else {
+
+ devInfo->DesiredState = DSM_DEV_UNDETERMINED;
+ }
+
+ if (Group->LoadBalanceType == DSM_LB_WEIGHTED_PATHS) {
+
+ devInfo->PathWeight = dsmPath->PathWeight;
+ }
+
+ inx++;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpUpdatedDesiredState (Group %p): Exiting function.\n",
+ Group));
+
+ return;
+}
+
+
+NTSTATUS
+DsmpQueryDevicePerf(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ PDSM_IDS DsmIds,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ )
+/*++
+
+Routine Description:
+
+ This routine returns the perf counters for each path for the
+ device that corresponds to the passed in DsmIds.
+
+Arguements:
+
+ DsmContext - Global DSM context
+ DsmIds - DSM Ids for the given device
+ InBufferSize - Size of the input buffer
+ OutBufferSize - Size of the output buffer
+ Buffer - Buffer in which the current Load Balance policy settings
+ is returned, if the buffer is big enough
+
+Return Value:
+
+ STATUS_SUCCESS on success
+ Appropriate error code on error.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSM_DEVICE_INFO devInfo;
+ ULONG sizeNeeded;
+ PMSDSM_DEVICE_PERF devicePerf;
+ ULONG i;
+ PMSDSM_DEVICEPATH_PERF pathPerf;
+ KIRQL irql;
+
+ UNREFERENCED_PARAMETER(InBufferSize);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDevicePerf (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // At least one device should be given
+ //
+ if (DsmIds->Count == 0) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDevicePerf (DsmIds %p): No DSM Ids given.\n",
+ DsmIds));
+
+ *OutBufferSize = 0;
+ status = STATUS_INVALID_PARAMETER;
+
+ goto __Exit_DsmpQueryDevicePerf;
+ }
+
+ sizeNeeded = AlignOn8Bytes(FIELD_OFFSET(MSDSM_DEVICE_PERF, PerfInfo));
+ sizeNeeded += (DsmIds->Count * sizeof(MSDSM_DEVICEPATH_PERF));
+
+ if (*OutBufferSize < sizeNeeded) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDevicePerf (DsmIds %p): Output buffer too small for QueryLBPolicy.\n",
+ DsmIds));
+
+ *OutBufferSize = sizeNeeded;
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ goto __Exit_DsmpQueryDevicePerf;
+ }
+
+ //
+ // Zero out the output buffer first
+ //
+ RtlZeroMemory(Buffer, sizeNeeded);
+
+#if DBG
+ devInfo = DsmIds->IdList[0];
+ DSM_ASSERT(devInfo);
+ DSM_ASSERT(devInfo->DeviceSig == DSM_DEVICE_SIG);
+#endif
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ devicePerf = (PMSDSM_DEVICE_PERF)Buffer;
+ devicePerf->NumberPaths = DsmIds->Count;
+
+ //
+ // For each path, get the stats info
+ //
+ for (i = 0; i < DsmIds->Count; i++) {
+
+ pathPerf = &devicePerf->PerfInfo[i];
+ devInfo = DsmIds->IdList[i];
+
+ if (DsmpIsDeviceInitialized(devInfo)) {
+
+ pathPerf->PathId = (ULONGLONG)((ULONG_PTR)((devInfo->FailGroup)->PathId));
+ pathPerf->NumberReads = (devInfo->DeviceStats).NumberReads;
+ pathPerf->NumberWrites = (devInfo->DeviceStats).NumberWrites;
+ pathPerf->BytesRead = (devInfo->DeviceStats).BytesRead;
+ pathPerf->BytesWritten = (devInfo->DeviceStats).BytesWritten;
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+ *OutBufferSize = sizeNeeded;
+
+__Exit_DsmpQueryDevicePerf:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDevicePerf (DsmIds %p): Exiting function with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpClearPerfCounters(
+ _In_ IN PDSM_CONTEXT DsmContext,
+ _In_ IN PDSM_IDS DsmIds
+ )
+/*++
+
+Routine Description:
+
+ This routine clears the perf counters for each path for the
+ device that corresponds to the passed in DsmIds.
+
+Arguements:
+
+ DsmContext - Global DSM context
+ DsmIds - DSM Ids for the given device
+
+Return Value:
+
+ STATUS_SUCCESS on success
+ Appropriate error code on error.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PDSM_DEVICE_INFO devInfo;
+ KIRQL irql;
+ ULONG i;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpClearPerfCounters (DsmIds %p): Entering function.\n",
+ DsmIds));
+
+ //
+ // At least one device should be given
+ //
+ if (DsmIds->Count == 0) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpClearPerfCounters (DsmIds %p): No DSM Ids given.\n",
+ DsmIds));
+
+ status = STATUS_INVALID_PARAMETER;
+
+ goto __Exit_DsmpClearPerfCounters;
+ }
+
+ irql = ExAcquireSpinLockExclusive(&(DsmContext->DsmContextLock));
+
+ for (i = 0; i < DsmIds->Count; i++) {
+
+ devInfo = DsmIds->IdList[i];
+ DSM_ASSERT(devInfo);
+ DSM_ASSERT(devInfo->DeviceSig == DSM_DEVICE_SIG);
+
+ if (devInfo) {
+ (devInfo->DeviceStats).BytesRead = 0;
+ (devInfo->DeviceStats).BytesWritten = 0;
+ (devInfo->DeviceStats).NumberReads = 0;
+ (devInfo->DeviceStats).NumberWrites = 0;
+ }
+ }
+
+ ExReleaseSpinLockExclusive(&(DsmContext->DsmContextLock), irql);
+
+__Exit_DsmpClearPerfCounters:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpClearPerfCounters (DsmIds %p): Exiting function with status %x.\n",
+ DsmIds,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQuerySupportedDevicesList(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ )
+/*++
+
+Routine Description:
+
+ This routine returns the list of devices that are supported by MSDSM.
+
+Arguements:
+
+ DsmContext - Global DSM context
+ InBufferSize - Size of the input buffer
+ OutBufferSize - Size of the output buffer
+ Buffer - Buffer in which the current Load Balance policy settings
+ is returned, if the buffer is big enough
+
+Return Value:
+
+ STATUS_SUCCESS on success
+ Appropriate error code on error.
+
+--*/
+{
+ NTSTATUS status;
+ ULONG sizeNeeded;
+ PMSDSM_SUPPORTED_DEVICES_LIST supportedDeviceIds;
+ PWSTR szIndex;
+ PWSTR deviceIdIndex;
+ ULONG numberDeviceIds = 0;
+ ULONG index = 0;
+ KIRQL oldIrql;
+ PWSTR tempBuffer = NULL;
+
+ UNREFERENCED_PARAMETER(InBufferSize);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQuerySupportedDevicesList (DsmContext %p): Entering function.\n",
+ DsmContext));
+
+ //
+ // It is possible that manually changes to the registry weren't yet picked up,
+ // so query for the list in its current state. Failure to get this list is not
+ // fatal, so ignore errors.
+ //
+#if DBG
+ status = DsmpGetDeviceList(DsmContext);
+ NT_ASSERT(NT_SUCCESS(status));
+#else
+ DsmpGetDeviceList(DsmContext);
+#endif
+
+ //
+ // Since it is possible that this list may change if a new device arrival
+ // gets processed at the same time as this query being processed, we need
+ // to protect it.
+ //
+ KeAcquireSpinLock(&DsmContext->SupportedDevicesListLock, &oldIrql);
+
+ tempBuffer = DsmpAllocatePool(NonPagedPoolNx, DsmContext->SupportedDevices.MaximumLength, DSM_TAG_REG_VALUE_RELATED);
+
+ if (tempBuffer) {
+
+ RtlCopyMemory(tempBuffer, DsmContext->SupportedDevices.Buffer, DsmContext->SupportedDevices.Length);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQuerySupportedDevicesList (DsmContext %p): Failed to allocate temporary list.\n",
+ DsmContext));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ KeReleaseSpinLock(&DsmContext->SupportedDevicesListLock, oldIrql);
+
+ goto __Exit_DsmpQuerySupportedDevicesList;
+ }
+
+ KeReleaseSpinLock(&DsmContext->SupportedDevicesListLock, oldIrql);
+
+ status = STATUS_SUCCESS;
+ szIndex = tempBuffer;
+
+ sizeNeeded = AlignOn8Bytes(FIELD_OFFSET(MSDSM_SUPPORTED_DEVICES_LIST, DeviceId));
+
+ if (szIndex) {
+
+ while (*szIndex) {
+
+ szIndex += wcslen(szIndex) + 1;
+ numberDeviceIds++;
+ }
+
+ sizeNeeded += numberDeviceIds * (MSDSM_MAX_DEVICE_ID_SIZE + sizeof(WNULL));
+ }
+
+ if (*OutBufferSize < sizeNeeded) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQuerySupportedDevicesList (DsmContext %p): Output buffer too small for QuerySupportedDevicesList.\n",
+ DsmContext));
+
+ *OutBufferSize = sizeNeeded;
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ goto __Exit_DsmpQuerySupportedDevicesList;
+ }
+
+ //
+ // Zero out the output buffer first
+ //
+ RtlZeroMemory(Buffer, sizeNeeded);
+
+ *OutBufferSize = sizeNeeded;
+
+ supportedDeviceIds = (PMSDSM_SUPPORTED_DEVICES_LIST)Buffer;
+ supportedDeviceIds->NumberDevices = numberDeviceIds;
+
+ for (index = 0, szIndex = tempBuffer, deviceIdIndex = supportedDeviceIds->DeviceId;
+ index < numberDeviceIds;
+ index++, szIndex += wcslen(szIndex) + 1, deviceIdIndex += MSDSM_MAX_DEVICE_ID_LENGTH) {
+
+ *((PUSHORT)deviceIdIndex) = MSDSM_MAX_DEVICE_ID_SIZE;
+ deviceIdIndex++;
+
+ RtlStringCchCopyW(deviceIdIndex,
+ MSDSM_MAX_DEVICE_ID_LENGTH - 1,
+ szIndex);
+ }
+
+__Exit_DsmpQuerySupportedDevicesList:
+
+ if (tempBuffer) {
+ DsmpFreePool(tempBuffer);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQuerySupportedDevicesList (DsmContext %p): Exiting function with status %x.\n",
+ DsmContext,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryTargetsDefaultPolicy(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to build the target list (for which the override default LB policy
+ was explicitly set), by querying the services key for the subkeys under
+ "msdsm\Parameters\DsmTargetsLoadBalanceSetting"
+
+Arguements:
+
+ Context - The DSM Context value. It contains storage for the target hardware ids and their
+ default policy info.
+ InBufferSize - Size of the input buffer
+ OutBufferSize - Size of the output buffer
+ Buffer - Buffer in which the current targets whose default policy settings is returned, if the buffer is big enough
+
+Return Value:
+
+ STATUS_SUCCESS on success
+ Appropriate error code on error.
+
+--*/
+{
+ ULONG sizeNeeded;
+ PMSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICY targetsPolicyInfo = (PMSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICY)Buffer;
+ PMSDSM_TARGET_DEFAULT_POLICY_INFO targetPolicyInfo;
+ HANDLE targetsLBSettingKey = NULL;
+ NTSTATUS status;
+ PKEY_FULL_INFORMATION keyFullInfo = NULL;
+ ULONG length = sizeof(KEY_FULL_INFORMATION);
+ ULONG numSubKeys = 0;
+ WCHAR vidPid[25] = {0};
+ PKEY_BASIC_INFORMATION keyBasicInfo = NULL;
+ OBJECT_ATTRIBUTES objectAttributes;
+ HANDLE targetKey = NULL;
+ ULONG index = 0;
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+ DSM_LOAD_BALANCE_TYPE loadBalanceType;
+ ULONGLONG preferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+ PWCHAR policyInfoIndex;
+ UNICODE_STRING keyValueName;
+ PKEY_VALUE_PARTIAL_INFORMATION keyValueInfo = NULL;
+
+ UNREFERENCED_PARAMETER(InBufferSize);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Entering function.\n",
+ DsmContext));
+
+ status = DsmpOpenTargetsLoadBalanceSettingKey(KEY_ALL_ACCESS, &targetsLBSettingKey);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to open Targets LB Setting key. Status %x.\n",
+ DsmContext,
+ status));
+
+ goto __Exit_DsmpQueryTargetsDefaultPolicy;
+ }
+
+ //
+ // Query for number of subkeys
+ //
+ do {
+ if (keyFullInfo) {
+
+ DsmpFreePool(keyFullInfo);
+ }
+
+ keyFullInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned, length, DSM_TAG_REG_KEY_RELATED);
+
+ if (!keyFullInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to allocate resources for key full info.\n",
+ DsmContext));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpQueryTargetsDefaultPolicy;
+ }
+
+ status = ZwQueryKey(targetsLBSettingKey,
+ KeyFullInformation,
+ keyFullInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to query key. Status %x.\n",
+ DsmContext,
+ status));
+
+ goto __Exit_DsmpQueryTargetsDefaultPolicy;
+ }
+
+ //
+ // Calculate total buffer size required
+ //
+ numSubKeys = keyFullInfo->SubKeys;
+
+ sizeNeeded = AlignOn8Bytes(FIELD_OFFSET(MSDSM_TARGETS_DEFAULT_LOAD_BALANCE_POLICY, TargetDefaultPolicyInfo));
+ sizeNeeded += numSubKeys * sizeof(MSDSM_TARGET_DEFAULT_POLICY_INFO);
+
+ if (*OutBufferSize < sizeNeeded) {
+
+ *OutBufferSize = sizeNeeded;
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Buffer insufficient. Status %x.\n",
+ DsmContext,
+ status));
+
+ goto __Exit_DsmpQueryTargetsDefaultPolicy;
+ }
+
+ *OutBufferSize = sizeNeeded;
+ RtlZeroMemory(Buffer, *OutBufferSize);
+
+ targetsPolicyInfo->NumberDevices = numSubKeys;
+ targetPolicyInfo = targetsPolicyInfo->TargetDefaultPolicyInfo;
+
+ //
+ // Now Enumerate all of the subkeys
+ //
+ for(index = 0; index < numSubKeys && NT_SUCCESS(status); index++) {
+
+ UNICODE_STRING targetName;
+
+ if (targetKey) {
+ ZwClose(targetKey);
+ targetKey = NULL;
+ }
+
+ length = sizeof(KEY_BASIC_INFORMATION);
+
+ do {
+ if (keyBasicInfo) {
+
+ DsmpFreePool(keyBasicInfo);
+ }
+
+ keyBasicInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned,
+ length,
+ DSM_TAG_REG_KEY_RELATED);
+
+ if (!keyBasicInfo) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to allocate resources for key basic info.\n",
+ DsmContext));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __Exit_DsmpQueryTargetsDefaultPolicy;
+ }
+
+ //
+ // Enumerate the index'th subkey
+ //
+ status = ZwEnumerateKey(targetsLBSettingKey,
+ index,
+ KeyBasicInformation,
+ keyBasicInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ //
+ // Ignore errors - this is a best case effort.
+ //
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to enumerate sub key's info. Status %x.\n",
+ DsmContext,
+ status));
+
+ status = STATUS_SUCCESS;
+ continue;
+ }
+
+ RtlZeroMemory(vidPid, sizeof(vidPid));
+ RtlStringCbCopyNW(vidPid, sizeof(vidPid), keyBasicInfo->Name, keyBasicInfo->NameLength);
+ RtlInitUnicodeString(&targetName, vidPid);
+
+ //
+ // Open a handle to the the target subkey.
+ //
+ InitializeObjectAttributes(&objectAttributes,
+ &targetName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ targetsLBSettingKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(&targetKey,
+ KEY_ALL_ACCESS,
+ &objectAttributes);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to open reg key %ws. Status %x.\n",
+ DsmContext,
+ vidPid,
+ status));
+
+ goto __Exit_DsmpQueryTargetsDefaultPolicy;
+ }
+
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_TYPECHECK;
+ queryTable[0].Name = DSM_LOAD_BALANCE_POLICY;
+ queryTable[0].EntryContext = &loadBalanceType;
+ queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ targetKey,
+ queryTable,
+ targetKey,
+ NULL);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to query LB Policy for %ws - error %x.\n",
+ DsmContext,
+ vidPid,
+ status));
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): LB Policy for %ws is %d.\n",
+ DsmContext,
+ vidPid,
+ loadBalanceType));
+
+ RtlInitUnicodeString(&keyValueName, DSM_PREFERRED_PATH);
+
+ length = sizeof(KEY_VALUE_PARTIAL_INFORMATION);
+
+ do {
+ DsmpFreePool(keyValueInfo);
+ keyValueInfo = DsmpAllocatePool(NonPagedPoolNxCacheAligned, length, DSM_TAG_REG_KEY_RELATED);
+ if (!keyValueInfo) {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to allocate resources for keyValueInfo (PP). Status %x.\n",
+ DsmContext,
+ status));
+
+ goto __Exit_DsmpQueryTargetsDefaultPolicy;
+ }
+
+ status = ZwQueryValueKey(targetKey,
+ &keyValueName,
+ KeyValuePartialInformation,
+ keyValueInfo,
+ length,
+ &length);
+
+ } while (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (NT_SUCCESS(status)) {
+
+ NT_ASSERT(keyValueInfo->DataLength == sizeof(ULONGLONG));
+
+ preferredPath = *((ULONGLONG UNALIGNED *)keyValueInfo->Data);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): PreferredPath for %ws is %I64x.\n",
+ DsmContext,
+ vidPid,
+ preferredPath));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Failed to query PreferredPath for %ws. Status %x.\n",
+ DsmContext,
+ vidPid,
+ status));
+ }
+
+ //
+ // Copy over this target's policy info.
+ //
+ policyInfoIndex = targetPolicyInfo->HardwareId;
+ *((PUSHORT)policyInfoIndex) = MSDSM_MAX_DEVICE_ID_SIZE;
+ policyInfoIndex++;
+ RtlStringCchCopyW((PWSTR)policyInfoIndex, MSDSM_MAX_DEVICE_ID_LENGTH - 1, vidPid);
+ targetPolicyInfo->LoadBalancePolicy = loadBalanceType;
+ targetPolicyInfo->PreferredPath = preferredPath;
+
+ targetPolicyInfo++;
+ }
+ }
+
+__Exit_DsmpQueryTargetsDefaultPolicy:
+
+ if (targetKey) {
+ ZwClose(targetKey);
+ }
+
+ if (targetsLBSettingKey) {
+ ZwClose(targetsLBSettingKey);
+ }
+
+ if (keyBasicInfo) {
+ DsmpFreePool(keyBasicInfo);
+ }
+
+ if (keyValueInfo) {
+ DsmpFreePool(keyValueInfo);
+ }
+
+ if (keyFullInfo) {
+ DsmpFreePool(keyFullInfo);
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryTargetsDefaultPolicy (Context %p): Exiting function with status %x.\n",
+ DsmContext,
+ status));
+
+ return status;
+}
+
+
+NTSTATUS
+DsmpQueryDsmDefaultPolicy(
+ _In_ PDSM_CONTEXT DsmContext,
+ _In_ ULONG InBufferSize,
+ _Inout_ PULONG OutBufferSize,
+ _Out_writes_to_(*OutBufferSize, *OutBufferSize) PUCHAR Buffer
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to return the override MSDSM-wide default LB policy
+ if it was explicitly set, by querying the services key at "msdsm\Parameters"
+
+Arguements:
+
+ Context - The DSM Context value. It contains storage for the target hardware ids and their
+ default policy info.
+ InBufferSize - Size of the input buffer
+ OutBufferSize - Size of the output buffer
+ Buffer - Buffer in which the current MSDSM-wide default policy is returned, if the buffer
+ is big enough
+
+Return Value:
+
+ STATUS_SUCCESS on success
+ Appropriate error code on error.
+
+--*/
+{
+ PMSDSM_DEFAULT_LOAD_BALANCE_POLICY dsmPolicyInfo = (PMSDSM_DEFAULT_LOAD_BALANCE_POLICY)Buffer;
+ NTSTATUS status;
+ DSM_LOAD_BALANCE_TYPE loadBalanceType;
+ ULONGLONG preferredPath = (ULONGLONG)((ULONG_PTR)MAXULONG);
+
+ UNREFERENCED_PARAMETER(InBufferSize);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDsmDefaultPolicy (Context %p): Entering function.\n",
+ DsmContext));
+
+ if (*OutBufferSize < sizeof(MSDSM_DEFAULT_LOAD_BALANCE_POLICY)) {
+
+ *OutBufferSize = sizeof(MSDSM_DEFAULT_LOAD_BALANCE_POLICY);
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDsmDefaultPolicy (Context %p): Buffer insufficient. Status %x.\n",
+ DsmContext,
+ status));
+
+ goto __Exit_DsmpQueryDsmDefaultPolicy;
+ }
+
+ *OutBufferSize = sizeof(MSDSM_DEFAULT_LOAD_BALANCE_POLICY);
+ RtlZeroMemory(Buffer, *OutBufferSize);
+
+ status = DsmpQueryDsmLBPolicyFromRegistry(&loadBalanceType, &preferredPath);
+
+ if (NT_SUCCESS(status)) {
+
+ dsmPolicyInfo->LoadBalancePolicy = loadBalanceType;
+ dsmPolicyInfo->PreferredPath = (ULONGLONG)((ULONG_PTR)preferredPath);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDsmDefaultPolicy (Context %p): LB policy = %u, Preferred path = %I64x.\n",
+ DsmContext,
+ dsmPolicyInfo->LoadBalancePolicy,
+ dsmPolicyInfo->PreferredPath));
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDsmDefaultPolicy (Context %p): Query for MSDSM-wide policy, status %x.\n",
+ DsmContext,
+ status));
+ }
+
+__Exit_DsmpQueryDsmDefaultPolicy:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_WMI,
+ "DsmpQueryDsmDefaultPolicy (Context %p): Exiting function with status %x.\n",
+ DsmContext,
+ status));
+
+ return status;
+}
+
diff --git a/tests/projects/wdk/kmdf/msdsm/xmake.lua b/tests/projects/wdk/kmdf/msdsm/xmake.lua
new file mode 100644
index 000000000..e765c1d43
--- /dev/null
+++ b/tests/projects/wdk/kmdf/msdsm/xmake.lua
@@ -0,0 +1,14 @@
+
+-- add modes: debug and release
+add_rules("mode.debug", "mode.release")
+
+-- add target
+target("msdsm")
+
+ -- add rules
+ add_rules("wdk.kmdf.driver")
+
+ -- add files
+ add_files("*.c")
+ add_files("*.mof", "*.rc", "*.inf")
+
diff --git a/tests/projects/wdk/kmdf/serial/xmake.lua b/tests/projects/wdk/kmdf/serial/xmake.lua
index 49bf2215e..e2f91d20f 100644
--- a/tests/projects/wdk/kmdf/serial/xmake.lua
+++ b/tests/projects/wdk/kmdf/serial/xmake.lua
@@ -2,9 +2,6 @@
-- add modes: debug and release
add_rules("mode.debug", "mode.release")
--- add include directories
-add_includedirs(".")
-
-- add target
target("serial")
diff --git a/xmake/rules/wdk/mof/xmake.lua b/xmake/rules/wdk/mof/xmake.lua
new file mode 100644
index 000000000..0e1678a54
--- /dev/null
+++ b/xmake/rules/wdk/mof/xmake.lua
@@ -0,0 +1,108 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015 - 2018, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file xmake.lua
+--
+
+-- define rule: *.mof
+rule("wdk.mof")
+
+ -- add rule: wdk environment
+ add_deps("wdk.env")
+
+ -- set extensions
+ set_extensions(".mof")
+
+ -- on load
+ on_load(function (target)
+
+ -- imports
+ import("core.project.config")
+
+ -- get arch
+ local arch = assert(config.arch(), "arch not found!")
+
+ -- get mofcomp
+ local mofcomp = path.join(target:data("wdk").bindir, arch, is_host("windows") and "mofcomp.exe" or "mofcomp")
+ assert(mofcomp and os.isexec(mofcomp), "mofcomp not found!")
+
+ -- save mofcomp
+ target:data_set("wdk.mofcomp", mofcomp)
+
+ -- save output directory
+ target:data_set("wdk.mof.outputdir", path.join(config.buildir(), ".wdk", "mof", config.get("mode") or "generic", config.get("arch") or os.arch(), target:name()))
+ end)
+
+ -- before build file
+ before_build_file(function (target, sourcefile, opt)
+
+ -- imports
+ import("core.base.option")
+ import("core.project.depend")
+
+ -- get mofcomp
+ local mofcomp = target:data("wdk.mofcomp")
+
+ -- get output directory
+ local outputdir = target:data("wdk.mof.outputdir")
+
+ -- init args
+ local args = {}
+ local flags = target:values("wdk.mof.flags")
+ if flags then
+ table.join2(args, flags)
+ end
+
+ -- add includedirs
+ target:add("includedirs", outputdir)
+
+ -- need build this object?
+ --[[
+ local dependfile = target:dependfile(headerfile)
+ local dependinfo = option.get("rebuild") and {} or (depend.load(dependfile) or {})
+ if not depend.is_changed(dependinfo, {lastmtime = os.mtime(headerfile), values = args}) then
+ return
+ end
+ ]]
+
+ -- trace progress info
+ if option.get("verbose") then
+ cprint("${green}[%02d%%]:${dim} compiling.wdk.mof %s", opt.progress, sourcefile)
+ else
+ cprint("${green}[%02d%%]:${clear} compiling.wdk.mof %s", opt.progress, sourcefile)
+ end
+
+ -- do mofcomp
+ --[[
+ if not os.isdir(outputdir) then
+ os.mkdir(outputdir)
+ end
+ os.vrunv(mofcomp, args)
+ ]]
+
+ -- update files and values to the dependent file
+ --[[
+ dependinfo.files = {sourcefile}
+ dependinfo.values = args
+ depend.save(dependinfo, dependfile)
+ ]]
+ end)
+
diff --git a/xmake/rules/wdk/xmake.lua b/xmake/rules/wdk/xmake.lua
index 931803f44..2415e17e7 100644
--- a/xmake/rules/wdk/xmake.lua
+++ b/xmake/rules/wdk/xmake.lua
@@ -26,7 +26,7 @@
rule("wdk.umdf.driver")
-- add rules
- add_deps("wdk.inf", "wdk.man", "wdk.mc")
+ add_deps("wdk.inf", "wdk.man", "wdk.mc", "wdk.mof")
-- on load
on_load(function (target)
@@ -37,7 +37,7 @@ rule("wdk.umdf.driver")
rule("wdk.umdf.binary")
-- add rules
- add_deps("wdk.inf", "wdk.man", "wdk.mc")
+ add_deps("wdk.inf", "wdk.man", "wdk.mc", "wdk.mof")
-- on load
on_load(function (target)
@@ -48,7 +48,7 @@ rule("wdk.umdf.binary")
rule("wdk.kmdf.driver")
-- add rules
- add_deps("wdk.inf", "wdk.man", "wdk.mc")
+ add_deps("wdk.inf", "wdk.man", "wdk.mc", "wdk.mof")
-- on load
on_load(function (target)
@@ -59,7 +59,7 @@ rule("wdk.kmdf.driver")
rule("wdk.kmdf.binary")
-- add rules
- add_deps("wdk.inf", "wdk.man", "wdk.mc")
+ add_deps("wdk.inf", "wdk.man", "wdk.mc", "wdk.mof")
-- on load
on_load(function (target)