diff options
| author | ruki <[email protected]> | 2021-12-07 23:23:40 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2021-12-07 23:23:40 +0800 |
| commit | b3ae4feec5afe623183d92f8f3f9f0347f6abaea (patch) | |
| tree | 7c011f929a2902aeec3c56a31949c3755b367b5c /tests/projects/windows/driver | |
| parent | 9d19f1a747c0ebf692fbeca25a78f41b7fcd55e3 (diff) | |
add linux driver example
Diffstat (limited to 'tests/projects/windows/driver')
82 files changed, 56666 insertions, 0 deletions
diff --git a/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.c b/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.c new file mode 100644 index 000000000..13f811f35 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.c @@ -0,0 +1,1309 @@ +/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ nonpnp.c
+
+Abstract:
+
+ Purpose of this driver is to demonstrate how to write a legacy (NON WDM)
+ driver using framework, show how to handle 4 different ioctls -
+ METHOD_NEITHER - in particular and also show how to read & write to file
+ from KernelMode using Zw functions.
+
+ For a non-framework version of sample on how to handle IOCTLs in driver,
+ study src\general\IOCTL in the DDK.
+
+Environment:
+
+ Kernel mode only.
+
+--*/
+
+#include "nonpnp.h"
+
+//
+// The trace message header file must be included in a source file
+// before any WPP macro calls and after defining a WPP_CONTROL_GUIDS
+// macro. During the compilation, WPP scans the source files for
+// TraceEvents() calls and builds a .tmh file which stores a unique
+// data GUID for each message, the text resource string for each message,
+// and the data types of the variables passed in for each message.
+// This file is automatically generated and used during post-processing.
+//
+#include "nonpnp.tmh"
+
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text( INIT, DriverEntry )
+#pragma alloc_text( PAGE, NonPnpDeviceAdd)
+#pragma alloc_text( PAGE, NonPnpEvtDriverContextCleanup)
+#pragma alloc_text( PAGE, NonPnpEvtDriverUnload)
+#pragma alloc_text( PAGE, NonPnpEvtDeviceIoInCallerContext)
+#pragma alloc_text( PAGE, NonPnpEvtDeviceFileCreate)
+#pragma alloc_text( PAGE, NonPnpEvtFileClose)
+#pragma alloc_text( PAGE, FileEvtIoRead)
+#pragma alloc_text( PAGE, FileEvtIoWrite)
+#pragma alloc_text( PAGE, FileEvtIoDeviceControl)
+#endif // ALLOC_PRAGMA
+
+
+NTSTATUS
+DriverEntry(
+ IN OUT PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+ This routine is called by the Operating System to initialize the driver.
+
+ It creates the device object, fills in the dispatch entry points and
+ completes the initialization.
+
+Arguments:
+ DriverObject - a pointer to the object that represents this device
+ driver.
+
+ RegistryPath - a pointer to our Services key in the registry.
+
+Return Value:
+ STATUS_SUCCESS if initialized; an error otherwise.
+
+--*/
+{
+ NTSTATUS status;
+ WDF_DRIVER_CONFIG config;
+ WDFDRIVER hDriver;
+ PWDFDEVICE_INIT pInit = NULL;
+ WDF_OBJECT_ATTRIBUTES attributes;
+
+ KdPrint(("Driver Frameworks NONPNP Legacy Driver Example\n"));
+
+
+ WDF_DRIVER_CONFIG_INIT(
+ &config,
+ WDF_NO_EVENT_CALLBACK // This is a non-pnp driver.
+ );
+
+ //
+ // Tell the framework that this is non-pnp driver so that it doesn't
+ // set the default AddDevice routine.
+ //
+ config.DriverInitFlags |= WdfDriverInitNonPnpDriver;
+
+ //
+ // NonPnp driver must explicitly register an unload routine for
+ // the driver to be unloaded.
+ //
+ config.EvtDriverUnload = NonPnpEvtDriverUnload;
+
+ //
+ // Register a cleanup callback so that we can call WPP_CLEANUP when
+ // the framework driver object is deleted during driver unload.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.EvtCleanupCallback = NonPnpEvtDriverContextCleanup;
+
+ //
+ // Create a framework driver object to represent our driver.
+ //
+ status = WdfDriverCreate(DriverObject,
+ RegistryPath,
+ &attributes,
+ &config,
+ &hDriver);
+ if (!NT_SUCCESS(status)) {
+ KdPrint (("NonPnp: WdfDriverCreate failed with status 0x%x\n", status));
+ return status;
+ }
+
+ //
+ // Since we are calling WPP_CLEANUP in the DriverContextCleanup
+ // callback we should initialize WPP Tracing after WDFDRIVER
+ // object is created to ensure that we cleanup WPP properly
+ // if we return failure status from DriverEntry. This
+ // eliminates the need to call WPP_CLEANUP in every path
+ // of DriverEntry.
+ //
+ WPP_INIT_TRACING( DriverObject, RegistryPath );
+
+ //
+ // On Win2K system, you will experience some delay in getting trace events
+ // due to the way the ETW is activated to accept trace messages.
+ //
+ KdPrint(("NonPnp: DriverEntry: tracing enabled\n"));
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT,
+ "Driver Frameworks NONPNP Legacy Driver Example");
+
+ //
+ //
+ // In order to create a control device, we first need to allocate a
+ // WDFDEVICE_INIT structure and set all properties.
+ //
+ pInit = WdfControlDeviceInitAllocate(
+ hDriver,
+ &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R
+ );
+
+ if (pInit == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ return status;
+ }
+
+ //
+ // Call NonPnpDeviceAdd to create a deviceobject to represent our
+ // software device.
+ //
+ status = NonPnpDeviceAdd(hDriver, pInit);
+
+ return status;
+}
+
+NTSTATUS
+NonPnpDeviceAdd(
+ IN WDFDRIVER Driver,
+ IN PWDFDEVICE_INIT DeviceInit
+ )
+/*++
+
+Routine Description:
+
+ Called by the DriverEntry to create a control-device. This call is
+ responsible for freeing the memory for DeviceInit.
+
+Arguments:
+
+ DriverObject - a pointer to the object that represents this device
+ driver.
+
+ DeviceInit - Pointer to a driver-allocated WDFDEVICE_INIT structure.
+
+Return Value:
+
+ STATUS_SUCCESS if initialized; an error otherwise.
+
+--*/
+{
+ NTSTATUS status;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_IO_QUEUE_CONFIG ioQueueConfig;
+ WDF_FILEOBJECT_CONFIG fileConfig;
+ WDFQUEUE queue;
+ WDFDEVICE controlDevice;
+ DECLARE_CONST_UNICODE_STRING(ntDeviceName, NTDEVICE_NAME_STRING) ;
+ DECLARE_CONST_UNICODE_STRING(symbolicLinkName, SYMBOLIC_NAME_STRING) ;
+
+ UNREFERENCED_PARAMETER( Driver );
+
+ PAGED_CODE();
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT,
+ "NonPnpDeviceAdd DeviceInit %p\n", DeviceInit);
+ //
+ // Set exclusive to TRUE so that no more than one app can talk to the
+ // control device at any time.
+ //
+ WdfDeviceInitSetExclusive(DeviceInit, TRUE);
+
+ WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoBuffered);
+
+
+ status = WdfDeviceInitAssignName(DeviceInit, &ntDeviceName);
+
+ if (!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfDeviceInitAssignName failed %!STATUS!", status);
+ goto End;
+ }
+
+ WdfControlDeviceInitSetShutdownNotification(DeviceInit,
+ NonPnpShutdown,
+ WdfDeviceShutdown);
+
+ //
+ // Initialize WDF_FILEOBJECT_CONFIG_INIT struct to tell the
+ // framework whether you are interested in handling Create, Close and
+ // Cleanup requests that gets generated when an application or another
+ // kernel component opens an handle to the device. If you don't register
+ // the framework default behaviour would be to complete these requests
+ // with STATUS_SUCCESS. A driver might be interested in registering these
+ // events if it wants to do security validation and also wants to maintain
+ // per handle (fileobject) context.
+ //
+
+ WDF_FILEOBJECT_CONFIG_INIT(
+ &fileConfig,
+ NonPnpEvtDeviceFileCreate,
+ NonPnpEvtFileClose,
+ WDF_NO_EVENT_CALLBACK // not interested in Cleanup
+ );
+
+ WdfDeviceInitSetFileObjectConfig(DeviceInit,
+ &fileConfig,
+ WDF_NO_OBJECT_ATTRIBUTES);
+
+ //
+ // In order to support METHOD_NEITHER Device controls, or
+ // NEITHER device I/O type, we need to register for the
+ // EvtDeviceIoInProcessContext callback so that we can handle the request
+ // in the calling threads context.
+ //
+ WdfDeviceInitSetIoInCallerContextCallback(DeviceInit,
+ NonPnpEvtDeviceIoInCallerContext);
+
+ //
+ // Specify the size of device context
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
+ CONTROL_DEVICE_EXTENSION);
+
+ status = WdfDeviceCreate(&DeviceInit,
+ &attributes,
+ &controlDevice);
+ if (!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfDeviceCreate failed %!STATUS!", status);
+ goto End;
+ }
+
+ //
+ // Create a symbolic link for the control object so that usermode can open
+ // the device.
+ //
+
+
+ status = WdfDeviceCreateSymbolicLink(controlDevice,
+ &symbolicLinkName);
+
+ if (!NT_SUCCESS(status)) {
+ //
+ // Control device will be deleted automatically by the framework.
+ //
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfDeviceCreateSymbolicLink failed %!STATUS!", status);
+ goto End;
+ }
+
+ //
+ // Configure a default queue so that requests that are not
+ // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
+ // other queues get dispatched here.
+ //
+ WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig,
+ WdfIoQueueDispatchSequential);
+
+ ioQueueConfig.EvtIoRead = FileEvtIoRead;
+ ioQueueConfig.EvtIoWrite = FileEvtIoWrite;
+ ioQueueConfig.EvtIoDeviceControl = FileEvtIoDeviceControl;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ //
+ // Since we are using Zw function set execution level to passive so that
+ // framework ensures that our Io callbacks called at only passive-level
+ // even if the request came in at DISPATCH_LEVEL from another driver.
+ //
+ //attributes.ExecutionLevel = WdfExecutionLevelPassive;
+
+ //
+ // By default, Static Driver Verifier (SDV) displays a warning if it
+ // doesn't find the EvtIoStop callback on a power-managed queue.
+ // The 'assume' below causes SDV to suppress this warning. If the driver
+ // has not explicitly set PowerManaged to WdfFalse, the framework creates
+ // power-managed queues when the device is not a filter driver. Normally
+ // the EvtIoStop is required for power-managed queues, but for this driver
+ // it is not needed b/c the driver doesn't hold on to the requests or
+ // forward them to other drivers. This driver completes the requests
+ // directly in the queue's handlers. If the EvtIoStop callback is not
+ // implemented, the framework waits for all driver-owned requests to be
+ // done before moving in the Dx/sleep states or before removing the
+ // device, which is the correct behavior for this type of driver.
+ // If the requests were taking an indeterminate amount of time to complete,
+ // or if the driver forwarded the requests to a lower driver/another stack,
+ // the queue should have an EvtIoStop/EvtIoResume.
+ //
+ __analysis_assume(ioQueueConfig.EvtIoStop != 0);
+ status = WdfIoQueueCreate(controlDevice,
+ &ioQueueConfig,
+ &attributes,
+ &queue // pointer to default queue
+ );
+ __analysis_assume(ioQueueConfig.EvtIoStop == 0);
+ if (!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfIoQueueCreate failed %!STATUS!", status);
+ goto End;
+ }
+
+ //
+ // Control devices must notify WDF when they are done initializing. I/O is
+ // rejected until this call is made.
+ //
+ WdfControlFinishInitializing(controlDevice);
+
+End:
+ //
+ // If the device is created successfully, framework would clear the
+ // DeviceInit value. Otherwise device create must have failed so we
+ // should free the memory ourself.
+ //
+ if (DeviceInit != NULL) {
+ WdfDeviceInitFree(DeviceInit);
+ }
+
+ return status;
+
+}
+
+VOID
+NonPnpEvtDriverContextCleanup(
+ IN WDFOBJECT Driver
+ )
+/*++
+Routine Description:
+
+ Called when the driver object is deleted during driver unload.
+ You can free all the resources created in DriverEntry that are
+ not automatically freed by the framework.
+
+Arguments:
+
+ Driver - Handle to a framework driver object created in DriverEntry
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT,
+ "Entered NonPnpEvtDriverContextCleanup\n");
+
+ PAGED_CODE();
+
+ //
+ // No need to free the controldevice object explicitly because it will
+ // be deleted when the Driver object is deleted due to the default parent
+ // child relationship between Driver and ControlDevice.
+ //
+ WPP_CLEANUP( WdfDriverWdmGetDriverObject( (WDFDRIVER)Driver ) );
+
+}
+
+
+
+VOID
+NonPnpEvtDeviceFileCreate (
+ IN WDFDEVICE Device,
+ IN WDFREQUEST Request,
+ IN WDFFILEOBJECT FileObject
+ )
+/*++
+
+Routine Description:
+
+ The framework calls a driver's EvtDeviceFileCreate callback
+ when it receives an IRP_MJ_CREATE request.
+ The system sends this request when a user application opens the
+ device to perform an I/O operation, such as reading or writing a file.
+ This callback is called synchronously, in the context of the thread
+ that created the IRP_MJ_CREATE request.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+ FileObject - Pointer to fileobject that represents the open handle.
+ CreateParams - Parameters of IO_STACK_LOCATION for create
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ PUNICODE_STRING fileName;
+ UNICODE_STRING absFileName, directory;
+ OBJECT_ATTRIBUTES fileAttributes;
+ IO_STATUS_BLOCK ioStatus;
+ PCONTROL_DEVICE_EXTENSION devExt;
+ NTSTATUS status;
+ USHORT length = 0;
+
+
+ UNREFERENCED_PARAMETER( FileObject );
+
+ PAGED_CODE ();
+
+ devExt = ControlGetData(Device);
+
+ //
+ // Assume the directory is a temp directory under %windir%
+ //
+ RtlInitUnicodeString(&directory, L"\\SystemRoot\\temp");
+
+ //
+ // Parsed filename has "\" in the begining. The object manager strips
+ // of all "\", except one, after the device name.
+ //
+ fileName = WdfFileObjectGetFileName(FileObject);
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "NonPnpEvtDeviceFileCreate %wZ%wZ",
+ &directory, fileName);
+
+ //
+ // Find the total length of the directory + filename
+ //
+ length = directory.Length + fileName->Length;
+
+ absFileName.Buffer = ExAllocatePoolWithTag(PagedPool, length, POOL_TAG);
+ if(absFileName.Buffer == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "ExAllocatePoolWithTag failed");
+ goto End;
+ }
+ absFileName.Length = 0;
+ absFileName.MaximumLength = length;
+
+ status = RtlAppendUnicodeStringToString(&absFileName, &directory);
+ if (!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
+ "RtlAppendUnicodeStringToString failed with status %!STATUS!",
+ status);
+ goto End;
+ }
+
+ status = RtlAppendUnicodeStringToString(&absFileName, fileName);
+ if (!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
+ "RtlAppendUnicodeStringToString failed with status %!STATUS!",
+ status);
+ goto End;
+ }
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "Absolute Filename %wZ", &absFileName);
+
+ InitializeObjectAttributes( &fileAttributes,
+ &absFileName,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ NULL, // RootDirectory
+ NULL // SecurityDescriptor
+ );
+
+ status = ZwCreateFile (
+ &devExt->FileHandle,
+ SYNCHRONIZE | GENERIC_WRITE | GENERIC_READ,
+ &fileAttributes,
+ &ioStatus,
+ NULL,// alloc size = none
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ,
+ FILE_OPEN_IF,
+ FILE_SYNCHRONOUS_IO_NONALERT |FILE_NON_DIRECTORY_FILE,
+ NULL,// eabuffer
+ 0// ealength
+ );
+
+ if (!NT_SUCCESS(status)) {
+
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT,
+ "ZwCreateFile failed with status %!STATUS!", status);
+ devExt->FileHandle = NULL;
+ }
+
+End:
+ if(absFileName.Buffer != NULL) {
+ ExFreePool(absFileName.Buffer);
+ }
+
+ WdfRequestComplete(Request, status);
+
+ return;
+}
+
+
+VOID
+NonPnpEvtFileClose (
+ IN WDFFILEOBJECT FileObject
+ )
+
+/*++
+
+Routine Description:
+
+ EvtFileClose is called when all the handles represented by the FileObject
+ is closed and all the references to FileObject is removed. This callback
+ may get called in an arbitrary thread context instead of the thread that
+ called CloseHandle. If you want to delete any per FileObject context that
+ must be done in the context of the user thread that made the Create call,
+ you should do that in the EvtDeviceCleanp callback.
+
+Arguments:
+
+ FileObject - Pointer to fileobject that represents the open handle.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ PCONTROL_DEVICE_EXTENSION devExt;
+
+ PAGED_CODE ();
+
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "NonPnpEvtFileClose\n");
+
+ devExt = ControlGetData(WdfFileObjectGetDevice(FileObject));
+
+ if(devExt->FileHandle) {
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT,
+ "Closing File Handle %p", devExt->FileHandle);
+ ZwClose(devExt->FileHandle);
+ }
+
+ return;
+}
+
+
+VOID
+FileEvtIoRead(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t Length
+ )
+/*++
+
+Routine Description:
+
+ This event is called when the framework receives IRP_MJ_READ requests.
+ We will just read the file.
+
+Arguments:
+
+ Queue - Handle to the framework queue object that is associated with the
+ I/O request.
+ Request - Handle to a framework request object.
+
+ Length - number of bytes to be read.
+ Queue is by default configured to fail zero length read & write requests.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PVOID outBuf;
+ IO_STATUS_BLOCK ioStatus;
+ PCONTROL_DEVICE_EXTENSION devExt;
+ FILE_POSITION_INFORMATION position;
+ ULONG_PTR bytesRead = 0;
+ size_t bufLength;
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_RW, "FileEvtIoRead: Request: 0x%p, Queue: 0x%p\n",
+ Request, Queue);
+
+ PAGED_CODE ();
+
+ //
+ // Get the request buffer. Since the device is set to do buffered
+ // I/O, this function will retrieve Irp->AssociatedIrp.SystemBuffer.
+ //
+ status = WdfRequestRetrieveOutputBuffer(Request, 0, &outBuf, &bufLength);
+ if(!NT_SUCCESS(status)) {
+ WdfRequestComplete(Request, status);
+ return;
+
+ }
+
+ devExt = ControlGetData(WdfIoQueueGetDevice(Queue));
+
+ if(devExt->FileHandle) {
+
+ //
+ // Set the file position to the beginning of the file.
+ //
+ position.CurrentByteOffset.QuadPart = 0;
+ status = ZwSetInformationFile(devExt->FileHandle,
+ &ioStatus,
+ &position,
+ sizeof(FILE_POSITION_INFORMATION),
+ FilePositionInformation);
+ if (NT_SUCCESS(status)) {
+
+ status = ZwReadFile (devExt->FileHandle,
+ NULL,// Event,
+ NULL,// PIO_APC_ROUTINE ApcRoutine
+ NULL,// PVOID ApcContext
+ &ioStatus,
+ outBuf,
+ (ULONG)Length,
+ 0, // ByteOffset
+ NULL // Key
+ );
+
+ if (!NT_SUCCESS(status)) {
+
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_RW,
+ "ZwReadFile failed with status 0x%x",
+ status);
+ }
+
+ status = ioStatus.Status;
+ bytesRead = ioStatus.Information;
+ }
+ }
+
+ WdfRequestCompleteWithInformation(Request, status, bytesRead);
+
+}
+
+
+
+VOID
+FileEvtIoWrite(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t Length
+ )
+/*++
+
+Routine Description:
+
+ This event is called when the framework receives IRP_MJ_WRITE requests.
+
+Arguments:
+
+ Queue - Handle to the framework queue object that is associated with the
+ I/O request.
+ Request - Handle to a framework request object.
+
+ Length - number of bytes to be written.
+ Queue is by default configured to fail zero length read & write requests.
+
+
+Return Value:
+
+ None
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PVOID inBuf;
+ IO_STATUS_BLOCK ioStatus;
+ PCONTROL_DEVICE_EXTENSION devExt;
+ FILE_POSITION_INFORMATION position;
+ ULONG_PTR bytesWritten = 0;
+ size_t bufLength;
+
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_RW, "FileEvtIoWrite: Request: 0x%p, Queue: 0x%p\n",
+ Request, Queue);
+ PAGED_CODE ();
+
+ //
+ // Get the request buffer. Since the device is set to do buffered
+ // I/O, this function will retrieve Irp->AssociatedIrp.SystemBuffer.
+ //
+ status = WdfRequestRetrieveInputBuffer(Request, 0, &inBuf, &bufLength);
+ if(!NT_SUCCESS(status)) {
+ WdfRequestComplete(Request, status);
+ return;
+
+ }
+
+ devExt = ControlGetData(WdfIoQueueGetDevice(Queue));
+
+ if(devExt->FileHandle) {
+
+ //
+ // Set the file position to the beginning of the file.
+ //
+ position.CurrentByteOffset.QuadPart = 0;
+
+ status = ZwSetInformationFile(devExt->FileHandle,
+ &ioStatus,
+ &position,
+ sizeof(FILE_POSITION_INFORMATION),
+ FilePositionInformation);
+ if (NT_SUCCESS(status))
+ {
+
+ status = ZwWriteFile(devExt->FileHandle,
+ NULL,// Event,
+ NULL,// PIO_APC_ROUTINE ApcRoutine
+ NULL,// PVOID ApcContext
+ &ioStatus,
+ inBuf,
+ (ULONG)Length,
+ 0, // ByteOffset
+ NULL // Key
+ );
+ if (!NT_SUCCESS(status))
+ {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_RW,
+ "ZwWriteFile failed with status 0x%x",
+ status);
+ }
+
+ status = ioStatus.Status;
+ bytesWritten = ioStatus.Information;
+ }
+ }
+
+ WdfRequestCompleteWithInformation(Request, status, bytesWritten);
+
+}
+
+VOID
+FileEvtIoDeviceControl(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t OutputBufferLength,
+ IN size_t InputBufferLength,
+ IN ULONG IoControlCode
+ )
+/*++
+Routine Description:
+
+ This event is called when the framework receives IRP_MJ_DEVICE_CONTROL
+ requests from the system.
+
+Arguments:
+
+ Queue - Handle to the framework queue object that is associated
+ with the I/O request.
+ Request - Handle to a framework request object.
+
+ OutputBufferLength - length of the request's output buffer,
+ if an output buffer is available.
+ InputBufferLength - length of the request's input buffer,
+ if an input buffer is available.
+
+ IoControlCode - the driver-defined or system-defined I/O control code
+ (IOCTL) that is associated with the request.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;// Assume success
+ PCHAR inBuf = NULL, outBuf = NULL; // pointer to Input and output buffer
+ PCHAR data = "this String is from Device Driver !!!";
+ ULONG datalen = (ULONG) strlen(data)+1;//Length of data including null
+ PCHAR buffer = NULL;
+ PREQUEST_CONTEXT reqContext = NULL;
+ size_t bufSize;
+
+ UNREFERENCED_PARAMETER( Queue );
+
+ PAGED_CODE();
+
+ if(!OutputBufferLength || !InputBufferLength)
+ {
+ WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ //
+ // Determine which I/O control code was specified.
+ //
+
+ switch (IoControlCode)
+ {
+ case IOCTL_NONPNP_METHOD_BUFFERED:
+
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Called IOCTL_NONPNP_METHOD_BUFFERED\n");
+
+ //
+ // For bufffered ioctls WdfRequestRetrieveInputBuffer &
+ // WdfRequestRetrieveOutputBuffer return the same buffer
+ // pointer (Irp->AssociatedIrp.SystemBuffer), so read the
+ // content of the buffer before writing to it.
+ //
+ status = WdfRequestRetrieveInputBuffer(Request, 0, &inBuf, &bufSize);
+ if(!NT_SUCCESS(status)) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+ }
+
+ ASSERT(bufSize == InputBufferLength);
+
+ //
+ // Read the input buffer content.
+ // We are using the following function to print characters instead
+ // TraceEvents with %s format because the string we get may or
+ // may not be null terminated. The buffer may contain non-printable
+ // characters also.
+ //
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data from User : %!HEXDUMP!\n",
+ log_xstr(inBuf, (USHORT)InputBufferLength)));
+ PrintChars(inBuf, InputBufferLength );
+
+
+ status = WdfRequestRetrieveOutputBuffer(Request, 0, &outBuf, &bufSize);
+ if(!NT_SUCCESS(status)) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+ }
+
+ ASSERT(bufSize == OutputBufferLength);
+
+ //
+ // Writing to the buffer over-writes the input buffer content
+ //
+
+ RtlCopyMemory(outBuf, data, OutputBufferLength);
+
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data to User : %!HEXDUMP!\n",
+ log_xstr(outBuf, (USHORT)datalen)));
+ PrintChars(outBuf, datalen );
+
+ //
+ // Assign the length of the data copied to IoStatus.Information
+ // of the request and complete the request.
+ //
+ WdfRequestSetInformation(Request,
+ OutputBufferLength < datalen? OutputBufferLength:datalen);
+
+ //
+ // When the request is completed the content of the SystemBuffer
+ // is copied to the User output buffer and the SystemBuffer is
+ // is freed.
+ //
+
+ break;
+
+
+ case IOCTL_NONPNP_METHOD_IN_DIRECT:
+
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Called IOCTL_NONPNP_METHOD_IN_DIRECT\n");
+
+ //
+ // Get the Input buffer. WdfRequestRetrieveInputBuffer returns
+ // Irp->AssociatedIrp.SystemBuffer.
+ //
+ status = WdfRequestRetrieveInputBuffer(Request, 0, &inBuf, &bufSize);
+ if(!NT_SUCCESS(status)) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+ }
+
+ ASSERT(bufSize == InputBufferLength);
+
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data from User : %!HEXDUMP!\n",
+ log_xstr(inBuf, (USHORT)InputBufferLength)));
+ PrintChars(inBuf, InputBufferLength);
+
+ //
+ // Get the output buffer. Framework calls MmGetSystemAddressForMdlSafe
+ // on the Irp->MdlAddress and returns the system address.
+ // Oddity: For this method, this buffer is intended for transfering data
+ // from the application to the driver.
+ //
+
+ status = WdfRequestRetrieveOutputBuffer(Request, 0, &buffer, &bufSize);
+ if(!NT_SUCCESS(status)) {
+ break;
+ }
+
+ ASSERT(bufSize == OutputBufferLength);
+
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data from User in OutputBuffer: %!HEXDUMP!\n",
+ log_xstr(buffer, (USHORT)OutputBufferLength)));
+ PrintChars(buffer, OutputBufferLength);
+
+ //
+ // Return total bytes read from the output buffer.
+ // Note OutputBufferLength = MmGetMdlByteCount(Irp->MdlAddress)
+ //
+
+ WdfRequestSetInformation(Request, OutputBufferLength);
+
+ //
+ // NOTE: Changes made to the SystemBuffer are not copied
+ // to the user input buffer by the I/O manager
+ //
+
+ break;
+
+ case IOCTL_NONPNP_METHOD_OUT_DIRECT:
+
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Called IOCTL_NONPNP_METHOD_OUT_DIRECT\n");
+
+ //
+ // Get the Input buffer. WdfRequestRetrieveInputBuffer returns
+ // Irp->AssociatedIrp.SystemBuffer.
+ //
+ status = WdfRequestRetrieveInputBuffer(Request, 0, &inBuf, &bufSize);
+ if(!NT_SUCCESS(status)) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+ }
+
+ ASSERT(bufSize == InputBufferLength);
+
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data from User : %!HEXDUMP!\n",
+ log_xstr(inBuf, (USHORT)InputBufferLength)));
+ PrintChars(inBuf, InputBufferLength);
+
+ //
+ // Get the output buffer. Framework calls MmGetSystemAddressForMdlSafe
+ // on the Irp->MdlAddress and returns the system address.
+ // For this method, this buffer is intended for transfering data from the
+ // driver to the application.
+ //
+ status = WdfRequestRetrieveOutputBuffer(Request, 0, &buffer, &bufSize);
+ if(!NT_SUCCESS(status)) {
+ break;
+ }
+
+ ASSERT(bufSize == OutputBufferLength);
+
+ //
+ // Write data to be sent to the user in this buffer
+ //
+ RtlCopyMemory(buffer, data, OutputBufferLength);
+
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data to User : %!HEXDUMP!\n",
+ log_xstr(buffer, (USHORT)datalen)));
+ PrintChars(buffer, datalen);
+
+ WdfRequestSetInformation(Request,
+ OutputBufferLength < datalen? OutputBufferLength: datalen);
+
+ //
+ // NOTE: Changes made to the SystemBuffer are not copied
+ // to the user input buffer by the I/O manager
+ //
+
+ break;
+
+ case IOCTL_NONPNP_METHOD_NEITHER:
+ {
+ size_t inBufLength, outBufLength;
+
+ //
+ // The NonPnpEvtDeviceIoInCallerContext has already probe and locked the
+ // pages and mapped the user buffer into system address space and
+ // stored memory buffer pointers in the request context. We can get the
+ // buffer pointer by calling WdfMemoryGetBuffer.
+ //
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Called IOCTL_NONPNP_METHOD_NEITHER\n");
+
+ reqContext = GetRequestContext(Request);
+
+ inBuf = WdfMemoryGetBuffer(reqContext->InputMemoryBuffer, &inBufLength);
+ outBuf = WdfMemoryGetBuffer(reqContext->OutputMemoryBuffer, &outBufLength);
+
+ if(inBuf == NULL || outBuf == NULL) {
+ status = STATUS_INVALID_PARAMETER;
+ }
+
+ ASSERT(inBufLength == InputBufferLength);
+ ASSERT(outBufLength == OutputBufferLength);
+
+ //
+ // Now you can safely read the data from the buffer in any arbitrary
+ // context.
+ //
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data from User : %!HEXDUMP!\n",
+ log_xstr(inBuf, (USHORT)inBufLength)));
+ PrintChars(inBuf, inBufLength);
+
+ //
+ // Write to the buffer in any arbitrary context.
+ //
+ RtlCopyMemory(outBuf, data, outBufLength);
+
+ Hexdump((TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Data to User : %!HEXDUMP!\n",
+ log_xstr(outBuf, (USHORT)datalen)));
+ PrintChars(outBuf, datalen);
+
+ //
+ // Assign the length of the data copied to IoStatus.Information
+ // of the Irp and complete the Irp.
+ //
+ WdfRequestSetInformation(Request,
+ outBufLength < datalen? outBufLength:datalen);
+
+ break;
+ }
+ default:
+
+ //
+ // The specified I/O control code is unrecognized by this driver.
+ //
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL, "ERROR: unrecognized IOCTL %x\n", IoControlCode);
+ break;
+ }
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Completing Request %p with status %X",
+ Request, status );
+
+ WdfRequestComplete( Request, status);
+
+}
+
+VOID
+NonPnpEvtDeviceIoInCallerContext(
+ IN WDFDEVICE Device,
+ IN WDFREQUEST Request
+ )
+/*++
+Routine Description:
+
+ This I/O in-process callback is called in the calling threads context/address
+ space before the request is subjected to any framework locking or queueing
+ scheme based on the device pnp/power or locking attributes set by the
+ driver. The process context of the calling app is guaranteed as long as
+ this driver is a top-level driver and no other filter driver is attached
+ to it.
+
+ This callback is only required if you are handling method-neither IOCTLs,
+ or want to process requests in the context of the calling process.
+
+ Driver developers should avoid defining neither IOCTLs and access user
+ buffers, and use much safer I/O tranfer methods such as buffered I/O
+ or direct I/O.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ Request - Handle to a framework request object. Framework calls
+ PreProcess callback only for Read/Write/ioctls and internal
+ ioctl requests.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PREQUEST_CONTEXT reqContext = NULL;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_REQUEST_PARAMETERS params;
+ size_t inBufLen, outBufLen;
+ PVOID inBuf, outBuf;
+
+ PAGED_CODE();
+
+ WDF_REQUEST_PARAMETERS_INIT(¶ms);
+
+ WdfRequestGetParameters(Request, ¶ms );
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "Entered NonPnpEvtDeviceIoInCallerContext %p \n",
+ Request);
+
+ //
+ // Check to see whether we have recevied a METHOD_NEITHER IOCTL. if not
+ // just send the request back to framework because we aren't doing
+ // any pre-processing in the context of the calling thread process.
+ //
+ if(!(params.Type == WdfRequestTypeDeviceControl &&
+ params.Parameters.DeviceIoControl.IoControlCode ==
+ IOCTL_NONPNP_METHOD_NEITHER)) {
+ //
+ // Forward it for processing by the I/O package
+ //
+ status = WdfDeviceEnqueueRequest(Device, Request);
+ if( !NT_SUCCESS(status) ) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
+ "Error forwarding Request 0x%x", status);
+ goto End;
+ }
+
+ return;
+ }
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "EvtIoPreProcess: received METHOD_NEITHER ioctl \n");
+
+ //
+ // In this type of transfer, the I/O manager assigns the user input
+ // to Type3InputBuffer and the output buffer to UserBuffer of the Irp.
+ // The I/O manager doesn't copy or map the buffers to the kernel
+ // buffers.
+ //
+ status = WdfRequestRetrieveUnsafeUserInputBuffer(Request, 0, &inBuf, &inBufLen);
+ if(!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
+ "Error WdfRequestRetrieveUnsafeUserInputBuffer failed 0x%x", status);
+ goto End;
+ }
+
+ status = WdfRequestRetrieveUnsafeUserOutputBuffer(Request, 0, &outBuf, &outBufLen);
+ if(!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
+ "Error WdfRequestRetrieveUnsafeUserOutputBuffer failed 0x%x", status);
+ goto End;
+ }
+
+ //
+ // Allocate a context for this request so that we can store the memory
+ // objects created for input and output buffer.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, REQUEST_CONTEXT);
+
+ status = WdfObjectAllocateContext(Request, &attributes, &reqContext);
+ if(!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
+ "Error WdfObjectAllocateContext failed 0x%x", status);
+ goto End;
+ }
+
+ //
+ // WdfRequestProbleAndLockForRead/Write function checks to see
+ // whether the caller in the right thread context, creates an MDL,
+ // probe and locks the pages, and map the MDL to system address
+ // space and finally creates a WDFMEMORY object representing this
+ // system buffer address. This memory object is associated with the
+ // request. So it will be freed when the request is completed. If we
+ // are accessing this memory buffer else where, we should store these
+ // pointers in the request context.
+ //
+
+ #pragma prefast(suppress:6387, "If inBuf==NULL at this point, then inBufLen==0")
+ status = WdfRequestProbeAndLockUserBufferForRead(Request,
+ inBuf,
+ inBufLen,
+ &reqContext->InputMemoryBuffer);
+
+ if(!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
+ "Error WdfRequestProbeAndLockUserBufferForRead failed 0x%x", status);
+ goto End;
+ }
+
+ #pragma prefast(suppress:6387, "If outBuf==NULL at this point, then outBufLen==0")
+ status = WdfRequestProbeAndLockUserBufferForWrite(Request,
+ outBuf,
+ outBufLen,
+ &reqContext->OutputMemoryBuffer);
+ if(!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
+ "Error WdfRequestProbeAndLockUserBufferForWrite failed 0x%x", status);
+ goto End;
+ }
+
+ //
+ // Finally forward it for processing by the I/O package
+ //
+ status = WdfDeviceEnqueueRequest(Device, Request);
+ if(!NT_SUCCESS(status)) {
+ TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
+ "Error WdfDeviceEnqueueRequest failed 0x%x", status);
+ goto End;
+ }
+
+ return;
+
+End:
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL, "EvtIoPreProcess failed %x \n", status);
+ WdfRequestComplete(Request, status);
+ return;
+}
+
+VOID
+NonPnpShutdown(
+ WDFDEVICE Device
+ )
+/*++
+
+Routine Description:
+ Callback invoked when the machine is shutting down. If you register for
+ a last chance shutdown notification you cannot do the following:
+ o Call any pageable routines
+ o Access pageable memory
+ o Perform any file I/O operations
+
+ If you register for a normal shutdown notification, all of these are
+ available to you.
+
+ This function implementation does nothing, but if you had any outstanding
+ file handles open, this is where you would close them.
+
+Arguments:
+ Device - The device which registered the notification during init
+
+Return Value:
+ None
+
+ --*/
+
+{
+ UNREFERENCED_PARAMETER(Device);
+ return;
+}
+
+
+VOID
+NonPnpEvtDriverUnload(
+ IN WDFDRIVER Driver
+ )
+/*++
+Routine Description:
+
+ Called by the I/O subsystem just before unloading the driver.
+ You can free the resources created in the DriverEntry either
+ in this routine or in the EvtDriverContextCleanup callback.
+
+Arguments:
+
+ Driver - Handle to a framework driver object created in DriverEntry
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ UNREFERENCED_PARAMETER(Driver);
+
+ PAGED_CODE();
+
+ TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "Entered NonPnpDriverUnload\n");
+
+ return;
+}
+
+VOID
+PrintChars(
+ _In_reads_(CountChars) PCHAR BufferAddress,
+ _In_ size_t CountChars
+ )
+{
+ if (CountChars) {
+
+ while (CountChars--) {
+
+ if (*BufferAddress > 31
+ && *BufferAddress != 127) {
+
+ KdPrint (( "%c", *BufferAddress) );
+
+ } else {
+
+ KdPrint(( ".") );
+
+ }
+ BufferAddress++;
+ }
+ KdPrint (("\n"));
+ }
+ return;
+}
+
diff --git a/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.h b/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.h new file mode 100644 index 000000000..c874e79d2 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.h @@ -0,0 +1,90 @@ +/*++
+
+Copyright (c) 1997 Microsoft Corporation
+
+Module Name:
+
+ nonpnp.h
+
+Abstract:
+
+ Contains function prototypes and includes other neccessary header files.
+
+Environment:
+
+ Kernel mode only.
+
+--*/
+
+#include <ntddk.h>
+#include <wdf.h>
+
+#define NTSTRSAFE_LIB
+#include <ntstrsafe.h>
+#include <wdmsec.h> // for SDDLs
+#include "public.h" // contains IOCTL definitions
+#include "Trace.h" // contains macros for WPP tracing
+
+#define NTDEVICE_NAME_STRING L"\\Device\\NONPNP"
+#define SYMBOLIC_NAME_STRING L"\\DosDevices\\NONPNP"
+#define POOL_TAG 'ELIF'
+
+typedef struct _CONTROL_DEVICE_EXTENSION {
+
+ HANDLE FileHandle; // Store your control data here
+
+} CONTROL_DEVICE_EXTENSION, *PCONTROL_DEVICE_EXTENSION;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CONTROL_DEVICE_EXTENSION,
+ ControlGetData)
+
+//
+// Following request context is used only for the method-neither ioctl case.
+//
+typedef struct _REQUEST_CONTEXT {
+
+ WDFMEMORY InputMemoryBuffer;
+ WDFMEMORY OutputMemoryBuffer;
+
+} REQUEST_CONTEXT, *PREQUEST_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(REQUEST_CONTEXT, GetRequestContext)
+
+//
+// Device driver routine declarations.
+//
+
+DRIVER_INITIALIZE DriverEntry;
+
+//
+// Don't use EVT_WDF_DRIVER_DEVICE_ADD for NonPnpDeviceAdd even though
+// the signature is same because this is not an event called by the
+// framework.
+//
+NTSTATUS
+NonPnpDeviceAdd(
+ IN WDFDRIVER Driver,
+ IN PWDFDEVICE_INIT DeviceInit
+ );
+
+EVT_WDF_DRIVER_UNLOAD NonPnpEvtDriverUnload;
+
+EVT_WDF_DEVICE_CONTEXT_CLEANUP NonPnpEvtDriverContextCleanup;
+EVT_WDF_DEVICE_SHUTDOWN_NOTIFICATION NonPnpShutdown;
+
+EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL FileEvtIoDeviceControl;
+EVT_WDF_IO_QUEUE_IO_READ FileEvtIoRead;
+EVT_WDF_IO_QUEUE_IO_WRITE FileEvtIoWrite;
+
+EVT_WDF_IO_IN_CALLER_CONTEXT NonPnpEvtDeviceIoInCallerContext;
+EVT_WDF_DEVICE_FILE_CREATE NonPnpEvtDeviceFileCreate;
+EVT_WDF_FILE_CLOSE NonPnpEvtFileClose;
+
+VOID
+PrintChars(
+ _In_reads_(CountChars) PCHAR BufferAddress,
+ _In_ size_t CountChars
+ );
+
+#pragma warning(disable:4127)
+
diff --git a/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.rc b/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.rc new file mode 100644 index 000000000..cac68f7b2 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/driver/nonpnp.rc @@ -0,0 +1,10 @@ +#include <windows.h>
+
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DRV
+#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
+#define VER_FILEDESCRIPTION_STR "Sample Non-PNP Driver using WDF"
+#define VER_INTERNALNAME_STR "NONPNP.sys"
+
+#include "common.ver"
diff --git a/tests/projects/windows/driver/kmdf/ioctl/driver/trace.h b/tests/projects/windows/driver/kmdf/ioctl/driver/trace.h new file mode 100644 index 000000000..089f213f0 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/driver/trace.h @@ -0,0 +1,68 @@ +/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ TRACE.h
+
+Abstract:
+
+ Header file for the debug tracing related function defintions and macros.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+//
+// If software tracing is defined in the sources file..
+// WPP_DEFINE_CONTROL_GUID specifies the GUID used for this driver.
+// *** REPLACE THE GUID WITH YOUR OWN UNIQUE ID ***
+// WPP_DEFINE_BIT allows setting debug bit masks to selectively print.
+// The names defined in the WPP_DEFINE_BIT call define the actual names
+// that are used to control the level of tracing for the control guid
+// specified.
+//
+// {71ae54db-0862-41bf-a24f-5330cec3c7f6}
+//
+#define WPP_CHECK_FOR_NULL_STRING //to prevent exceptions due to NULL strings
+
+#define WPP_CONTROL_GUIDS \
+ WPP_DEFINE_CONTROL_GUID( FileIoTraceGuid, \
+ (71ae54db,0862,41bf,a24f,5330cec3c7f6), \
+ WPP_DEFINE_BIT(DBG_INIT) \
+ WPP_DEFINE_BIT(DBG_RW) \
+ WPP_DEFINE_BIT(DBG_IOCTL) \
+ )
+
+#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) WPP_LEVEL_LOGGER(flags)
+#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)
+
+#pragma warning(disable:4204) // C4204 nonstandard extension used : non-constant aggregate initializer
+
+//
+// Define the 'xstr' structure for logging buffer and length pairs
+// and the 'log_xstr' function which returns it to create one in-place.
+// this enables logging of complex data types.
+//
+typedef struct xstr { char * _buf; short _len; } xstr_t;
+__inline xstr_t log_xstr(void * p, short l) { xstr_t xs = {(char*)p,l}; return xs; }
+
+#pragma warning(default:4204)
+
+//
+// Define the macro required for a hexdump use as:
+//
+// Hexdump((FLAG,"%!HEXDUMP!\n", log_xstr(buffersize,(char *)buffer) ));
+//
+//
+#define WPP_LOGHEXDUMP(x) WPP_LOGPAIR(2, &((x)._len)) WPP_LOGPAIR((x)._len, (x)._buf)
+
+
diff --git a/tests/projects/windows/driver/kmdf/ioctl/exe/install.c b/tests/projects/windows/driver/kmdf/ioctl/exe/install.c new file mode 100644 index 000000000..53d1f7678 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/exe/install.c @@ -0,0 +1,812 @@ +/*++
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ install.c
+
+Abstract:
+
+ Win32 routines to dynamically load and unload a Windows NT kernel-mode
+ driver using the Service Control Manager APIs.
+
+Environment:
+
+ User mode only
+
+--*/
+
+
+#include <DriverSpecs.h>
+_Analysis_mode_(_Analysis_code_type_user_code_)
+
+#include <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "public.h"
+
+#include <wdfinstaller.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) /sizeof(x[0]))
+
+extern
+PCHAR
+GetCoinstallerVersion(
+ VOID
+ ) ;
+
+BOOLEAN
+InstallDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName,
+ IN LPCTSTR ServiceExe
+ );
+
+
+BOOLEAN
+RemoveDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName
+ );
+
+BOOLEAN
+StartDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName
+ );
+
+BOOLEAN
+StopDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName
+ );
+
+#define SYSTEM32_DRIVERS "\\System32\\Drivers\\"
+#define NONPNP_INF_FILENAME L"\\nonpnp.inf"
+#define WDF_SECTION_NAME L"nonpnp.NT.Wdf"
+
+//----------------------------------------------------------------------------
+//
+//----------------------------------------------------------------------------
+PFN_WDFPREDEVICEINSTALLEX pfnWdfPreDeviceInstallEx;
+PFN_WDFPOSTDEVICEINSTALL pfnWdfPostDeviceInstall;
+PFN_WDFPREDEVICEREMOVE pfnWdfPreDeviceRemove;
+PFN_WDFPOSTDEVICEREMOVE pfnWdfPostDeviceRemove;
+
+//-----------------------------------------------------------------------------
+// 4127 -- Conditional Expression is Constant warning
+//-----------------------------------------------------------------------------
+#define WHILE(a) \
+__pragma(warning(suppress:4127)) while(a)
+
+LONG
+GetPathToInf(
+ _Out_writes_(InfFilePathSize) PWCHAR InfFilePath,
+ IN ULONG InfFilePathSize
+ )
+{
+ LONG error = ERROR_SUCCESS;
+
+ if (GetCurrentDirectoryW(InfFilePathSize, InfFilePath) == 0) {
+ error = GetLastError();
+ printf("InstallDriver failed! Error = %d \n", error);
+ return error;
+ }
+ if (FAILED( StringCchCatW(InfFilePath,
+ InfFilePathSize,
+ NONPNP_INF_FILENAME) )) {
+ error = ERROR_BUFFER_OVERFLOW;
+ return error;
+ }
+ return error;
+
+}
+
+//----------------------------------------------------------------------------
+//
+//----------------------------------------------------------------------------
+BOOLEAN
+InstallDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName,
+ IN LPCTSTR ServiceExe
+ )
+/*++
+
+Routine Description:
+
+Arguments:
+
+Return Value:
+
+--*/
+{
+ SC_HANDLE schService;
+ DWORD err;
+ WCHAR infPath[MAX_PATH];
+ WDF_COINSTALLER_INSTALL_OPTIONS clientOptions;
+
+ WDF_COINSTALLER_INSTALL_OPTIONS_INIT(&clientOptions);
+
+ //
+ // NOTE: This creates an entry for a standalone driver. If this
+ // is modified for use with a driver that requires a Tag,
+ // Group, and/or Dependencies, it may be necessary to
+ // query the registry for existing driver information
+ // (in order to determine a unique Tag, etc.).
+ //
+ //
+ // PRE-INSTALL for WDF support
+ //
+ err = GetPathToInf(infPath, ARRAY_SIZE(infPath) );
+ if (err != ERROR_SUCCESS) {
+ return FALSE;
+ }
+ err = pfnWdfPreDeviceInstallEx(infPath, WDF_SECTION_NAME, &clientOptions);
+
+ if (err != ERROR_SUCCESS) {
+ if (err == ERROR_SUCCESS_REBOOT_REQUIRED) {
+ printf("System needs to be rebooted, before the driver installation can proceed.\n");
+ }
+
+ return FALSE;
+ }
+
+ //
+ // Create a new a service object.
+ //
+
+ schService = CreateService(SchSCManager, // handle of service control manager database
+ DriverName, // address of name of service to start
+ DriverName, // address of display name
+ SERVICE_ALL_ACCESS, // type of access to service
+ SERVICE_KERNEL_DRIVER, // type of service
+ SERVICE_DEMAND_START, // when to start service
+ SERVICE_ERROR_NORMAL, // severity if service fails to start
+ ServiceExe, // address of name of binary file
+ NULL, // service does not belong to a group
+ NULL, // no tag requested
+ NULL, // no dependency names
+ NULL, // use LocalSystem account
+ NULL // no password for service account
+ );
+
+ if (schService == NULL) {
+
+ err = GetLastError();
+
+ if (err == ERROR_SERVICE_EXISTS) {
+
+ //
+ // Ignore this error.
+ //
+
+ return TRUE;
+
+ } else {
+
+ printf("CreateService failed! Error = %d \n", err );
+
+ //
+ // Indicate an error.
+ //
+
+ return FALSE;
+ }
+ }
+
+ //
+ // Close the service object.
+ //
+ CloseServiceHandle(schService);
+
+ //
+ // POST-INSTALL for WDF support
+ //
+ err = pfnWdfPostDeviceInstall( infPath, WDF_SECTION_NAME );
+
+ if (err != ERROR_SUCCESS) {
+ return FALSE;
+ }
+
+ //
+ // Indicate success.
+ //
+
+ return TRUE;
+
+} // InstallDriver
+
+BOOLEAN
+ManageDriver(
+ IN LPCTSTR DriverName,
+ IN LPCTSTR ServiceName,
+ IN USHORT Function
+ )
+{
+
+ SC_HANDLE schSCManager;
+
+ BOOLEAN rCode = TRUE;
+
+ //
+ // Insure (somewhat) that the driver and service names are valid.
+ //
+
+ if (!DriverName || !ServiceName) {
+
+ printf("Invalid Driver or Service provided to ManageDriver() \n");
+
+ return FALSE;
+ }
+
+ //
+ // Connect to the Service Control Manager and open the Services database.
+ //
+
+ schSCManager = OpenSCManager(NULL, // local machine
+ NULL, // local database
+ SC_MANAGER_ALL_ACCESS // access required
+ );
+
+ if (!schSCManager) {
+
+ printf("Open SC Manager failed! Error = %d \n", GetLastError());
+
+ return FALSE;
+ }
+
+ //
+ // Do the requested function.
+ //
+
+ switch( Function ) {
+
+ case DRIVER_FUNC_INSTALL:
+
+ //
+ // Install the driver service.
+ //
+
+ if (InstallDriver(schSCManager,
+ DriverName,
+ ServiceName
+ )) {
+
+ //
+ // Start the driver service (i.e. start the driver).
+ //
+
+ rCode = StartDriver(schSCManager,
+ DriverName
+ );
+
+ } else {
+
+ //
+ // Indicate an error.
+ //
+
+ rCode = FALSE;
+ }
+
+ break;
+
+ case DRIVER_FUNC_REMOVE:
+
+ //
+ // Stop the driver.
+ //
+
+ StopDriver(schSCManager,
+ DriverName
+ );
+
+ //
+ // Remove the driver service.
+ //
+
+ RemoveDriver(schSCManager,
+ DriverName
+ );
+
+ //
+ // Ignore all errors.
+ //
+
+ rCode = TRUE;
+
+ break;
+
+ default:
+
+ printf("Unknown ManageDriver() function. \n");
+
+ rCode = FALSE;
+
+ break;
+ }
+
+ //
+ // Close handle to service control manager.
+ //
+ CloseServiceHandle(schSCManager);
+
+ return rCode;
+
+} // ManageDriver
+
+
+BOOLEAN
+RemoveDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName
+ )
+{
+ SC_HANDLE schService;
+ BOOLEAN rCode;
+ DWORD err;
+ WCHAR infPath[MAX_PATH];
+
+ err = GetPathToInf(infPath, ARRAY_SIZE(infPath) );
+ if (err != ERROR_SUCCESS) {
+ return FALSE;
+ }
+
+ //
+ // PRE-REMOVE of WDF support
+ //
+ err = pfnWdfPreDeviceRemove( infPath, WDF_SECTION_NAME );
+
+ if (err != ERROR_SUCCESS) {
+ return FALSE;
+ }
+
+ //
+ // Open the handle to the existing service.
+ //
+
+ schService = OpenService(SchSCManager,
+ DriverName,
+ SERVICE_ALL_ACCESS
+ );
+
+ if (schService == NULL) {
+
+ printf("OpenService failed! Error = %d \n", GetLastError());
+
+ //
+ // Indicate error.
+ //
+
+ return FALSE;
+ }
+
+ //
+ // Mark the service for deletion from the service control manager database.
+ //
+
+ if (DeleteService(schService)) {
+
+ //
+ // Indicate success.
+ //
+
+ rCode = TRUE;
+
+ } else {
+
+ printf("DeleteService failed! Error = %d \n", GetLastError());
+
+ //
+ // Indicate failure. Fall through to properly close the service handle.
+ //
+
+ rCode = FALSE;
+ }
+
+ //
+ // Close the service object.
+ //
+ CloseServiceHandle(schService);
+
+ //
+ // POST-REMOVE of WDF support
+ //
+ err = pfnWdfPostDeviceRemove(infPath, WDF_SECTION_NAME );
+
+ if (err != ERROR_SUCCESS) {
+ rCode = FALSE;
+ }
+
+ return rCode;
+
+} // RemoveDriver
+
+
+
+BOOLEAN
+StartDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName
+ )
+{
+ SC_HANDLE schService;
+ DWORD err;
+ BOOL ok;
+
+ //
+ // Open the handle to the existing service.
+ //
+ schService = OpenService(SchSCManager,
+ DriverName,
+ SERVICE_ALL_ACCESS
+ );
+
+ if (schService == NULL) {
+ //
+ // Indicate failure.
+ //
+ printf("OpenService failed! Error = %d\n", GetLastError());
+ return FALSE;
+ }
+
+ //
+ // Start the execution of the service (i.e. start the driver).
+ //
+ ok = StartService( schService, 0, NULL );
+
+ if (!ok) {
+
+ err = GetLastError();
+
+ if (err == ERROR_SERVICE_ALREADY_RUNNING) {
+ //
+ // Ignore this error.
+ //
+ return TRUE;
+
+ } else {
+ //
+ // Indicate failure.
+ // Fall through to properly close the service handle.
+ //
+ printf("StartService failure! Error = %d\n", err );
+ return FALSE;
+ }
+ }
+
+ //
+ // Close the service object.
+ //
+ CloseServiceHandle(schService);
+
+ return TRUE;
+
+} // StartDriver
+
+
+
+BOOLEAN
+StopDriver(
+ IN SC_HANDLE SchSCManager,
+ IN LPCTSTR DriverName
+ )
+{
+ BOOLEAN rCode = TRUE;
+ SC_HANDLE schService;
+ SERVICE_STATUS serviceStatus;
+
+ //
+ // Open the handle to the existing service.
+ //
+
+ schService = OpenService(SchSCManager,
+ DriverName,
+ SERVICE_ALL_ACCESS
+ );
+
+ if (schService == NULL) {
+
+ printf("OpenService failed! Error = %d \n", GetLastError());
+
+ return FALSE;
+ }
+
+ //
+ // Request that the service stop.
+ //
+
+ if (ControlService(schService,
+ SERVICE_CONTROL_STOP,
+ &serviceStatus
+ )) {
+
+ //
+ // Indicate success.
+ //
+
+ rCode = TRUE;
+
+ } else {
+
+ printf("ControlService failed! Error = %d \n", GetLastError() );
+
+ //
+ // Indicate failure. Fall through to properly close the service handle.
+ //
+
+ rCode = FALSE;
+ }
+
+ //
+ // Close the service object.
+ //
+ CloseServiceHandle (schService);
+
+ return rCode;
+
+} // StopDriver
+
+
+//
+// Caller must free returned pathname string.
+//
+PCHAR
+BuildDriversDirPath(
+ _In_ PSTR DriverName
+ )
+{
+ size_t remain;
+ size_t len;
+ PCHAR dir;
+
+ if (!DriverName || strlen(DriverName) == 0) {
+ return NULL;
+ }
+
+ remain = MAX_PATH;
+
+ //
+ // Allocate string space
+ //
+ dir = (PCHAR) malloc( remain + 1 );
+
+ if (!dir) {
+ return NULL;
+ }
+
+ //
+ // Get the base windows directory path.
+ //
+ len = GetWindowsDirectory( dir, (UINT) remain );
+
+ if (len == 0 ||
+ (remain - len) < sizeof(SYSTEM32_DRIVERS)) {
+ free(dir);
+ return NULL;
+ }
+ remain -= len;
+
+ //
+ // Build dir to have "%windir%\System32\Drivers\<DriverName>".
+ //
+ if (FAILED( StringCchCat(dir, remain, SYSTEM32_DRIVERS) )) {
+ free(dir);
+ return NULL;
+ }
+
+ remain -= sizeof(SYSTEM32_DRIVERS);
+ len += sizeof(SYSTEM32_DRIVERS);
+ len += strlen(DriverName);
+
+ if (remain < len) {
+ free(dir);
+ return NULL;
+ }
+
+ if (FAILED( StringCchCat(dir, remain, DriverName) )) {
+ free(dir);
+ return NULL;
+ }
+
+ dir[len] = '\0'; // keeps prefast happy
+
+ return dir;
+}
+
+
+BOOLEAN
+SetupDriverName(
+ _Inout_updates_all_(BufferLength) PCHAR DriverLocation,
+ _In_ ULONG BufferLength
+ )
+{
+ HANDLE fileHandle;
+ DWORD driverLocLen = 0;
+ BOOL ok;
+ PCHAR driversDir;
+
+ //
+ // Setup path name to driver file.
+ //
+ driverLocLen =
+ GetCurrentDirectory(BufferLength, DriverLocation);
+
+ if (!driverLocLen) {
+
+ printf("GetCurrentDirectory failed! Error = %d \n",
+ GetLastError());
+
+ return FALSE;
+ }
+
+ if (FAILED( StringCchCat(DriverLocation, BufferLength, "\\" DRIVER_NAME ".sys") )) {
+ return FALSE;
+ }
+
+ //
+ // Insure driver file is in the specified directory.
+ //
+ fileHandle = CreateFile( DriverLocation,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL );
+
+ if (fileHandle == INVALID_HANDLE_VALUE) {
+ //
+ // Indicate failure.
+ //
+ printf("Driver: %s.SYS is not in the %s directory. \n",
+ DRIVER_NAME, DriverLocation );
+ return FALSE;
+ }
+
+ //
+ // Build %windir%\System32\Drivers\<DRIVER_NAME> path.
+ // Copy the driver to %windir%\system32\drivers
+ //
+ driversDir = BuildDriversDirPath( DRIVER_NAME ".sys" );
+
+ if (!driversDir) {
+ printf("BuildDriversDirPath failed!\n");
+ return FALSE;
+ }
+
+ ok = CopyFile( DriverLocation, driversDir, FALSE );
+
+ if(!ok) {
+ printf("CopyFile failed: error(%d) - \"%s\"\n",
+ GetLastError(), driversDir );
+ free(driversDir);
+ return FALSE;
+ }
+
+ if (FAILED( StringCchCopy(DriverLocation, BufferLength, driversDir) )) {
+ free(driversDir);
+ return FALSE;
+ }
+
+ free(driversDir);
+
+ //
+ // Close open file handle.
+ //
+ if (fileHandle) {
+ CloseHandle(fileHandle);
+ }
+
+ //
+ // Indicate success.
+ //
+ return TRUE;
+
+} // SetupDriverName
+
+
+HMODULE
+LoadWdfCoInstaller(
+ VOID
+ )
+{
+ HMODULE library = NULL;
+ DWORD error = ERROR_SUCCESS;
+ CHAR szCurDir[MAX_PATH];
+ CHAR tempCoinstallerName[MAX_PATH];
+ PCHAR coinstallerVersion;
+
+ do {
+
+ if (GetCurrentDirectory(MAX_PATH, szCurDir) == 0) {
+
+ printf("GetCurrentDirectory failed! Error = %d \n", GetLastError());
+ break;
+ }
+ coinstallerVersion = GetCoinstallerVersion();
+ if (FAILED( StringCchPrintf(tempCoinstallerName,
+ MAX_PATH,
+ "\\WdfCoInstaller%s.dll",
+ coinstallerVersion) )) {
+ break;
+ }
+ if (FAILED( StringCchCat(szCurDir, MAX_PATH, tempCoinstallerName) )) {
+ break;
+ }
+
+ library = LoadLibrary(szCurDir);
+
+ if (library == NULL) {
+ error = GetLastError();
+ printf("LoadLibrary(%s) failed: %d\n", szCurDir, error);
+ break;
+ }
+
+ pfnWdfPreDeviceInstallEx =
+ (PFN_WDFPREDEVICEINSTALLEX) GetProcAddress( library, "WdfPreDeviceInstallEx" );
+
+ if (pfnWdfPreDeviceInstallEx == NULL) {
+ error = GetLastError();
+ printf("GetProcAddress(\"WdfPreDeviceInstallEx\") failed: %d\n", error);
+ return NULL;
+ }
+
+ pfnWdfPostDeviceInstall =
+ (PFN_WDFPOSTDEVICEINSTALL) GetProcAddress( library, "WdfPostDeviceInstall" );
+
+ if (pfnWdfPostDeviceInstall == NULL) {
+ error = GetLastError();
+ printf("GetProcAddress(\"WdfPostDeviceInstall\") failed: %d\n", error);
+ return NULL;
+ }
+
+ pfnWdfPreDeviceRemove =
+ (PFN_WDFPREDEVICEREMOVE) GetProcAddress( library, "WdfPreDeviceRemove" );
+
+ if (pfnWdfPreDeviceRemove == NULL) {
+ error = GetLastError();
+ printf("GetProcAddress(\"WdfPreDeviceRemove\") failed: %d\n", error);
+ return NULL;
+ }
+
+ pfnWdfPostDeviceRemove =
+ (PFN_WDFPREDEVICEREMOVE) GetProcAddress( library, "WdfPostDeviceRemove" );
+
+ if (pfnWdfPostDeviceRemove == NULL) {
+ error = GetLastError();
+ printf("GetProcAddress(\"WdfPostDeviceRemove\") failed: %d\n", error);
+ return NULL;
+ }
+
+ } WHILE (0);
+
+ if (error != ERROR_SUCCESS) {
+ if (library) {
+ FreeLibrary( library );
+ }
+ library = NULL;
+ }
+
+ return library;
+}
+
+
+VOID
+UnloadWdfCoInstaller(
+ HMODULE Library
+ )
+{
+ if (Library) {
+ FreeLibrary( Library );
+ }
+}
+
diff --git a/tests/projects/windows/driver/kmdf/ioctl/exe/nonpnp.inf b/tests/projects/windows/driver/kmdf/ioctl/exe/nonpnp.inf new file mode 100644 index 000000000..b20a58b48 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/exe/nonpnp.inf @@ -0,0 +1,8 @@ +[Version] +Signature="$WINDOWS NT$" + +[nonpnp.NT.Wdf] +KmdfService = nonpnp, nonpnp_Service_kmdfInst + +[nonpnp_Service_kmdfInst] +KmdfLibraryVersion = 1.11 diff --git a/tests/projects/windows/driver/kmdf/ioctl/exe/testapp.c b/tests/projects/windows/driver/kmdf/ioctl/exe/testapp.c new file mode 100644 index 000000000..c12f26073 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/exe/testapp.c @@ -0,0 +1,643 @@ +/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+
+Module Name:
+
+ testapp.c
+
+Abstract:
+
+ Purpose of this app to test the NONPNP sample driver. The app
+ makes four different ioctl calls to test all the buffer types, write
+ some random buffer content to a file created by the driver in \SystemRoot\Temp
+ directory, and reads the same file and matches the content.
+ If -l option is specified, it does the write and read operation in a loop
+ until the app is terminated by pressing ^C.
+
+ Make sure you have the \SystemRoot\Temp directory exists before you run the test.
+
+Environment:
+
+ Win32 console application.
+
+--*/
+
+
+#include <DriverSpecs.h>
+_Analysis_mode_(_Analysis_code_type_user_code_)
+
+#include <windows.h>
+
+#pragma warning(disable:4201) // nameless struct/union
+#include <winioctl.h>
+#pragma warning(default:4201)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <limits.h>
+#include <strsafe.h>
+#include "public.h"
+
+
+BOOLEAN
+ManageDriver(
+ IN LPCTSTR DriverName,
+ IN LPCTSTR ServiceName,
+ IN USHORT Function
+ );
+
+HMODULE
+LoadWdfCoInstaller(
+ VOID
+ );
+
+VOID
+UnloadWdfCoInstaller(
+ HMODULE Library
+ );
+
+BOOLEAN
+SetupDriverName(
+ _Inout_updates_all_(BufferLength) PCHAR DriverLocation,
+ _In_ ULONG BufferLength
+ );
+
+BOOLEAN
+DoFileReadWrite(
+ HANDLE HDevice
+ );
+
+VOID
+DoIoctls(
+ HANDLE hDevice
+ );
+
+// for example, WDF 1.9 is "01009". the size 6 includes the ending NULL marker
+//
+#define MAX_VERSION_SIZE 6
+
+CHAR G_coInstallerVersion[MAX_VERSION_SIZE] = {0};
+BOOLEAN G_fLoop = FALSE;
+BOOL G_versionSpecified = FALSE;
+
+
+
+//-----------------------------------------------------------------------------
+// 4127 -- Conditional Expression is Constant warning
+//-----------------------------------------------------------------------------
+#define WHILE(constant) \
+__pragma(warning(disable: 4127)) while(constant); __pragma(warning(default: 4127))
+
+
+#define USAGE \
+"Usage: nonpnpapp <-V version> <-l> \n" \
+ " -V version {if no version is specified the version specified in the build environment will be used.}\n" \
+ " The version is the version of the KMDF coinstaller to use \n" \
+ " The format of version is MMmmm where MM -- major #, mmm - serial# \n" \
+ " -l { option to continuously read & write to the file} \n"
+
+BOOL
+ValidateCoinstallerVersion(
+ _In_ PSTR Version
+ )
+{ BOOL ok = FALSE;
+ INT i;
+
+ for(i= 0; i<MAX_VERSION_SIZE ;i++){
+ if( ! IsCharAlphaNumericA(Version[i])) {
+ break;
+ }
+ }
+ if (i == (MAX_VERSION_SIZE -sizeof(CHAR))) {
+ ok = TRUE;
+ }
+ return ok;
+}
+
+LONG
+Parse(
+ _In_ int argc,
+ _In_reads_(argc) char *argv[]
+ )
+/*++
+Routine Description:
+
+ Called by main() to parse command line parms
+
+Arguments:
+
+ argc and argv that was passed to main()
+
+Return Value:
+
+ Sets global flags as per user function request
+
+--*/
+{
+ int i;
+ BOOL ok;
+ LONG error = ERROR_SUCCESS;
+
+ for (i=0; i<argc; i++) {
+ if (argv[i][0] == '-' ||
+ argv[i][0] == '/') {
+ switch(argv[i][1]) {
+ case 'V':
+ case 'v':
+ if (( (i+1 < argc ) &&
+ ( argv[i+1][0] != '-' && argv[i+1][0] != '/'))) {
+ //
+ // use version in commandline
+ //
+ i++;
+ ok = ValidateCoinstallerVersion(argv[i]);
+ if (!ok) {
+ printf("Not a valid format for coinstaller version\n"
+ "It should be characters between A-Z, a-z , 0-9\n"
+ "The version format is MMmmm where MM -- major #, mmm - serial#");
+ error = ERROR_INVALID_PARAMETER;
+ break;
+ }
+ if (FAILED( StringCchCopy(G_coInstallerVersion,
+ MAX_VERSION_SIZE,
+ argv[i]) )) {
+ break;
+ }
+ G_versionSpecified = TRUE;
+
+ }
+ else{
+ printf(USAGE);
+ error = ERROR_INVALID_PARAMETER;
+ }
+ break;
+ case 'l':
+ case 'L':
+ G_fLoop = TRUE;
+ break;
+ default:
+ printf(USAGE);
+ error = ERROR_INVALID_PARAMETER;
+
+ }
+ }
+ }
+ return error;
+}
+
+PCHAR
+GetCoinstallerVersion(
+ VOID
+ )
+{
+ if (!G_versionSpecified &&
+ FAILED( StringCchPrintf(G_coInstallerVersion,
+ MAX_VERSION_SIZE,
+ "%02d%03d", // for example, "01009"
+ KMDF_VERSION_MAJOR,
+ KMDF_VERSION_MINOR)))
+ {
+ printf("StringCchCopy failed with error \n");
+ }
+
+ return (PCHAR)&G_coInstallerVersion;
+}
+
+VOID __cdecl
+main(
+ _In_ ULONG argc,
+ _In_reads_(argc) PCHAR argv[]
+ )
+{
+ HANDLE hDevice;
+ DWORD errNum = 0;
+ CHAR driverLocation [MAX_PATH];
+ BOOL ok;
+ HMODULE library = NULL;
+ LONG error;
+ PCHAR coinstallerVersion;
+
+ //
+ // Parse command line args
+ // -l -- loop option
+ //
+ if ( argc > 1 ) {// give usage if invoked with no parms
+ error = Parse(argc, argv);
+ if (error != ERROR_SUCCESS) {
+ return;
+ }
+ }
+
+ if (!G_versionSpecified ) {
+ coinstallerVersion = GetCoinstallerVersion();
+
+ //
+ // if no version is specified or an invalid one is specified use default version
+ //
+ printf("No version specified. Using default version:%s\n",
+ coinstallerVersion);
+
+ } else {
+ coinstallerVersion = (PCHAR)&G_coInstallerVersion;
+ }
+
+ //
+ // open the device
+ //
+ hDevice = CreateFile(DEVICE_NAME,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if(hDevice == INVALID_HANDLE_VALUE) {
+
+ errNum = GetLastError();
+
+ if (!(errNum == ERROR_FILE_NOT_FOUND ||
+ errNum == ERROR_PATH_NOT_FOUND)) {
+
+ printf("CreateFile failed! ERROR_FILE_NOT_FOUND = %d\n",
+ errNum);
+ return ;
+ }
+
+ //
+ // Load WdfCoInstaller.dll.
+ //
+ library = LoadWdfCoInstaller();
+
+ if (library == NULL) {
+ printf("The WdfCoInstaller%s.dll library needs to be "
+ "in same directory as nonpnpapp.exe\n", coinstallerVersion);
+ return;
+ }
+
+ //
+ // The driver is not started yet so let us the install the driver.
+ // First setup full path to driver name.
+ //
+ ok = SetupDriverName( driverLocation, MAX_PATH );
+
+ if (!ok) {
+ return ;
+ }
+
+ ok = ManageDriver( DRIVER_NAME,
+ driverLocation,
+ DRIVER_FUNC_INSTALL );
+
+ if (!ok) {
+
+ printf("Unable to install driver. \n");
+
+ //
+ // Error - remove driver.
+ //
+ ManageDriver( DRIVER_NAME,
+ driverLocation,
+ DRIVER_FUNC_REMOVE );
+ return;
+ }
+
+ hDevice = CreateFile( DEVICE_NAME,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL );
+
+ if (hDevice == INVALID_HANDLE_VALUE) {
+ printf ( "Error: CreatFile Failed : %d\n", GetLastError());
+ return;
+ }
+ }
+
+ DoIoctls(hDevice);
+
+ do {
+
+ if(!DoFileReadWrite(hDevice)) {
+ break;
+ }
+
+ if(!G_fLoop) {
+ break;
+ }
+ Sleep(1000); // sleep for 1 sec.
+
+ } WHILE (TRUE);
+
+ //
+ // Close the handle to the device before unloading the driver.
+ //
+ CloseHandle ( hDevice );
+
+ //
+ // Unload the driver. Ignore any errors.
+ //
+ ManageDriver( DRIVER_NAME,
+ driverLocation,
+ DRIVER_FUNC_REMOVE );
+
+ //
+ // Unload WdfCoInstaller.dll
+ //
+ if ( library ) {
+ UnloadWdfCoInstaller( library );
+ }
+ return;
+}
+
+
+VOID
+DoIoctls(
+ HANDLE hDevice
+ )
+{
+ char OutputBuffer[100];
+ char InputBuffer[200];
+ BOOL bRc;
+ ULONG bytesReturned;
+
+ //
+ // Printing Input & Output buffer pointers and size
+ //
+
+ printf("InputBuffer Pointer = %p, BufLength = %Id\n", InputBuffer,
+ sizeof(InputBuffer));
+ printf("OutputBuffer Pointer = %p BufLength = %Id\n", OutputBuffer,
+ sizeof(OutputBuffer));
+ //
+ // Performing METHOD_BUFFERED
+ //
+
+ if(FAILED(StringCchCopy(InputBuffer, sizeof(InputBuffer),
+ "this String is from User Application; using METHOD_BUFFERED"))){
+ return;
+ }
+
+ printf("\nCalling DeviceIoControl METHOD_BUFFERED:\n");
+
+ memset(OutputBuffer, 0, sizeof(OutputBuffer));
+
+ bRc = DeviceIoControl ( hDevice,
+ (DWORD) IOCTL_NONPNP_METHOD_BUFFERED,
+ InputBuffer,
+ (DWORD) strlen( InputBuffer )+1,
+ OutputBuffer,
+ sizeof( OutputBuffer),
+ &bytesReturned,
+ NULL
+ );
+
+ if ( !bRc )
+ {
+ printf ( "Error in DeviceIoControl : %d", GetLastError());
+ return;
+
+ }
+ printf(" OutBuffer (%d): %s\n", bytesReturned, OutputBuffer);
+
+
+ //
+ // Performing METHOD_NIETHER
+ //
+
+ printf("\nCalling DeviceIoControl METHOD_NEITHER\n");
+
+ if(FAILED(StringCchCopy(InputBuffer, sizeof(InputBuffer),
+ "this String is from User Application; using METHOD_NEITHER"))) {
+ return;
+ }
+
+ memset(OutputBuffer, 0, sizeof(OutputBuffer));
+
+ bRc = DeviceIoControl ( hDevice,
+ (DWORD) IOCTL_NONPNP_METHOD_NEITHER,
+ InputBuffer,
+ (DWORD) strlen( InputBuffer )+1,
+ OutputBuffer,
+ sizeof( OutputBuffer),
+ &bytesReturned,
+ NULL
+ );
+
+ if ( !bRc )
+ {
+ printf ( "Error in DeviceIoControl : %d\n", GetLastError());
+ return;
+
+ }
+
+ printf(" OutBuffer (%d): %s\n", bytesReturned, OutputBuffer);
+
+ //
+ // Performing METHOD_IN_DIRECT
+ //
+
+ printf("\nCalling DeviceIoControl METHOD_IN_DIRECT\n");
+
+ if(FAILED(StringCchCopy(InputBuffer, sizeof(InputBuffer),
+ "this String is from User Application; using METHOD_IN_DIRECT"))) {
+ return;
+ }
+
+ if(FAILED(StringCchCopy(OutputBuffer, sizeof(OutputBuffer),
+ "This String is from User Application in OutBuffer; using METHOD_IN_DIRECT"))) {
+ return;
+ }
+
+ bRc = DeviceIoControl ( hDevice,
+ (DWORD) IOCTL_NONPNP_METHOD_IN_DIRECT,
+ InputBuffer,
+ (DWORD) strlen( InputBuffer )+1,
+ OutputBuffer,
+ sizeof( OutputBuffer),
+ &bytesReturned,
+ NULL
+ );
+
+ if ( !bRc )
+ {
+ printf ( "Error in DeviceIoControl : : %d", GetLastError());
+ return;
+ }
+
+ printf(" Number of bytes transfered from OutBuffer: %d\n",
+ bytesReturned);
+
+ //
+ // Performing METHOD_OUT_DIRECT
+ //
+
+ printf("\nCalling DeviceIoControl METHOD_OUT_DIRECT\n");
+ if(FAILED(StringCchCopy(InputBuffer, sizeof(InputBuffer),
+ "this String is from User Application; using METHOD_OUT_DIRECT"))){
+ return;
+ }
+
+ memset(OutputBuffer, 0, sizeof(OutputBuffer));
+
+ bRc = DeviceIoControl ( hDevice,
+ (DWORD) IOCTL_NONPNP_METHOD_OUT_DIRECT,
+ InputBuffer,
+ (DWORD) strlen( InputBuffer )+1,
+ OutputBuffer,
+ sizeof( OutputBuffer),
+ &bytesReturned,
+ NULL
+ );
+
+ if ( !bRc )
+ {
+ printf ( "Error in DeviceIoControl : : %d", GetLastError());
+ return;
+ }
+
+ printf(" OutBuffer (%d): %s\n", bytesReturned, OutputBuffer);
+
+ return;
+
+}
+
+
+BOOLEAN
+DoFileReadWrite(
+ HANDLE HDevice
+ )
+{
+ ULONG bufLength, index;
+ PUCHAR readBuf = NULL;
+ PUCHAR writeBuf = NULL;
+ BOOLEAN ret;
+ ULONG bytesWritten, bytesRead;
+
+ //
+ // Seed the random-number generator with current time so that
+ // the numbers will be different every time we run.
+ //
+ srand( (unsigned)time( NULL ) );
+
+ //
+ // rand function returns a pseudorandom integer in the range 0 to RAND_MAX
+ // (0x7fff)
+ //
+ bufLength = rand();
+ //
+ // Try until the bufLength is not zero.
+ //
+ while(bufLength == 0) {
+ bufLength = rand();
+ }
+
+ //
+ // Allocate a buffer of that size to use for write operation.
+ //
+ writeBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufLength);
+ if(!writeBuf) {
+ ret = FALSE;
+ goto End;
+ }
+ //
+ // Fill the buffer with randon number less than UCHAR_MAX.
+ //
+ index = bufLength;
+ while(index){
+ writeBuf[index-1] = (UCHAR) rand() % UCHAR_MAX;
+ index--;
+ }
+
+ printf("Write %d bytes to file\n", bufLength);
+
+ //
+ // Tell the driver to write the buffer content to the file from the
+ // begining of the file.
+ //
+
+ if (!WriteFile(HDevice,
+ writeBuf,
+ bufLength,
+ &bytesWritten,
+ NULL)) {
+
+ printf("ReadFile failed with error 0x%x\n", GetLastError());
+
+ ret = FALSE;
+ goto End;
+
+ }
+
+ //
+ // Allocate another buffer of same size.
+ //
+ readBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufLength);
+ if(!readBuf) {
+
+ ret = FALSE;
+ goto End;
+ }
+
+ printf("Read %d bytes from the same file\n", bufLength);
+
+ //
+ // Tell the driver to read the file from the begining.
+ //
+ if (!ReadFile(HDevice,
+ readBuf,
+ bufLength,
+ &bytesRead,
+ NULL)) {
+
+ printf("Error: ReadFile failed with error 0x%x\n", GetLastError());
+
+ ret = FALSE;
+ goto End;
+
+ }
+
+ //
+ // Now compare the readBuf and writeBuf content. They should be the same.
+ //
+
+ if(bytesRead != bytesWritten) {
+ printf("bytesRead(%d) != bytesWritten(%d)\n", bytesRead, bytesWritten);
+ ret = FALSE;
+ goto End;
+ }
+
+ if(memcmp(readBuf, writeBuf, bufLength) != 0){
+ printf("Error: ReadBuf and WriteBuf contents are not the same\n");
+ ret = FALSE;
+ goto End;
+ }
+
+ ret = TRUE;
+
+End:
+
+ if(readBuf){
+ HeapFree (GetProcessHeap(), 0, readBuf);
+ }
+
+ if(writeBuf){
+ HeapFree (GetProcessHeap(), 0, writeBuf);
+ }
+
+ return ret;
+
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/ioctl/localwpp.ini b/tests/projects/windows/driver/kmdf/ioctl/localwpp.ini new file mode 100644 index 000000000..c290070a7 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/localwpp.ini @@ -0,0 +1,17 @@ +// +// This defines how to log a len/buffer pair. +// This function should be in trace.h +// + +DEFINE_CPLX_TYPE(HEXDUMP, WPP_LOGHEXDUMP, xstr_t, ItemHEXDump,"s", _HEX_, 0,2); + +// DEFINE_CPLX_TYPE( +// name, // i.e. HEXDUMP // %!HEXDUMP! +// macro, // i.e. WPP_LOGHEXDUMP // Marshalling macro, defined in trace.h +// structure, // i.e. xstr_t // Argument type (structure to be created by above macro) +// item type, // i.e. ItemHEXDump // MOF type that TracePrt can understand +// format specifier, // i.e. "s" // a format specifier that TracePrt can understand +// ???? // i.e. _HEX_ // Type signature (becomes a part of function name) +// ???? // i.e. 0 // Weight (0 is variable data length) +// ???? // i.e. 2 // Slots used by this entry (optional, 1 default) +// ) diff --git a/tests/projects/windows/driver/kmdf/ioctl/public.h b/tests/projects/windows/driver/kmdf/ioctl/public.h new file mode 100644 index 000000000..02e24be2e --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/public.h @@ -0,0 +1,53 @@ +/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+
+Module Name:
+
+ PUBLIC.H
+
+Abstract:
+
+
+ Defines the IOCTL codes that will be used by this driver. The IOCTL code
+ contains a command identifier, plus other information about the device,
+ the type of access with which the file must have been opened,
+ and the type of buffering.
+
+Environment:
+
+ Kernel mode only.
+
+--*/
+
+//
+// Device type -- in the "User Defined" range."
+//
+#define FILEIO_TYPE 40001
+//
+// The IOCTL function codes from 0x800 to 0xFFF are for customer use.
+//
+#define IOCTL_NONPNP_METHOD_IN_DIRECT \
+ CTL_CODE( FILEIO_TYPE, 0x900, METHOD_IN_DIRECT, FILE_ANY_ACCESS )
+
+#define IOCTL_NONPNP_METHOD_OUT_DIRECT \
+ CTL_CODE( FILEIO_TYPE, 0x901, METHOD_OUT_DIRECT , FILE_ANY_ACCESS )
+
+#define IOCTL_NONPNP_METHOD_BUFFERED \
+ CTL_CODE( FILEIO_TYPE, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS )
+
+#define IOCTL_NONPNP_METHOD_NEITHER \
+ CTL_CODE( FILEIO_TYPE, 0x903, METHOD_NEITHER , FILE_ANY_ACCESS )
+
+
+#define DRIVER_FUNC_INSTALL 0x01
+#define DRIVER_FUNC_REMOVE 0x02
+
+#define DRIVER_NAME "NONPNP"
+#define DEVICE_NAME "\\\\.\\NONPNP\\nonpnpsamp.log"
diff --git a/tests/projects/windows/driver/kmdf/ioctl/xmake.lua b/tests/projects/windows/driver/kmdf/ioctl/xmake.lua new file mode 100644 index 000000000..9cbaf0b5f --- /dev/null +++ b/tests/projects/windows/driver/kmdf/ioctl/xmake.lua @@ -0,0 +1,15 @@ +add_rules("mode.debug", "mode.release") + +add_includedirs(".") + +target("nonpnp") + add_rules("wdk.env.kmdf", "wdk.driver") + add_values("wdk.tracewpp.flags", "-func:TraceEvents(LEVEL,FLAGS,MSG,...)", "-func:Hexdump((LEVEL,FLAGS,MSG,...))") + add_files("driver/*.c", {rule = "wdk.tracewpp"}) + add_files("driver/*.rc") + +target("app") + add_rules("wdk.env.kmdf", "wdk.binary") + add_files("exe/*.c") + add_files("exe/*.inf") + diff --git a/tests/projects/windows/driver/kmdf/serial/error.c b/tests/projects/windows/driver/kmdf/serial/error.c new file mode 100644 index 000000000..904e54b05 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/error.c @@ -0,0 +1,67 @@ +/*++
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ error.c
+
+Abstract:
+
+ This module contains the code that is very specific to error
+ operations in the serial driver
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "error.tmh"
+#endif
+
+
+VOID
+SerialCommError(
+ IN WDFDPC Dpc
+ )
+/*++
+
+Routine Description:
+
+ This routine is invoked at dpc level to in response to
+ a comm error. All comm errors complete all read and writes
+
+Arguments:
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT,
+ ">SerialCommError(%p)\n", Extension);
+
+ SerialFlushRequests(
+ Extension->WriteQueue,
+ &Extension->CurrentWriteRequest
+ );
+
+ SerialFlushRequests(
+ Extension->ReadQueue,
+ &Extension->CurrentReadRequest
+ );
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT,
+ "<SerialCommError\n");
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/flush.c b/tests/projects/windows/driver/kmdf/serial/flush.c new file mode 100644 index 000000000..695a60a26 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/flush.c @@ -0,0 +1,86 @@ +/*++
+
+Copyright (c) 1991, 1992, 1993 Microsoft Corporation
+
+Module Name:
+
+ flush.c
+
+Abstract:
+
+ This module contains the code that is very specific to flush
+ operations in the serial driver
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "flush.tmh"
+#endif
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, SerialFlush)
+#endif
+
+#pragma warning(push)
+#pragma warning(disable:28118) // this callback will run at IRQL=PASSIVE_LEVEL
+_Use_decl_annotations_
+NTSTATUS
+SerialFlush(
+ WDFDEVICE Device,
+ PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the dispatch routine for flush. Flushing works by placing
+ this request in the write queue. When this request reaches the
+ front of the write queue we simply complete it since this implies
+ that all previous writes have completed.
+
+Arguments:
+
+ DeviceObject - Pointer to the device object for this device
+
+ Irp - Pointer to the IRP for the current request
+
+Return Value:
+
+ Could return status success, cancelled, or pending.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension;
+
+ extension = SerialGetDeviceExtension(Device);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialFlush(%p, %p)\n", Device, Irp);
+
+ PAGED_CODE();
+
+ WdfIoQueueStopSynchronously(extension->WriteQueue);
+ //
+ // Flush is done - restart the queue
+ //
+ WdfIoQueueStart(extension->WriteQueue);
+
+ Irp->IoStatus.Information = 0L;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Irp, IO_NO_INCREMENT);
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialFlush\n");
+
+ return STATUS_SUCCESS;
+ }
+#pragma warning(pop) // enable 28118 again
+
diff --git a/tests/projects/windows/driver/kmdf/serial/immediat.c b/tests/projects/windows/driver/kmdf/serial/immediat.c new file mode 100644 index 000000000..9fd7683af --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/immediat.c @@ -0,0 +1,458 @@ +/*++
+
+Copyright (c) 1991, 1992, 1993 - 1997 Microsoft Corporation
+
+Module Name:
+
+ immediat.c
+
+Abstract:
+
+ This module contains the code that is very specific to transmit
+ immediate character operations in the serial driver
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "immediat.tmh"
+#endif
+
+
+VOID
+SerialGetNextImmediate(
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ IN WDFREQUEST *NewRequest,
+ IN BOOLEAN CompleteCurrent,
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+EVT_WDF_REQUEST_CANCEL SerialCancelImmediate;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGiveImmediateToIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabImmediateFromIsr;
+
+
+VOID
+SerialStartImmediate(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will calculate the timeouts needed for the
+ write. It will then hand the request off to the isr. It
+ will need to be careful incase the request has been canceled.
+
+Arguments:
+
+ Extension - A pointer to the serial device extension.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LARGE_INTEGER TotalTime = {0};
+ BOOLEAN UseATimer;
+ SERIAL_TIMEOUTS Timeouts;
+ PREQUEST_CONTEXT reqContext;
+
+ reqContext = SerialGetRequestContext(Extension->CurrentImmediateRequest);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, ">SerialStartImmediate(%p)\n",
+ Extension);
+
+ UseATimer = FALSE;
+ reqContext->Status = STATUS_PENDING;
+
+ //
+ // Calculate the timeout value needed for the
+ // request. Note that the values stored in the
+ // timeout record are in milliseconds. Note that
+ // if the timeout values are zero then we won't start
+ // the timer.
+ //
+
+ Timeouts = Extension->Timeouts;
+
+ if (Timeouts.WriteTotalTimeoutConstant ||
+ Timeouts.WriteTotalTimeoutMultiplier) {
+
+ UseATimer = TRUE;
+
+ //
+ // We have some timer values to calculate.
+ //
+
+ TotalTime.QuadPart
+ = (LONGLONG)((ULONG)Timeouts.WriteTotalTimeoutMultiplier);
+
+ TotalTime.QuadPart += Timeouts.WriteTotalTimeoutConstant;
+
+ TotalTime.QuadPart *= -10000;
+
+ }
+
+ //
+ // As the request might be going to the isr, this is a good time
+ // to initialize the reference count.
+ //
+
+ SERIAL_INIT_REFERENCE(reqContext);
+
+ //
+ // We give the request to to the isr to write out.
+ // We set a cancel routine that knows how to
+ // grab the current write away from the isr.
+ //
+ SerialSetCancelRoutine(Extension->CurrentImmediateRequest,
+ SerialCancelImmediate);
+
+ if (UseATimer) {
+ BOOLEAN result;
+
+ result = SerialSetTimer(
+ Extension->ImmediateTotalTimer,
+ TotalTime
+ );
+
+ if(result == FALSE) {
+ //
+ // Since the timer knows about the request we increment
+ // the reference count.
+ //
+
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_TOTAL_TIMER
+ );
+ }
+ }
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGiveImmediateToIsr,
+ Extension
+ );
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "<SerialStartImmediate\n");
+
+}
+
+VOID
+SerialCompleteImmediate(
+ IN WDFDPC Dpc
+ )
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, ">SerialCompleteImmediate(%p)\n",
+ Extension);
+
+ SerialTryToCompleteCurrent(
+ Extension,
+ NULL,
+ STATUS_SUCCESS,
+ &Extension->CurrentImmediateRequest,
+ NULL,
+ NULL,
+ Extension->ImmediateTotalTimer,
+ NULL,
+ SerialGetNextImmediate,
+ SERIAL_REF_ISR
+ );
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "<SerialCompleteImmediate\n");
+
+}
+
+VOID
+SerialTimeoutImmediate(
+ IN WDFTIMER Timer
+ )
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, ">SerialTimeoutImmediate(%p)\n",
+ Extension);
+
+ SerialTryToCompleteCurrent(
+ Extension,
+ SerialGrabImmediateFromIsr,
+ STATUS_TIMEOUT,
+ &Extension->CurrentImmediateRequest,
+ NULL,
+ NULL,
+ Extension->ImmediateTotalTimer,
+ NULL,
+ SerialGetNextImmediate,
+ SERIAL_REF_TOTAL_TIMER
+ );
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "<SerialTimeoutImmediate\n");
+}
+
+VOID
+SerialGetNextImmediate(
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ IN WDFREQUEST *NewRequest,
+ IN BOOLEAN CompleteCurrent,
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to complete the current immediate
+ request. Even though the current immediate will always
+ be completed and there is no queue associated with it,
+ we use this routine so that we can try to satisfy
+ a wait for transmit queue empty event.
+
+Arguments:
+
+ CurrentOpRequest - Pointer to the pointer that points to the
+ current write request. This should point
+ to CurrentImmediateRequest.
+
+ QueueToProcess - Always NULL.
+
+ NewRequest - Always NULL on exit to this routine.
+
+ CompleteCurrent - Should always be true for this routine.
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ WDFREQUEST oldRequest = *CurrentOpRequest;
+ PREQUEST_CONTEXT reqContext = SerialGetRequestContext(oldRequest);
+
+ UNREFERENCED_PARAMETER(QueueToProcess);
+ UNREFERENCED_PARAMETER(CompleteCurrent);
+
+
+ ASSERT(Extension->TotalCharsQueued >= 1);
+ Extension->TotalCharsQueued--;
+
+ *CurrentOpRequest = NULL;
+ *NewRequest = NULL;
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialProcessEmptyTransmit,
+ Extension
+ );
+
+ SerialCompleteRequest(oldRequest, reqContext->Status, reqContext->Information);
+}
+
+VOID
+SerialCancelImmediate(
+ IN WDFREQUEST Request
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to cancel a request that is waiting on
+ a comm event.
+
+Arguments:
+
+ Request - Pointer to the WDFREQUEST for the current request
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+ WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request));
+
+ UNREFERENCED_PARAMETER(Request);
+
+ Extension = SerialGetDeviceExtension(device);
+
+ SerialTryToCompleteCurrent(
+ Extension,
+ SerialGrabImmediateFromIsr,
+ STATUS_CANCELLED,
+ &Extension->CurrentImmediateRequest,
+ NULL,
+ NULL,
+ Extension->ImmediateTotalTimer,
+ NULL,
+ SerialGetNextImmediate,
+ SERIAL_REF_CANCEL
+ );
+
+}
+
+BOOLEAN
+SerialGiveImmediateToIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ Try to start off the write by slipping it in behind
+ a transmit immediate char, or if that isn't available
+ and the transmit holding register is empty, "tickle"
+ the UART into interrupting with a transmit buffer
+ empty.
+
+ NOTE: This routine is called by WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that it is called with the
+ cancel spin lock held.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentImmediateRequest);
+
+ Extension->TransmitImmediate = TRUE;
+ Extension->ImmediateChar = *((UCHAR *) (reqContext->SystemBuffer));
+
+ //
+ // The isr now has a reference to the request.
+ //
+
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ //
+ // Check first to see if a write is going on. If
+ // there is then we'll just slip in during the write.
+ //
+
+ if (!Extension->WriteLength) {
+
+ //
+ // If there is no normal write transmitting then we
+ // will "re-enable" the transmit holding register empty
+ // interrupt. The 8250 family of devices will always
+ // signal a transmit holding register empty interrupt
+ // *ANY* time this bit is set to one. By doing things
+ // this way we can simply use the normal interrupt code
+ // to start off this write.
+ //
+ // We've been keeping track of whether the transmit holding
+ // register is empty so it we only need to do this
+ // if the register is empty.
+ //
+
+ if (Extension->HoldingEmpty) {
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+
+ }
+
+ }
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialGrabImmediateFromIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+
+ This routine is used to grab the current request, which could be timing
+ out or canceling, from the ISR
+
+ NOTE: This routine is being called from WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that the cancel spin lock is held
+ when this routine is called.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always false.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentImmediateRequest);
+
+ if (Extension->TransmitImmediate) {
+
+ Extension->TransmitImmediate = FALSE;
+
+ //
+ // Since the isr no longer references this request, we can
+ // decrement it's reference count.
+ //
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ }
+
+ return FALSE;
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/initunlo.c b/tests/projects/windows/driver/kmdf/serial/initunlo.c new file mode 100644 index 000000000..07e7fb003 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/initunlo.c @@ -0,0 +1,197 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ initunlo.c
+
+Abstract:
+
+ This module contains the code that is very specific to initialization
+ and unload operations in the serial driver
+
+ WDF Version of serial sample doesn't support:
+ 1) Multiport Serial devices.
+ 2) Enumeration of Non PNP serial devices that are not detected by BIOS
+ (IO address range 0x2F0-0x2F7 using IRQ 9)
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "initunlo.tmh"
+#endif
+
+static const PHYSICAL_ADDRESS SerialPhysicalZero = {0};
+
+//
+// We use this to query into the registry as to whether we
+// should break at driver entry.
+//
+
+SERIAL_FIRMWARE_DATA driverDefaults;
+
+//
+// This is exported from the kernel. It is used to point
+// to the address that the kernel debugger is using.
+//
+extern PUCHAR *KdComPortInUse;
+//
+// INIT - only needed during init and then can be disposed
+// PAGESRP0 - always paged / never locked
+// PAGESER - must be locked when a device is open, else paged
+//
+//
+// INIT is used for DriverEntry() specific code
+//
+// PAGESRP0 is used for code that is not often called and has nothing
+// to do with I/O performance. An example, passive-level PNP
+// support functions
+//
+// PAGESER is used for code that needs to be locked after an open for both
+// performance and IRQL reasons.
+//
+
+ULONG DebugLevel = TRACE_LEVEL_INFORMATION;
+ULONG DebugFlag = 0xf;//0x46;//0x4FF; //0x00000006;
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(INIT, DriverEntry)
+#pragma alloc_text(PAGE, SerialEvtDriverContextCleanup)
+#endif
+
+
+
+NTSTATUS
+DriverEntry(
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+
+ The entry point that the system point calls to initialize
+ any driver.
+
+Arguments:
+
+ DriverObject - Just what it says, really of little use
+ to the driver itself, it is something that the IO system
+ cares more about.
+
+ PathToRegistry - points to the entry for this driver
+ in the current control set of the registry.
+
+Return Value:
+
+ Always STATUS_SUCCESS
+
+--*/
+
+{
+ WDF_DRIVER_CONFIG config;
+ WDFDRIVER hDriver;
+ NTSTATUS status;
+ WDF_OBJECT_ATTRIBUTES attributes;
+
+ //
+ // Initialize WPP Tracing
+ //
+ WPP_INIT_TRACING( DriverObject, RegistryPath );
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT,
+ "Serial Sample (WDF Version)\n");
+ //
+ // Register a cleanup callback so that we can call WPP_CLEANUP when
+ // the framework driver object is deleted during driver unload.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.EvtCleanupCallback = SerialEvtDriverContextCleanup;
+
+ WDF_DRIVER_CONFIG_INIT(&config, SerialEvtDeviceAdd);
+
+ status = WdfDriverCreate(DriverObject,
+ RegistryPath,
+ &attributes,
+ &config,
+ &hDriver);
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_INIT,
+ "WdfDriverCreate failed with status 0x%x\n",
+ status);
+ //
+ // Cleanup tracing here because DriverContextCleanup will not be called
+ // as we have failed to create WDFDRIVER object itself.
+ // Please note that if your return failure from DriverEntry after the
+ // WDFDRIVER object is created successfully, you don't have to
+ // call WPP cleanup because in those cases DriverContextCleanup
+ // will be executed when the framework deletes the DriverObject.
+ //
+ WPP_CLEANUP(DriverObject);
+ return status;
+ }
+
+ //
+ // Call to find out default values to use for all the devices that the
+ // driver controls, including whether or not to break on entry.
+ //
+
+ SerialGetConfigDefaults(&driverDefaults, hDriver);
+
+ //
+ // Break on entry if requested via registry
+ //
+ if (driverDefaults.ShouldBreakOnEntry) {
+ DbgBreakPoint();
+ }
+
+
+ return status;
+}
+
+
+_Use_decl_annotations_
+VOID
+SerialEvtDriverContextCleanup(
+ WDFOBJECT Driver
+ )
+/*++
+Routine Description:
+
+ Free all the resources allocated in DriverEntry.
+
+Arguments:
+
+ Driver - handle to a WDF Driver object.
+
+Return Value:
+
+ VOID.
+
+--*/
+{
+ UNREFERENCED_PARAMETER(Driver);
+
+ PAGED_CODE ();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT,
+ "--> SerialEvtDriverContextCleanup\n");
+
+ //
+ // Stop WPP Tracing
+ //
+ WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT,
+ "<-- SerialEvtDriverContextCleanup\n");
+
+}
+
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/ioctl.c b/tests/projects/windows/driver/kmdf/serial/ioctl.c new file mode 100644 index 000000000..07ff2b147 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/ioctl.c @@ -0,0 +1,2187 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ ioctl.c
+
+Abstract:
+
+ This module contains the ioctl dispatcher as well as a couple
+ of routines that are generally just called in response to
+ ioctl calls.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "ioctl.tmh"
+#endif
+
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetModemUpdate;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetCommStatus;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetEscapeChar;
+
+PCHAR
+SerialGetIoctlName(
+ IN ULONG IoControlCode
+ )
+/*++
+
+Routine Description:
+ SerialGetIoctlName returns the name of the ioctl
+
+--*/
+{
+ switch (IoControlCode)
+ {
+ case IOCTL_SERIAL_SET_BAUD_RATE : return "IOCTL_SERIAL_SET_BAUD_RATE";
+ case IOCTL_SERIAL_GET_BAUD_RATE: return "IOCTL_SERIAL_GET_BAUD_RATE";
+ case IOCTL_SERIAL_GET_MODEM_CONTROL: return "IOCTL_SERIAL_GET_MODEM_CONTROL";
+ case IOCTL_SERIAL_SET_MODEM_CONTROL: return "IOCTL_SERIAL_SET_MODEM_CONTROL";
+ case IOCTL_SERIAL_SET_FIFO_CONTROL: return "IOCTL_SERIAL_SET_FIFO_CONTROL";
+ case IOCTL_SERIAL_SET_LINE_CONTROL: return "IOCTL_SERIAL_SET_LINE_CONTROL";
+ case IOCTL_SERIAL_GET_LINE_CONTROL: return "IOCTL_SERIAL_GET_LINE_CONTROL";
+ case IOCTL_SERIAL_SET_TIMEOUTS: return "IOCTL_SERIAL_SET_TIMEOUTS";
+ case IOCTL_SERIAL_GET_TIMEOUTS: return "IOCTL_SERIAL_GET_TIMEOUTS";
+ case IOCTL_SERIAL_SET_CHARS: return "IOCTL_SERIAL_SET_CHARS";
+ case IOCTL_SERIAL_GET_CHARS: return "IOCTL_SERIAL_GET_CHARS";
+ case IOCTL_SERIAL_SET_DTR: return "IOCTL_SERIAL_SET_DTR";
+ case IOCTL_SERIAL_CLR_DTR: return "IOCTL_SERIAL_SET_DTR";
+ case IOCTL_SERIAL_RESET_DEVICE: return "IOCTL_SERIAL_RESET_DEVICE";
+ case IOCTL_SERIAL_SET_RTS: return "IOCTL_SERIAL_SET_RTS";
+ case IOCTL_SERIAL_CLR_RTS: return "IOCTL_SERIAL_CLR_RTS";
+ case IOCTL_SERIAL_SET_XOFF: return "IOCTL_SERIAL_SET_XOFF";
+ case IOCTL_SERIAL_SET_XON: return "IOCTL_SERIAL_SET_XON";
+ case IOCTL_SERIAL_SET_BREAK_ON: return "IOCTL_SERIAL_SET_BREAK_ON";
+ case IOCTL_SERIAL_SET_BREAK_OFF: return "IOCTL_SERIAL_SET_BREAK_OFF";
+ case IOCTL_SERIAL_SET_QUEUE_SIZE: return "IOCTL_SERIAL_SET_QUEUE_SIZE";
+ case IOCTL_SERIAL_GET_WAIT_MASK: return "IOCTL_SERIAL_GET_WAIT_MASK";
+ case IOCTL_SERIAL_SET_WAIT_MASK: return "IOCTL_SERIAL_SET_WAIT_MASK";
+ case IOCTL_SERIAL_WAIT_ON_MASK: return "IOCTL_SERIAL_WAIT_ON_MASK";
+ case IOCTL_SERIAL_IMMEDIATE_CHAR: return "IOCTL_SERIAL_IMMEDIATE_CHAR";
+ case IOCTL_SERIAL_PURGE: return "IOCTL_SERIAL_PURGE";
+ case IOCTL_SERIAL_GET_HANDFLOW: return "IOCTL_SERIAL_GET_HANDFLOW";
+ case IOCTL_SERIAL_SET_HANDFLOW: return "IOCTL_SERIAL_SET_HANDFLOW";
+ case IOCTL_SERIAL_GET_MODEMSTATUS: return "IOCTL_SERIAL_GET_MODEMSTATUS";
+ case IOCTL_SERIAL_GET_DTRRTS: return "IOCTL_SERIAL_GET_DTRRTS";
+ case IOCTL_SERIAL_GET_COMMSTATUS: return "IOCTL_SERIAL_GET_COMMSTATUS";
+ case IOCTL_SERIAL_GET_PROPERTIES: return "IOCTL_SERIAL_GET_PROPERTIES";
+ case IOCTL_SERIAL_XOFF_COUNTER: return "IOCTL_SERIAL_XOFF_COUNTER";
+ case IOCTL_SERIAL_LSRMST_INSERT: return "IOCTL_SERIAL_LSRMST_INSERT";
+ case IOCTL_SERIAL_CONFIG_SIZE: return "IOCTL_SERIAL_CONFIG_SIZE";
+ case IOCTL_SERIAL_GET_STATS: return "IOCTL_SERIAL_GET_STATS";
+ case IOCTL_SERIAL_CLEAR_STATS: return "IOCTL_SERIAL_CLEAR_STATS";
+ default: return "UnKnown ioctl";
+ }
+}
+
+
+
+BOOLEAN
+SerialGetStats(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ In sync with the interrpt service routine (which sets the perf stats)
+ return the perf stats to the caller.
+
+
+Arguments:
+
+ Context - Pointer to a the request.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PREQUEST_CONTEXT reqContext = (PREQUEST_CONTEXT)Context;
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(WdfInterruptGetDevice(Interrupt));
+ PSERIALPERF_STATS sp = reqContext->SystemBuffer;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ *sp = extension->PerfStats;
+ return FALSE;
+
+}
+
+
+BOOLEAN
+SerialClearStats(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ In sync with the interrpt service routine (which sets the perf stats)
+ clear the perf stats.
+
+
+Arguments:
+
+ Context - Pointer to a the extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ RtlZeroMemory(
+ &((PSERIAL_DEVICE_EXTENSION)Context)->PerfStats,
+ sizeof(SERIALPERF_STATS)
+ );
+
+ RtlZeroMemory(&((PSERIAL_DEVICE_EXTENSION)Context)->WmiPerfData,
+ sizeof(SERIAL_WMI_PERF_DATA));
+
+ return FALSE;
+}
+
+
+
+BOOLEAN
+SerialSetChars(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to set the special characters for the
+ driver.
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and a pointer to a special characters
+ structure.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ ((PSERIAL_IOCTL_SYNC)Context)->Extension->SpecialChars =
+ *((PSERIAL_CHARS)(((PSERIAL_IOCTL_SYNC)Context)->Data));
+
+ return FALSE;
+}
+
+
+BOOLEAN
+SerialSetBaud(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to set the baud rate of the device.
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and what should be the current
+ baud rate.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension;
+ USHORT Appropriate = PtrToUshort(((PSERIAL_IOCTL_SYNC)Context)->Data);
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ WRITE_DIVISOR_LATCH(
+ Extension,
+ Extension->Controller,
+ Appropriate
+ );
+
+ return FALSE;
+}
+
+
+BOOLEAN
+SerialSetLineControl(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to set the buad rate of the device.
+
+Arguments:
+
+ Context - Pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ WRITE_LINE_CONTROL(Extension,
+ Extension->Controller,
+ Extension->LineControl
+ );
+
+ return FALSE;
+}
+
+
+BOOLEAN
+SerialGetModemUpdate(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is simply used to call the interrupt level routine
+ that handles modem status update.
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and a pointer to a ulong.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension;
+ ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data);
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ *Result = SerialHandleModemUpdate(
+ Extension,
+ FALSE
+ );
+
+ return FALSE;
+}
+
+
+
+BOOLEAN
+SerialSetMCRContents(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ This routine is simply used to set the contents of the MCR
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and a pointer to a ulong.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension;
+ ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data);
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ //
+ // This is severe casting abuse!!!
+ //
+ WRITE_MODEM_CONTROL(Extension, Extension->Controller, (UCHAR)PtrToUlong(Result));
+
+ return FALSE;
+}
+
+
+
+
+BOOLEAN
+SerialGetMCRContents(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is simply used to get the contents of the MCR
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and a pointer to a ulong.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension;
+ ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data);
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ *Result = READ_MODEM_CONTROL(Extension, Extension->Controller);
+
+ return FALSE;
+}
+
+
+
+
+BOOLEAN
+SerialSetFCRContents(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ This routine is simply used to set the contents of the FCR
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and a pointer to a ulong.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension;
+ ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data);
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ //
+ // This is severe casting abuse!!!
+ //
+ WRITE_FIFO_CONTROL(Extension, Extension->Controller, (UCHAR)*Result);
+
+ return FALSE;
+}
+
+
+
+BOOLEAN
+SerialGetCommStatus(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This is used to get the current state of the serial driver.
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and a pointer to a serial status
+ record.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension;
+ PSERIAL_STATUS Stat = ((PSERIAL_IOCTL_SYNC)Context)->Data;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ Stat->Errors = Extension->ErrorWord;
+ Extension->ErrorWord = 0;
+
+ //
+ // Eof isn't supported in binary mode
+ //
+ Stat->EofReceived = FALSE;
+
+ Stat->AmountInInQueue = Extension->CharsInInterruptBuffer;
+
+ Stat->AmountInOutQueue = Extension->TotalCharsQueued;
+
+ if (Extension->WriteLength) {
+
+ //
+ // By definition if we have a writelength the we have
+ // a current write request.
+ //
+ PREQUEST_CONTEXT reqContext = NULL;
+
+ ASSERT(Extension->CurrentWriteRequest);
+ ASSERT(Stat->AmountInOutQueue >= Extension->WriteLength);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest);
+ Stat->AmountInOutQueue -= reqContext->Length - (Extension->WriteLength);
+
+ }
+
+ Stat->WaitForImmediate = Extension->TransmitImmediate;
+
+ Stat->HoldReasons = 0;
+ if (Extension->TXHolding) {
+
+ if (Extension->TXHolding & SERIAL_TX_CTS) {
+
+ Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_CTS;
+
+ }
+
+ if (Extension->TXHolding & SERIAL_TX_DSR) {
+
+ Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_DSR;
+
+ }
+
+ if (Extension->TXHolding & SERIAL_TX_DCD) {
+
+ Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_DCD;
+
+ }
+
+ if (Extension->TXHolding & SERIAL_TX_XOFF) {
+
+ Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_XON;
+
+ }
+
+ if (Extension->TXHolding & SERIAL_TX_BREAK) {
+
+ Stat->HoldReasons |= SERIAL_TX_WAITING_ON_BREAK;
+
+ }
+
+ }
+
+ if (Extension->RXHolding & SERIAL_RX_DSR) {
+
+ Stat->HoldReasons |= SERIAL_RX_WAITING_FOR_DSR;
+
+ }
+
+ if (Extension->RXHolding & SERIAL_RX_XOFF) {
+
+ Stat->HoldReasons |= SERIAL_TX_WAITING_XOFF_SENT;
+
+ }
+
+ return FALSE;
+}
+
+
+BOOLEAN
+SerialSetEscapeChar(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This is used to set the character that will be used to escape
+ line status and modem status information when the application
+ has set up that line status and modem status should be passed
+ back in the data stream.
+
+Arguments:
+
+ Context - Pointer to the request that is specify the escape character.
+ Implicitly - An escape character of 0 means no escaping
+ will occur.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PREQUEST_CONTEXT reqContext = (PREQUEST_CONTEXT)Context;
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(WdfInterruptGetDevice(Interrupt));
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ extension->EscapeChar = *(PUCHAR)reqContext->SystemBuffer;
+
+ return FALSE;
+}
+
+VOID
+SerialEvtIoDeviceControl(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t OutputBufferLength,
+ IN size_t InputBufferLength,
+ IN ULONG IoControlCode
+ )
+
+/*++
+
+Routine Description:
+
+ This routine provides the initial processing for all of the
+ Ioctrls for the serial device.
+
+Arguments:
+
+ Request - Pointer to the WDFREQUEST for the current request
+
+Return Value:
+
+ The function value is the final status of the call
+
+--*/
+
+{
+ //
+ // The status that gets returned to the caller and
+ // set in the Request.
+ //
+ NTSTATUS Status;
+
+ //
+ // Just what it says. This is the serial specific device
+ // extension of the device object create for the serial driver.
+ //
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ PVOID buffer;
+ PREQUEST_CONTEXT reqContext;
+ size_t bufSize;
+
+ UNREFERENCED_PARAMETER(OutputBufferLength);
+ UNREFERENCED_PARAMETER(InputBufferLength);
+
+ reqContext = SerialGetRequestContext(Request);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "%s for: %p\n",
+ SerialGetIoctlName(IoControlCode), Request);
+
+ Extension = SerialGetDeviceExtension(WdfIoQueueGetDevice(Queue));
+
+ //
+ // We expect to be open so all our pages are locked down. This is, after
+ // all, an IO operation, so the device should be open first.
+ //
+
+ if (Extension->DeviceIsOpened != TRUE) {
+ SerialCompleteRequest(Request, STATUS_INVALID_DEVICE_REQUEST, 0);
+ return;
+ }
+
+
+ if (SerialCompleteIfError(Extension, Request) != STATUS_SUCCESS) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS,
+ "<SerialEvtIoDeviceControl (2) %d\n", STATUS_CANCELLED);
+ return;
+
+ }
+
+ reqContext = SerialGetRequestContext(Request);
+ reqContext->Information = 0;
+ reqContext->Status = STATUS_SUCCESS;
+ reqContext->MajorFunction = IRP_MJ_DEVICE_CONTROL;
+
+
+ Status = STATUS_SUCCESS;
+
+ switch (IoControlCode) {
+
+ case IOCTL_SERIAL_SET_BAUD_RATE : {
+
+ ULONG BaudRate;
+ //
+ // Will hold the value of the appropriate divisor for
+ // the requested baud rate. If the baudrate is invalid
+ // (because the device won't support that baud rate) then
+ // this value is undefined.
+ //
+ // Note: in one sense the concept of a valid baud rate
+ // is cloudy. We could allow the user to request any
+ // baud rate. We could then calculate the divisor needed
+ // for that baud rate. As long as the divisor wasn't less
+ // than one we would be "ok". (The percentage difference
+ // between the "true" divisor and the "rounded" value given
+ // to the hardware might make it unusable, but... ) It would
+ // really be up to the user to "Know" whether the baud rate
+ // is suitable. So much for theory, *We* only support a given
+ // set of baud rates.
+ //
+ SHORT AppropriateDivisor;
+
+ Status = WdfRequestRetrieveInputBuffer (Request, sizeof(SERIAL_BAUD_RATE), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ BaudRate = ((PSERIAL_BAUD_RATE)(buffer))->BaudRate;
+
+
+ //
+ // Get the baud rate from the request. We pass it
+ // to a routine which will set the correct divisor.
+ //
+
+ Status = SerialGetDivisorFromBaud(
+ Extension->ClockRate,
+ BaudRate,
+ &AppropriateDivisor
+ );
+
+
+ if (NT_SUCCESS(Status)) {
+
+ SERIAL_IOCTL_SYNC S;
+
+
+ Extension->CurrentBaud = BaudRate;
+ Extension->WmiCommData.BaudRate = BaudRate;
+
+ S.Extension = Extension;
+ S.Data = (PVOID) (ULONG_PTR) AppropriateDivisor;
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialSetBaud,
+ &S
+ );
+
+ }
+
+ break;
+ }
+
+ case IOCTL_SERIAL_GET_BAUD_RATE: {
+
+ PSERIAL_BAUD_RATE Br;
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_BAUD_RATE), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ Br = (PSERIAL_BAUD_RATE)buffer;
+
+ Br->BaudRate = Extension->CurrentBaud;
+
+ reqContext->Information = sizeof(SERIAL_BAUD_RATE);
+
+ break;
+
+ }
+
+ case IOCTL_SERIAL_GET_MODEM_CONTROL: {
+ SERIAL_IOCTL_SYNC S;
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->Information = sizeof(ULONG);
+
+ S.Extension = Extension;
+ S.Data = buffer;
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGetMCRContents,
+ &S
+ );
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_MODEM_CONTROL: {
+ SERIAL_IOCTL_SYNC S;
+
+ Status = WdfRequestRetrieveInputBuffer (Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ S.Extension = Extension;
+ S.Data = buffer;
+
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialSetMCRContents,
+ &S
+ );
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_FIFO_CONTROL: {
+ SERIAL_IOCTL_SYNC S;
+
+ Status = WdfRequestRetrieveInputBuffer (Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ S.Extension = Extension;
+ S.Data = buffer;
+
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialSetFCRContents,
+ &S
+ );
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_LINE_CONTROL: {
+
+ PSERIAL_LINE_CONTROL Lc;
+ UCHAR LData;
+ UCHAR LStop;
+ UCHAR LParity;
+ UCHAR Mask = 0xff;
+
+ Status = WdfRequestRetrieveInputBuffer (Request, sizeof(SERIAL_LINE_CONTROL), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ //
+ // Points to the line control record in the Request.
+ //
+ Lc = (PSERIAL_LINE_CONTROL)buffer;
+
+ switch (Lc->WordLength) {
+ case 5: {
+
+ LData = SERIAL_5_DATA;
+ Mask = 0x1f;
+ break;
+
+ }
+ case 6: {
+
+ LData = SERIAL_6_DATA;
+ Mask = 0x3f;
+ break;
+
+ }
+ case 7: {
+
+ LData = SERIAL_7_DATA;
+ Mask = 0x7f;
+ break;
+
+ }
+ case 8: {
+
+ LData = SERIAL_8_DATA;
+ break;
+
+ }
+ default: {
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto DoneWithIoctl;
+
+ }
+
+ }
+
+ Extension->WmiCommData.BitsPerByte = Lc->WordLength;
+
+ switch (Lc->Parity) {
+
+ case NO_PARITY: {
+ Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_NONE;
+ LParity = SERIAL_NONE_PARITY;
+ break;
+
+ }
+ case EVEN_PARITY: {
+ Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_EVEN;
+ LParity = SERIAL_EVEN_PARITY;
+ break;
+
+ }
+ case ODD_PARITY: {
+ Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_ODD;
+ LParity = SERIAL_ODD_PARITY;
+ break;
+
+ }
+ case SPACE_PARITY: {
+ Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_SPACE;
+ LParity = SERIAL_SPACE_PARITY;
+ break;
+
+ }
+ case MARK_PARITY: {
+ Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_MARK;
+ LParity = SERIAL_MARK_PARITY;
+ break;
+
+ }
+ default: {
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto DoneWithIoctl;
+ break;
+ }
+
+ }
+
+ switch (Lc->StopBits) {
+
+ case STOP_BIT_1: {
+ Extension->WmiCommData.StopBits = SERIAL_WMI_STOP_1;
+ LStop = SERIAL_1_STOP;
+ break;
+ }
+ case STOP_BITS_1_5: {
+
+ if (LData != SERIAL_5_DATA) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto DoneWithIoctl;
+ }
+ Extension->WmiCommData.StopBits = SERIAL_WMI_STOP_1_5;
+ LStop = SERIAL_1_5_STOP;
+ break;
+
+ }
+ case STOP_BITS_2: {
+
+ if (LData == SERIAL_5_DATA) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto DoneWithIoctl;
+ }
+ Extension->WmiCommData.StopBits = SERIAL_WMI_STOP_2;
+ LStop = SERIAL_2_STOP;
+ break;
+
+ }
+ default: {
+
+ Status = STATUS_INVALID_PARAMETER;
+ goto DoneWithIoctl;
+ }
+
+ }
+
+ Extension->LineControl =
+ (UCHAR)((Extension->LineControl & SERIAL_LCR_BREAK) |
+ (LData | LParity | LStop));
+ Extension->ValidDataMask = Mask;
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialSetLineControl,
+ Extension
+ );
+
+ break;
+ }
+ case IOCTL_SERIAL_GET_LINE_CONTROL: {
+
+ PSERIAL_LINE_CONTROL Lc;
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_LINE_CONTROL), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ Lc = (PSERIAL_LINE_CONTROL)buffer;
+
+ RtlZeroMemory(buffer, OutputBufferLength);
+
+ if ((Extension->LineControl & SERIAL_DATA_MASK) == SERIAL_5_DATA) {
+ Lc->WordLength = 5;
+ } else if ((Extension->LineControl & SERIAL_DATA_MASK)
+ == SERIAL_6_DATA) {
+ Lc->WordLength = 6;
+ } else if ((Extension->LineControl & SERIAL_DATA_MASK)
+ == SERIAL_7_DATA) {
+ Lc->WordLength = 7;
+ } else if ((Extension->LineControl & SERIAL_DATA_MASK)
+ == SERIAL_8_DATA) {
+ Lc->WordLength = 8;
+ }
+
+ if ((Extension->LineControl & SERIAL_PARITY_MASK)
+ == SERIAL_NONE_PARITY) {
+ Lc->Parity = NO_PARITY;
+ } else if ((Extension->LineControl & SERIAL_PARITY_MASK)
+ == SERIAL_ODD_PARITY) {
+ Lc->Parity = ODD_PARITY;
+ } else if ((Extension->LineControl & SERIAL_PARITY_MASK)
+ == SERIAL_EVEN_PARITY) {
+ Lc->Parity = EVEN_PARITY;
+ } else if ((Extension->LineControl & SERIAL_PARITY_MASK)
+ == SERIAL_MARK_PARITY) {
+ Lc->Parity = MARK_PARITY;
+ } else if ((Extension->LineControl & SERIAL_PARITY_MASK)
+ == SERIAL_SPACE_PARITY) {
+ Lc->Parity = SPACE_PARITY;
+ }
+
+ if (Extension->LineControl & SERIAL_2_STOP) {
+ if (Lc->WordLength == 5) {
+ Lc->StopBits = STOP_BITS_1_5;
+ } else {
+ Lc->StopBits = STOP_BITS_2;
+ }
+ } else {
+ Lc->StopBits = STOP_BIT_1;
+ }
+
+ reqContext->Information = sizeof(SERIAL_LINE_CONTROL);
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_TIMEOUTS: {
+
+ PSERIAL_TIMEOUTS NewTimeouts;
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_TIMEOUTS), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ NewTimeouts =(PSERIAL_TIMEOUTS)buffer;
+
+ if ((NewTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (NewTimeouts->ReadTotalTimeoutMultiplier == MAXULONG) &&
+ (NewTimeouts->ReadTotalTimeoutConstant == MAXULONG)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+
+ Extension->Timeouts.ReadIntervalTimeout =
+ NewTimeouts->ReadIntervalTimeout;
+
+ Extension->Timeouts.ReadTotalTimeoutMultiplier =
+ NewTimeouts->ReadTotalTimeoutMultiplier;
+
+ Extension->Timeouts.ReadTotalTimeoutConstant =
+ NewTimeouts->ReadTotalTimeoutConstant;
+
+ Extension->Timeouts.WriteTotalTimeoutMultiplier =
+ NewTimeouts->WriteTotalTimeoutMultiplier;
+
+ Extension->Timeouts.WriteTotalTimeoutConstant =
+ NewTimeouts->WriteTotalTimeoutConstant;
+
+ break;
+ }
+ case IOCTL_SERIAL_GET_TIMEOUTS: {
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_TIMEOUTS), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ *((PSERIAL_TIMEOUTS)buffer) = Extension->Timeouts;
+ reqContext->Information = sizeof(SERIAL_TIMEOUTS);
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_CHARS: {
+
+ SERIAL_IOCTL_SYNC S;
+ PSERIAL_CHARS NewChars;
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_CHARS), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ NewChars = (PSERIAL_CHARS)buffer;
+
+ //
+ // The only thing that can be wrong with the chars
+ // is that the xon and xoff characters are the
+ // same.
+ //
+#if 0
+ if (NewChars->XonChar == NewChars->XoffChar) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+#endif
+
+ //
+ // We acquire the control lock so that only
+ // one request can GET or SET the characters
+ // at a time. The sets could be synchronized
+ // by the interrupt spinlock, but that wouldn't
+ // prevent multiple gets at the same time.
+ //
+
+ S.Extension = Extension;
+ S.Data = NewChars;
+
+ //
+ // Under the protection of the lock, make sure that
+ // the xon and xoff characters aren't the same as
+ // the escape character.
+ //
+
+ if (Extension->EscapeChar) {
+
+ if ((Extension->EscapeChar == NewChars->XonChar) ||
+ (Extension->EscapeChar == NewChars->XoffChar)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ }
+
+ Extension->WmiCommData.XonCharacter = NewChars->XonChar;
+ Extension->WmiCommData.XoffCharacter = NewChars->XoffChar;
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialSetChars,
+ &S
+ );
+
+
+ break;
+
+ }
+ case IOCTL_SERIAL_GET_CHARS: {
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_CHARS), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ *((PSERIAL_CHARS)buffer) = Extension->SpecialChars;
+ reqContext->Information = sizeof(SERIAL_CHARS);
+
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_DTR:
+ case IOCTL_SERIAL_CLR_DTR: {
+
+
+ //
+ // We acquire the lock so that we can check whether
+ // automatic dtr flow control is enabled. If it is
+ // then we return an error since the app is not allowed
+ // to touch this if it is automatic.
+ //
+
+ if ((Extension->HandFlow.ControlHandShake & SERIAL_DTR_MASK)
+ == SERIAL_DTR_HANDSHAKE) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ ((IoControlCode ==
+ IOCTL_SERIAL_SET_DTR)?
+ (SerialSetDTR):(SerialClrDTR)),
+ Extension
+ );
+
+ }
+
+ break;
+ }
+ case IOCTL_SERIAL_RESET_DEVICE: {
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_RTS:
+ case IOCTL_SERIAL_CLR_RTS: {
+
+ //
+ // We acquire the lock so that we can check whether
+ // automatic rts flow control or transmit toggleing
+ // is enabled. If it is then we return an error since
+ // the app is not allowed to touch this if it is automatic
+ // or toggling.
+ //
+
+ if (((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK)
+ == SERIAL_RTS_HANDSHAKE) ||
+ ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK)
+ == SERIAL_TRANSMIT_TOGGLE)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ ((IoControlCode ==
+ IOCTL_SERIAL_SET_RTS)?
+ (SerialSetRTS):(SerialClrRTS)),
+ Extension
+ );
+
+ }
+
+ break;
+
+ }
+ case IOCTL_SERIAL_SET_XOFF: {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialPretendXoff,
+ Extension
+ );
+
+ break;
+
+ }
+ case IOCTL_SERIAL_SET_XON: {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialPretendXon,
+ Extension
+ );
+
+ break;
+
+ }
+ case IOCTL_SERIAL_SET_BREAK_ON: {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialTurnOnBreak,
+ Extension
+ );
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_BREAK_OFF: {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialTurnOffBreak,
+ Extension
+ );
+
+ break;
+ }
+ case IOCTL_SERIAL_SET_QUEUE_SIZE: {
+
+ //
+ // Type ahead buffer is fixed, so we just validate
+ // the the users request is not bigger that our
+ // own internal buffer size.
+ //
+
+ PSERIAL_QUEUE_SIZE Rs;
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_QUEUE_SIZE), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ ASSERT(Extension->InterruptReadBuffer);
+
+ Rs = (PSERIAL_QUEUE_SIZE)buffer;
+
+ reqContext->SystemBuffer = buffer;
+
+ //
+ // We have to allocate the memory for the new
+ // buffer while we're still in the context of the
+ // caller. We don't even try to protect this
+ // with a lock because the value could be stale
+ // as soon as we release the lock - The only time
+ // we will know for sure is when we actually try
+ // to do the resize.
+ //
+
+ if (Rs->InSize <= Extension->BufferSize) {
+
+ Status = STATUS_SUCCESS;
+ break;
+
+ }
+
+ reqContext->Type3InputBuffer =
+ ExAllocatePoolWithQuotaTag(
+ NonPagedPoolNx | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
+ Rs->InSize,
+ POOL_TAG
+ );
+
+ if (!reqContext->Type3InputBuffer) {
+
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+
+ }
+
+ //
+ // Well the data passed was big enough. Do the request.
+ //
+ // There are two reason we place it in the read queue:
+ //
+ // 1) We want to serialize these resize requests so that
+ // they don't contend with each other.
+ //
+ // 2) We want to serialize these requests with reads since
+ // we don't want reads and resizes contending over the
+ // read buffer.
+ //
+
+
+ SerialStartOrQueue(
+ Extension,
+ Request,
+ Extension->ReadQueue,
+ &Extension->CurrentReadRequest,
+ SerialStartRead
+ );
+
+ return;
+ }
+ case IOCTL_SERIAL_GET_WAIT_MASK: {
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ //
+ // Simple scalar read. No reason to acquire a lock.
+ //
+
+ reqContext->Information = sizeof(ULONG);
+
+ *((ULONG *)buffer) = Extension->IsrWaitMask;
+
+ break;
+
+ }
+ case IOCTL_SERIAL_SET_WAIT_MASK: {
+
+ ULONG NewMask;
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "In Ioctl processing for set mask\n");
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ NewMask = *((ULONG *)buffer);
+ reqContext->SystemBuffer = buffer;
+
+ //
+ // Make sure that the mask only contains valid
+ // waitable events.
+ //
+
+ if (NewMask & ~(SERIAL_EV_RXCHAR |
+ SERIAL_EV_RXFLAG |
+ SERIAL_EV_TXEMPTY |
+ SERIAL_EV_CTS |
+ SERIAL_EV_DSR |
+ SERIAL_EV_RLSD |
+ SERIAL_EV_BREAK |
+ SERIAL_EV_ERR |
+ SERIAL_EV_RING |
+ SERIAL_EV_PERR |
+ SERIAL_EV_RX80FULL |
+ SERIAL_EV_EVENT1 |
+ SERIAL_EV_EVENT2)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Unknown mask %x\n", NewMask);
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ //
+ // Either start this request or put it on the
+ // queue.
+ //
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Starting or queuing set mask request %p"
+ "\n", Request);
+
+ SerialStartOrQueue(Extension, Request, Extension->MaskQueue,
+ &Extension->CurrentMaskRequest,
+ SerialStartMask);
+ return;
+
+ }
+ case IOCTL_SERIAL_WAIT_ON_MASK: {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "In Ioctl processing for wait mask\n");
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->SystemBuffer = buffer;
+
+ //
+ // Either start this request or put it on the
+ // queue.
+ //
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Starting or queuing wait mask request"
+ "%p\n", Request);
+
+ SerialStartOrQueue(
+ Extension,
+ Request,
+ Extension->MaskQueue,
+ &Extension->CurrentMaskRequest,
+ SerialStartMask
+ );
+ return;
+ }
+ case IOCTL_SERIAL_IMMEDIATE_CHAR: {
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(UCHAR), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->SystemBuffer = buffer;
+
+ if (Extension->CurrentImmediateRequest) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ //
+ // We can queue the char. We need to set
+ // a cancel routine because flow control could
+ // keep the char from transmitting. Make sure
+ // that the request hasn't already been canceled.
+ //
+
+ Extension->CurrentImmediateRequest = Request;
+ Extension->TotalCharsQueued++;
+ SerialStartImmediate(Extension);
+ return;
+
+ }
+
+ break;
+
+ }
+ case IOCTL_SERIAL_PURGE: {
+
+ ULONG Mask;
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ //
+ // Check to make sure that the mask only has
+ // 0 or the other appropriate values.
+ //
+
+ Mask = *((ULONG *)(buffer));
+
+ if ((!Mask) || (Mask & (~(SERIAL_PURGE_TXABORT |
+ SERIAL_PURGE_RXABORT |
+ SERIAL_PURGE_TXCLEAR |
+ SERIAL_PURGE_RXCLEAR
+ )
+ )
+ )) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ reqContext->SystemBuffer = buffer;
+
+ //
+ // Either start this request or put it on the
+ // queue.
+ //
+
+ SerialStartOrQueue(
+ Extension,
+ Request,
+ Extension->PurgeQueue,
+ &Extension->CurrentPurgeRequest,
+ SerialStartPurge
+ );
+ return;
+ }
+ case IOCTL_SERIAL_GET_HANDFLOW: {
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_HANDFLOW), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->Information = sizeof(SERIAL_HANDFLOW);
+
+ *((PSERIAL_HANDFLOW)buffer) = Extension->HandFlow;
+
+ break;
+
+ }
+ case IOCTL_SERIAL_SET_HANDFLOW: {
+
+ SERIAL_IOCTL_SYNC S;
+ PSERIAL_HANDFLOW HandFlow;
+
+ //
+ // Make sure that the hand shake and control is the
+ // right size.
+ //
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_HANDFLOW), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ HandFlow = (PSERIAL_HANDFLOW)buffer;
+
+ //
+ // Make sure that there are no invalid bits set in
+ // the control and handshake.
+ //
+
+ if (HandFlow->ControlHandShake & SERIAL_CONTROL_INVALID) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ if (HandFlow->FlowReplace & SERIAL_FLOW_INVALID) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ //
+ // Make sure that the app hasn't set an invlid DTR mode.
+ //
+
+ if ((HandFlow->ControlHandShake & SERIAL_DTR_MASK) ==
+ SERIAL_DTR_MASK) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ //
+ // Make sure that haven't set totally invalid xon/xoff
+ // limits.
+ //
+
+ if ((HandFlow->XonLimit < 0) ||
+ ((ULONG)HandFlow->XonLimit > Extension->BufferSize)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ if ((HandFlow->XoffLimit < 0) ||
+ ((ULONG)HandFlow->XoffLimit > Extension->BufferSize)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ S.Extension = Extension;
+ S.Data = HandFlow;
+
+ //
+ // Under the protection of the lock, make sure that
+ // we aren't turning on error replacement when we
+ // are doing line status/modem status insertion.
+ //
+
+ if (Extension->EscapeChar) {
+
+ if (HandFlow->FlowReplace & SERIAL_ERROR_CHAR) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ }
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialSetHandFlow,
+ &S
+ );
+
+ break;
+
+ }
+ case IOCTL_SERIAL_GET_MODEMSTATUS: {
+
+ SERIAL_IOCTL_SYNC S;
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->Information = sizeof(ULONG);
+
+ S.Extension = Extension;
+ S.Data = buffer;
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGetModemUpdate,
+ &S
+ );
+
+ break;
+
+ }
+ case IOCTL_SERIAL_GET_DTRRTS: {
+
+ ULONG ModemControl;
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->Information = sizeof(ULONG);
+ reqContext->Status = STATUS_SUCCESS;
+
+ //
+ // Reading this hardware has no effect on the device.
+ //
+
+ ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller);
+
+ ModemControl &= SERIAL_DTR_STATE | SERIAL_RTS_STATE;
+
+ *(PULONG)buffer = ModemControl;
+
+ break;
+
+ }
+ case IOCTL_SERIAL_GET_COMMSTATUS: {
+
+ SERIAL_IOCTL_SYNC S;
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_STATUS), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->Information = sizeof(SERIAL_STATUS);
+
+ S.Extension = Extension;
+ S.Data = buffer;
+
+ //
+ // Acquire the cancel spin lock so nothing much
+ // changes while were getting the state.
+ //
+
+ //IoAcquireCancelSpinLock(&OldIrql);
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGetCommStatus,
+ &S
+ );
+
+ //IoReleaseCancelSpinLock(OldIrql);
+
+ break;
+
+ }
+ case IOCTL_SERIAL_GET_PROPERTIES: {
+
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_COMMPROP), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ //
+ // No synchronization is required since this information
+ // is "static".
+ //
+
+ SerialGetProperties(
+ Extension,
+ buffer
+ );
+
+ reqContext->Information = sizeof(SERIAL_COMMPROP);
+ reqContext->Status = STATUS_SUCCESS;
+
+ break;
+ }
+ case IOCTL_SERIAL_XOFF_COUNTER: {
+
+ PSERIAL_XOFF_COUNTER Xc;
+
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_XOFF_COUNTER), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ Xc = (PSERIAL_XOFF_COUNTER)buffer;
+
+ if (Xc->Counter <= 0) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+ reqContext->SystemBuffer = buffer;
+
+ //
+ // There is no output, so make that clear now
+ //
+
+ reqContext->Information = 0;
+
+ //
+ // So far so good. Put the request onto the write queue.
+ //
+
+ SerialStartOrQueue(
+ Extension,
+ Request,
+ Extension->WriteQueue,
+ &Extension->CurrentWriteRequest,
+ SerialStartWrite
+ );
+ return;
+
+ }
+ case IOCTL_SERIAL_LSRMST_INSERT: {
+
+ PUCHAR escapeChar;
+ SERIAL_IOCTL_SYNC S;
+
+ //
+ // Make sure we get a byte.
+ //
+ Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(UCHAR), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->SystemBuffer = buffer;
+
+ escapeChar = (PUCHAR)buffer;
+
+ if (*escapeChar) {
+
+ //
+ // We've got some escape work to do. We will make sure that
+ // the character is not the same as the Xon or Xoff character,
+ // or that we are already doing error replacement.
+ //
+
+ if ((*escapeChar == Extension->SpecialChars.XoffChar) ||
+ (*escapeChar == Extension->SpecialChars.XonChar) ||
+ (Extension->HandFlow.FlowReplace & SERIAL_ERROR_CHAR)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ break;
+
+ }
+
+ }
+
+ S.Extension = Extension;
+ S.Data = buffer;
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialSetEscapeChar,
+ reqContext
+ );
+
+ break;
+
+ }
+ case IOCTL_SERIAL_CONFIG_SIZE: {
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->Information = sizeof(ULONG);
+ reqContext->Status = STATUS_SUCCESS;
+
+ *(PULONG)buffer = 0;
+
+ break;
+ }
+ case IOCTL_SERIAL_GET_STATS: {
+
+ Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIALPERF_STATS), &buffer, &bufSize );
+ if( !NT_SUCCESS(Status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status);
+ break;
+ }
+
+ reqContext->SystemBuffer = buffer;
+
+ reqContext->Information = sizeof(SERIALPERF_STATS);
+ reqContext->Status = STATUS_SUCCESS;
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGetStats,
+ reqContext
+ );
+
+ break;
+ }
+ case IOCTL_SERIAL_CLEAR_STATS: {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialClearStats,
+ Extension
+ );
+ break;
+ }
+ default: {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+DoneWithIoctl:;
+
+ reqContext->Status = Status;
+
+ SerialCompleteRequest(Request, Status, reqContext->Information);
+
+ return;
+
+}
+
+
+VOID
+SerialGetProperties(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN PSERIAL_COMMPROP Properties
+ )
+
+/*++
+
+Routine Description:
+
+ This function returns the capabilities of this particular
+ serial device.
+
+Arguments:
+
+ Extension - The serial device extension.
+
+ Properties - The structure used to return the properties
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+
+ RtlZeroMemory(
+ Properties,
+ sizeof(SERIAL_COMMPROP)
+ );
+
+ Properties->PacketLength = sizeof(SERIAL_COMMPROP);
+ Properties->PacketVersion = 2;
+ Properties->ServiceMask = SERIAL_SP_SERIALCOMM;
+ Properties->MaxTxQueue = 0;
+ Properties->MaxRxQueue = 0;
+
+ Properties->MaxBaud = SERIAL_BAUD_USER;
+ Properties->SettableBaud = Extension->SupportedBauds;
+
+ Properties->ProvSubType = SERIAL_SP_RS232;
+ Properties->ProvCapabilities = SERIAL_PCF_DTRDSR |
+ SERIAL_PCF_RTSCTS |
+ SERIAL_PCF_CD |
+ SERIAL_PCF_PARITY_CHECK |
+ SERIAL_PCF_XONXOFF |
+ SERIAL_PCF_SETXCHAR |
+ SERIAL_PCF_TOTALTIMEOUTS |
+ SERIAL_PCF_INTTIMEOUTS;
+ Properties->SettableParams = SERIAL_SP_PARITY |
+ SERIAL_SP_BAUD |
+ SERIAL_SP_DATABITS |
+ SERIAL_SP_STOPBITS |
+ SERIAL_SP_HANDSHAKING |
+ SERIAL_SP_PARITY_CHECK |
+ SERIAL_SP_CARRIER_DETECT;
+
+
+ Properties->SettableData = SERIAL_DATABITS_5 |
+ SERIAL_DATABITS_6 |
+ SERIAL_DATABITS_7 |
+ SERIAL_DATABITS_8;
+ Properties->SettableStopParity = SERIAL_STOPBITS_10 |
+ SERIAL_STOPBITS_15 |
+ SERIAL_STOPBITS_20 |
+ SERIAL_PARITY_NONE |
+ SERIAL_PARITY_ODD |
+ SERIAL_PARITY_EVEN |
+ SERIAL_PARITY_MARK |
+ SERIAL_PARITY_SPACE;
+ Properties->CurrentTxQueue = 0;
+ Properties->CurrentRxQueue = Extension->BufferSize;
+
+}
+
+VOID
+SerialEvtIoInternalDeviceControl(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t OutputBufferLength,
+ IN size_t InputBufferLength,
+ IN ULONG IoControlCode
+)
+/*++
+
+Routine Description:
+
+ This routine provides the initial processing for all of the
+ internal Ioctrls for the serial device.
+
+Arguments:
+
+ PDevObj - Pointer to the device object for this device
+
+ PIrp - Pointer to the WDFREQUEST for the current request
+
+Return Value:
+
+ The function value is the final status of the call
+
+--*/
+
+{
+ NTSTATUS status;
+ PSERIAL_DEVICE_EXTENSION pDevExt = NULL;
+ PVOID buffer;
+ PREQUEST_CONTEXT reqContext;
+ WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings;
+ size_t bufSize;
+
+ UNREFERENCED_PARAMETER(OutputBufferLength);
+ UNREFERENCED_PARAMETER(InputBufferLength);
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "SerialEvtIoInternalDeviceControl for: %p\n", Request);
+
+ pDevExt = SerialGetDeviceExtension(WdfIoQueueGetDevice(Queue));
+
+ if (SerialCompleteIfError(pDevExt, Request) != STATUS_SUCCESS) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "<SerialEvtIoDeviceControl (2) %d\n", STATUS_CANCELLED);
+ return;
+
+ }
+
+ reqContext = SerialGetRequestContext(Request);
+ reqContext->Information = 0;
+ reqContext->Status = STATUS_SUCCESS;
+ reqContext->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
+
+ switch (IoControlCode) {
+
+ case IOCTL_SERIAL_INTERNAL_DO_WAIT_WAKE:
+ //
+ // Init wait-wake policy structure.
+ //
+ WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings);
+ //
+ // Override the default settings from allow user control to do not allow.
+ //
+ wakeSettings.UserControlOfWakeSettings = IdleDoNotAllowUserControl;
+ status = WdfDeviceAssignSxWakeSettings(pDevExt->WdfDevice, &wakeSettings);
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDeviceAssignSxWakeSettings failed %x \n", status);
+ break;
+ }
+
+ pDevExt->IsWakeEnabled = TRUE;
+ status = STATUS_SUCCESS;
+ break;
+
+ case IOCTL_SERIAL_INTERNAL_CANCEL_WAIT_WAKE:
+
+ WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings);
+ //
+ // Override the default settings.
+ //
+ wakeSettings.Enabled = WdfFalse; // Disables wait-wake
+ wakeSettings.UserControlOfWakeSettings = IdleDoNotAllowUserControl;
+ status = WdfDeviceAssignSxWakeSettings(pDevExt->WdfDevice, &wakeSettings);
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDeviceAssignSxWakeSettings failed %x \n", status);
+ break;
+ }
+
+ pDevExt->IsWakeEnabled = FALSE;
+ status = STATUS_SUCCESS;
+ break;
+
+
+ //
+ // Put the serial port in a "filter-driver" appropriate state
+ //
+ // WARNING: This code assumes it is being called by a trusted kernel
+ // entity and no checking is done on the validity of the settings
+ // passed to IOCTL_SERIAL_INTERNAL_RESTORE_SETTINGS
+ //
+ // If validity checking is desired, the regular ioctl's should be used
+ //
+
+ case IOCTL_SERIAL_INTERNAL_BASIC_SETTINGS:
+ case IOCTL_SERIAL_INTERNAL_RESTORE_SETTINGS: {
+
+ SERIAL_BASIC_SETTINGS basic;
+ PSERIAL_BASIC_SETTINGS pBasic;
+ SERIAL_IOCTL_SYNC S;
+
+ if (IoControlCode == IOCTL_SERIAL_INTERNAL_BASIC_SETTINGS) {
+
+
+ //
+ // Check the buffer size
+ //
+ status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_BASIC_SETTINGS), &buffer, &bufSize );
+ if( !NT_SUCCESS(status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", status);
+ break;
+ }
+
+ reqContext->SystemBuffer = buffer;
+
+ //
+ // Everything is 0 -- timeouts and flow control and fifos. If
+ // We add additional features, this zero memory method
+ // may not work.
+ //
+
+ RtlZeroMemory(&basic, sizeof(SERIAL_BASIC_SETTINGS));
+
+ basic.TxFifo = 1;
+ basic.RxFifo = SERIAL_1_BYTE_HIGH_WATER;
+
+ reqContext->Information = sizeof(SERIAL_BASIC_SETTINGS);
+ pBasic = (PSERIAL_BASIC_SETTINGS)buffer;
+
+ //
+ // Save off the old settings
+ //
+
+ RtlCopyMemory(&pBasic->Timeouts, &pDevExt->Timeouts,
+ sizeof(SERIAL_TIMEOUTS));
+
+ RtlCopyMemory(&pBasic->HandFlow, &pDevExt->HandFlow,
+ sizeof(SERIAL_HANDFLOW));
+
+ pBasic->RxFifo = pDevExt->RxFifoTrigger;
+ pBasic->TxFifo = pDevExt->TxFifoAmount;
+
+ //
+ // Point to our new settings
+ //
+
+ pBasic = &basic;
+ } else { // restoring settings
+
+ status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_BASIC_SETTINGS), &buffer, &bufSize );
+ if( !NT_SUCCESS(status) ) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", status);
+ break;
+ }
+
+ pBasic = (PSERIAL_BASIC_SETTINGS)buffer;
+ }
+
+ //
+ // Set the timeouts
+ //
+
+ RtlCopyMemory(&pDevExt->Timeouts, &pBasic->Timeouts,
+ sizeof(SERIAL_TIMEOUTS));
+
+ //
+ // Set flowcontrol
+ //
+
+ S.Extension = pDevExt;
+ S.Data = &pBasic->HandFlow;
+ WdfInterruptSynchronize(pDevExt->WdfInterrupt, SerialSetHandFlow, &S);
+
+ if (pDevExt->FifoPresent) {
+ pDevExt->TxFifoAmount = pBasic->TxFifo;
+ pDevExt->RxFifoTrigger = (UCHAR)pBasic->RxFifo;
+
+ WRITE_FIFO_CONTROL(pDevExt, pDevExt->Controller, (UCHAR)0);
+ READ_RECEIVE_BUFFER(pDevExt, pDevExt->Controller);
+ WRITE_FIFO_CONTROL(pDevExt, pDevExt->Controller,
+ (UCHAR)(SERIAL_FCR_ENABLE | pDevExt->RxFifoTrigger
+ | SERIAL_FCR_RCVR_RESET
+ | SERIAL_FCR_TXMT_RESET));
+ } else {
+ pDevExt->TxFifoAmount = pDevExt->RxFifoTrigger = 0;
+ WRITE_FIFO_CONTROL(pDevExt, pDevExt->Controller, (UCHAR)0);
+ }
+
+
+ break;
+ }
+
+ default:
+ status = STATUS_INVALID_PARAMETER;
+ break;
+
+ }
+
+ reqContext->Status = status;
+
+ SerialCompleteRequest(Request, reqContext->Status, reqContext->Information);
+
+ return;
+}
+
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/isr.c b/tests/projects/windows/driver/kmdf/serial/isr.c new file mode 100644 index 000000000..806164a61 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/isr.c @@ -0,0 +1,1517 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ isr.c
+
+Abstract:
+
+ This module contains the interrupt service routine for the
+ serial driver.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "isr.tmh"
+#endif
+
+
+NTSTATUS
+SerialEvtInterruptEnable(
+ IN WDFINTERRUPT Interrupt,
+ IN WDFDEVICE AssociatedDevice
+ )
+/*++
+
+Routine Description:
+
+ This event is called when the Framework moves the device to D0, and after
+ EvtDeviceD0Entry. The driver should enable its interrupt here.
+
+ This function will be called at the device's assigned interrupt
+ IRQL (DIRQL.)
+
+Arguments:
+
+ Interrupt - Handle to a Framework interrupt object.
+
+ AssociatedDevice - Handle to a Framework device object.
+
+Return Value:
+
+ BOOLEAN - TRUE indicates that the interrupt was successfully enabled.
+
+--*/
+{
+ UNREFERENCED_PARAMETER(Interrupt);
+ UNREFERENCED_PARAMETER(AssociatedDevice);
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "--> SerialEvtInterruptEnable\n");
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "<-- SerialEvtInterruptEnable\n");
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+SerialEvtInterruptDisable(
+ IN WDFINTERRUPT Interrupt,
+ IN WDFDEVICE AssociatedDevice
+ )
+/*++
+
+Routine Description:
+
+ This event is called before the Framework moves the device to D1, D2 or D3
+ and before EvtDeviceD0Exit. The driver should disable its interrupt here.
+
+ This function will be called at the device's assigned interrupt
+ IRQL (DIRQL.)
+
+Arguments:
+
+ Interrupt - Handle to a Framework interrupt object.
+
+ AssociatedDevice - Handle to a Framework device object.
+
+Return Value:
+
+ BOOLEAN - TRUE indicates that the interrupt was successfully disabled.
+
+--*/
+{
+ UNREFERENCED_PARAMETER(Interrupt);
+ UNREFERENCED_PARAMETER(AssociatedDevice);
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "--> SerialEvtInterruptDisable\n");
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "<-- SerialEvtInterruptDisable\n");
+
+ return STATUS_SUCCESS;
+}
+
+BOOLEAN
+SerialISR(
+ IN WDFINTERRUPT Interrupt,
+ IN ULONG MessageID
+ )
+
+/*++
+
+Routine Description:
+
+ This is the interrupt service routine for the serial port driver.
+ It will determine whether the serial port is the source of this
+ interrupt. If it is, then this routine will do the minimum of
+ processing to quiet the interrupt. It will store any information
+ necessary for later processing.
+
+Arguments:
+
+ InterruptObject - Points to the interrupt object declared for this
+ device. We *do not* use this parameter.
+
+
+Return Value:
+
+ This function will return TRUE if the serial port is the source
+ of this interrupt, FALSE otherwise.
+
+--*/
+
+{
+ //
+ // Holds the information specific to handling this device.
+ //
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ //
+ // Holds the contents of the interrupt identification record.
+ // A low bit of zero in this register indicates that there is
+ // an interrupt pending on this device.
+ //
+ UCHAR InterruptIdReg;
+
+ //
+ // Will hold whether we've serviced any interrupt causes in this
+ // routine.
+ //
+ BOOLEAN ServicedAnInterrupt;
+
+ UCHAR tempLSR;
+ PREQUEST_CONTEXT reqContext = NULL;
+
+ UNREFERENCED_PARAMETER(MessageID);
+
+ Extension = SerialGetDeviceExtension(WdfInterruptGetDevice(Interrupt));
+
+ //
+ // Make sure we have an interrupt pending. If we do then
+ // we need to make sure that the device is open. If the
+ // device isn't open or powered down then quiet the device. Note that
+ // if the device isn't opened when we enter this routine
+ // it can't open while we're in it.
+ //
+
+ InterruptIdReg = READ_INTERRUPT_ID_REG(Extension, Extension->Controller);
+
+ if ((InterruptIdReg & SERIAL_IIR_NO_INTERRUPT_PENDING)) {
+
+ ServicedAnInterrupt = FALSE;
+
+ } else if (!Extension->DeviceIsOpened/*
+ || (Extension->PowerState != PowerDeviceD0)*/) {
+
+
+ //
+ // We got an interrupt with the device being closed or when the
+ // device is supposed to be powered down. This
+ // is not unlikely with a serial device. We just quietly
+ // keep servicing the causes until it calms down.
+ //
+
+ ServicedAnInterrupt = TRUE;
+ do {
+
+ InterruptIdReg &= (~SERIAL_IIR_FIFOS_ENABLED);
+ switch (InterruptIdReg) {
+
+ case SERIAL_IIR_RLS: {
+
+ READ_LINE_STATUS(Extension, Extension->Controller);
+
+ break;
+
+ }
+
+ case SERIAL_IIR_RDA:
+ case SERIAL_IIR_CTI: {
+
+ READ_RECEIVE_BUFFER(Extension, Extension->Controller);
+
+ break;
+
+ }
+
+ case SERIAL_IIR_THR: {
+
+ //
+ // Alread clear from reading the iir.
+ //
+ // We want to keep close track of whether
+ // the holding register is empty.
+ //
+
+ Extension->HoldingEmpty = TRUE;
+ break;
+
+ }
+
+ case SERIAL_IIR_MS: {
+
+ READ_MODEM_STATUS(Extension, Extension->Controller);
+ break;
+
+ }
+
+ default: {
+
+ ASSERT(FALSE);
+ break;
+
+ }
+
+ }
+
+ } while (!((InterruptIdReg =
+ READ_INTERRUPT_ID_REG(Extension, Extension->Controller))
+ & SERIAL_IIR_NO_INTERRUPT_PENDING));
+
+ } else {
+
+ ServicedAnInterrupt = TRUE;
+ do {
+
+ //
+ // We only care about bits that can denote an interrupt.
+ //
+
+ InterruptIdReg &= SERIAL_IIR_RLS | SERIAL_IIR_RDA |
+ SERIAL_IIR_CTI | SERIAL_IIR_THR |
+ SERIAL_IIR_MS;
+
+ //
+ // We have an interrupt. We look for interrupt causes
+ // in priority order. The presence of a higher interrupt
+ // will mask out causes of a lower priority. When we service
+ // and quiet a higher priority interrupt we then need to check
+ // the interrupt causes to see if a new interrupt cause is
+ // present.
+ //
+
+ switch (InterruptIdReg) {
+
+ case SERIAL_IIR_RLS: {
+
+ SerialProcessLSR(Extension);
+
+ break;
+
+ }
+
+ case SERIAL_IIR_RDA:
+ case SERIAL_IIR_CTI:
+
+ {
+
+ //
+ // Reading the receive buffer will quiet this interrupt.
+ //
+ // It may also reveal a new interrupt cause.
+ //
+ UCHAR ReceivedChar;
+
+ do {
+
+ ReceivedChar =
+ READ_RECEIVE_BUFFER(Extension, Extension->Controller);
+ Extension->PerfStats.ReceivedCount++;
+ Extension->WmiPerfData.ReceivedCount++;
+
+ ReceivedChar &= Extension->ValidDataMask;
+
+ if (!ReceivedChar &&
+ (Extension->HandFlow.FlowReplace &
+ SERIAL_NULL_STRIPPING)) {
+
+ //
+ // If what we got is a null character
+ // and we're doing null stripping, then
+ // we simply act as if we didn't see it.
+ //
+
+ goto ReceiveDoLineStatus;
+
+ }
+
+ if ((Extension->HandFlow.FlowReplace &
+ SERIAL_AUTO_TRANSMIT) &&
+ ((ReceivedChar ==
+ Extension->SpecialChars.XonChar) ||
+ (ReceivedChar ==
+ Extension->SpecialChars.XoffChar))) {
+
+ //
+ // No matter what happens this character
+ // will never get seen by the app.
+ //
+
+ if (ReceivedChar ==
+ Extension->SpecialChars.XoffChar) {
+
+ Extension->TXHolding |= SERIAL_TX_XOFF;
+
+ if ((Extension->HandFlow.FlowReplace &
+ SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+ }
+
+
+ } else {
+
+ if (Extension->TXHolding & SERIAL_TX_XOFF) {
+
+ //
+ // We got the xon char **AND*** we
+ // were being held up on transmission
+ // by xoff. Clear that we are holding
+ // due to xoff. Transmission will
+ // automatically restart because of
+ // the code outside the main loop that
+ // catches problems chips like the
+ // SMC and the Winbond.
+ //
+
+ Extension->TXHolding &= ~SERIAL_TX_XOFF;
+
+ }
+
+ }
+
+ goto ReceiveDoLineStatus;
+
+ }
+
+ //
+ // Check to see if we should note
+ // the receive character or special
+ // character event.
+ //
+
+ if (Extension->IsrWaitMask) {
+
+ if (Extension->IsrWaitMask &
+ SERIAL_EV_RXCHAR) {
+
+ Extension->HistoryMask |= SERIAL_EV_RXCHAR;
+
+ }
+
+ if ((Extension->IsrWaitMask &
+ SERIAL_EV_RXFLAG) &&
+ (Extension->SpecialChars.EventChar ==
+ ReceivedChar)) {
+
+ Extension->HistoryMask |= SERIAL_EV_RXFLAG;
+
+ }
+
+ if (Extension->IrpMaskLocation &&
+ Extension->HistoryMask) {
+
+ *Extension->IrpMaskLocation =
+ Extension->HistoryMask;
+ Extension->IrpMaskLocation = NULL;
+ Extension->HistoryMask = 0;
+ reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest);
+ reqContext->Information = sizeof(ULONG);
+ SerialInsertQueueDpc(
+ Extension->CommWaitDpc
+ );
+
+ }
+
+ }
+
+ SerialPutChar(
+ Extension,
+ ReceivedChar
+ );
+
+ //
+ // If we're doing line status and modem
+ // status insertion then we need to insert
+ // a zero following the character we just
+ // placed into the buffer to mark that this
+ // was reception of what we are using to
+ // escape.
+ //
+
+ if (Extension->EscapeChar &&
+ (Extension->EscapeChar ==
+ ReceivedChar)) {
+
+ SerialPutChar(
+ Extension,
+ SERIAL_LSRMST_ESCAPE
+ );
+
+ }
+
+
+ReceiveDoLineStatus: ;
+ //
+ // This reads the interrupt ID register and detemines if bits are 0
+ // If either of the reserved bits are 1, we stop servicing interrupts
+ // Since this detection method is not guarenteed this is enabled via
+ // a registry entry "UartDetectRemoval" and intialized on DriverEntry.
+ // This is disabled by default and will only be enabled on Stratus systems
+ // that allow hot replacement of serial cards
+ //
+ if(Extension->UartRemovalDetect)
+ {
+ UCHAR DetectRemoval;
+
+ DetectRemoval = READ_INTERRUPT_ID_REG(Extension, Extension->Controller);
+
+ if(DetectRemoval & SERIAL_IIR_MUST_BE_ZERO)
+ {
+ // break out of this loop and stop processing interrupts
+ break;
+ }
+ }
+
+ if (!((tempLSR = SerialProcessLSR(Extension)) &
+ SERIAL_LSR_DR)) {
+
+ //
+ // No more characters, get out of the
+ // loop.
+ //
+
+ break;
+
+ }
+
+ if ((tempLSR & ~(SERIAL_LSR_THRE | SERIAL_LSR_TEMT |
+ SERIAL_LSR_DR)) &&
+ Extension->EscapeChar) {
+
+ //
+ // An error was indicated and inserted into the
+ // stream, get out of the loop.
+ //
+
+ break;
+ }
+
+ } WHILE (TRUE);
+
+ break;
+
+ }
+
+ case SERIAL_IIR_THR: {
+
+doTrasmitStuff:;
+ Extension->HoldingEmpty = TRUE;
+
+ if (Extension->WriteLength ||
+ Extension->TransmitImmediate ||
+ Extension->SendXoffChar ||
+ Extension->SendXonChar) {
+
+ //
+ // Even though all of the characters being
+ // sent haven't all been sent, this variable
+ // will be checked when the transmit queue is
+ // empty. If it is still true and there is a
+ // wait on the transmit queue being empty then
+ // we know we finished transmitting all characters
+ // following the initiation of the wait since
+ // the code that initiates the wait will set
+ // this variable to false.
+ //
+ // One reason it could be false is that
+ // the writes were cancelled before they
+ // actually started, or that the writes
+ // failed due to timeouts. This variable
+ // basically says a character was written
+ // by the isr at some point following the
+ // initiation of the wait.
+ //
+
+ Extension->EmptiedTransmit = TRUE;
+
+ //
+ // If we have output flow control based on
+ // the modem status lines, then we have to do
+ // all the modem work before we output each
+ // character. (Otherwise we might miss a
+ // status line change.)
+ //
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_OUT_HANDSHAKEMASK) {
+
+ SerialHandleModemUpdate(
+ Extension,
+ TRUE
+ );
+
+ }
+
+ //
+ // We can only send the xon character if
+ // the only reason we are holding is because
+ // of the xoff. (Hardware flow control or
+ // sending break preclude putting a new character
+ // on the wire.)
+ //
+
+ if (Extension->SendXonChar &&
+ !(Extension->TXHolding & ~SERIAL_TX_XOFF)) {
+
+ if ((Extension->HandFlow.FlowReplace &
+ SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ //
+ // We have to raise if we're sending
+ // this character.
+ //
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+
+ WRITE_TRANSMIT_HOLDING(Extension, Extension->Controller,
+ Extension->SpecialChars.XonChar);
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+
+ } else {
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+
+ WRITE_TRANSMIT_HOLDING(Extension,
+ Extension->Controller,
+ Extension->SpecialChars.XonChar);
+ }
+
+
+ Extension->SendXonChar = FALSE;
+ Extension->HoldingEmpty = FALSE;
+
+ //
+ // If we send an xon, by definition we
+ // can't be holding by Xoff.
+ //
+
+ Extension->TXHolding &= ~SERIAL_TX_XOFF;
+
+ //
+ // If we are sending an xon char then
+ // by definition we can't be "holding"
+ // up reception by Xoff.
+ //
+
+ Extension->RXHolding &= ~SERIAL_RX_XOFF;
+
+ } else if (Extension->SendXoffChar &&
+ !Extension->TXHolding) {
+
+ if ((Extension->HandFlow.FlowReplace &
+ SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ //
+ // We have to raise if we're sending
+ // this character.
+ //
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+ WRITE_TRANSMIT_HOLDING(Extension,
+ Extension->Controller,
+ Extension->SpecialChars.XoffChar);
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+ } else {
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+ WRITE_TRANSMIT_HOLDING(Extension,
+ Extension->Controller,
+ Extension->SpecialChars.XoffChar);
+
+ }
+
+ //
+ // We can't be sending an Xoff character
+ // if the transmission is already held
+ // up because of Xoff. Therefore, if we
+ // are holding then we can't send the char.
+ //
+
+ //
+ // If the application has set xoff continue
+ // mode then we don't actually stop sending
+ // characters if we send an xoff to the other
+ // side.
+ //
+
+ if (!(Extension->HandFlow.FlowReplace &
+ SERIAL_XOFF_CONTINUE)) {
+
+ Extension->TXHolding |= SERIAL_TX_XOFF;
+
+ if ((Extension->HandFlow.FlowReplace &
+ SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+ }
+
+ }
+
+ Extension->SendXoffChar = FALSE;
+ Extension->HoldingEmpty = FALSE;
+
+ //
+ // Even if transmission is being held
+ // up, we should still transmit an immediate
+ // character if all that is holding us
+ // up is xon/xoff (OS/2 rules).
+ //
+
+ } else if (Extension->TransmitImmediate &&
+ (!Extension->TXHolding ||
+ (Extension->TXHolding == SERIAL_TX_XOFF)
+ )) {
+
+ Extension->TransmitImmediate = FALSE;
+
+ if ((Extension->HandFlow.FlowReplace &
+ SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ //
+ // We have to raise if we're sending
+ // this character.
+ //
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+ WRITE_TRANSMIT_HOLDING(Extension,
+ Extension->Controller,
+ Extension->ImmediateChar);
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+ } else {
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+ WRITE_TRANSMIT_HOLDING(Extension,
+ Extension->Controller,
+ Extension->ImmediateChar);
+
+ }
+
+ Extension->HoldingEmpty = FALSE;
+
+ SerialInsertQueueDpc(
+ Extension->CompleteImmediateDpc
+ );
+
+ } else if (!Extension->TXHolding) {
+
+ ULONG amountToWrite;
+
+ if (Extension->FifoPresent) {
+
+ amountToWrite = (Extension->TxFifoAmount <
+ Extension->WriteLength)?
+ Extension->TxFifoAmount:
+ Extension->WriteLength;
+
+ } else {
+
+ amountToWrite = 1;
+
+ }
+ if ((Extension->HandFlow.FlowReplace &
+ SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ //
+ // We have to raise if we're sending
+ // this character.
+ //
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ if (amountToWrite == 1) {
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+ WRITE_TRANSMIT_HOLDING(Extension,
+ Extension->Controller,
+ *(Extension->WriteCurrentChar));
+
+ } else {
+
+ Extension->PerfStats.TransmittedCount +=
+ amountToWrite;
+ Extension->WmiPerfData.TransmittedCount +=
+ amountToWrite;
+ WRITE_TRANSMIT_FIFO_HOLDING(Extension,
+ Extension->Controller,
+ Extension->WriteCurrentChar,
+ amountToWrite);
+ }
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+ } else {
+
+ if (amountToWrite == 1) {
+
+ Extension->PerfStats.TransmittedCount++;
+ Extension->WmiPerfData.TransmittedCount++;
+ WRITE_TRANSMIT_HOLDING(Extension,
+ Extension->Controller,
+ *(Extension->WriteCurrentChar));
+
+ } else {
+
+ Extension->PerfStats.TransmittedCount +=
+ amountToWrite;
+ Extension->WmiPerfData.TransmittedCount +=
+ amountToWrite;
+ WRITE_TRANSMIT_FIFO_HOLDING(Extension,
+ Extension->Controller,
+ Extension->WriteCurrentChar,
+ amountToWrite);
+
+ }
+
+ }
+
+ Extension->HoldingEmpty = FALSE;
+ Extension->WriteCurrentChar += amountToWrite;
+ Extension->WriteLength -= amountToWrite;
+
+ if (!Extension->WriteLength) {
+
+ //
+ // No More characters left. This
+ // write is complete. Take care
+ // when updating the information field,
+ // we could have an xoff counter masquerading
+ // as a write request.
+ //
+ reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest);
+
+ reqContext->Information =
+ (reqContext->MajorFunction == IRP_MJ_WRITE)?
+ (reqContext->Length): (1);
+
+ SerialInsertQueueDpc(
+ Extension->CompleteWriteDpc
+ );
+
+ }
+
+ }
+
+ }
+
+ break;
+
+ }
+
+ case SERIAL_IIR_MS: {
+
+ SerialHandleModemUpdate(
+ Extension,
+ FALSE
+ );
+
+ break;
+
+ }
+
+ }
+
+ } while (!((InterruptIdReg =
+ READ_INTERRUPT_ID_REG(Extension, Extension->Controller))
+ & SERIAL_IIR_NO_INTERRUPT_PENDING));
+
+ //
+ // Besides catching the WINBOND and SMC chip problems this
+ // will also cause transmission to restart incase of an xon
+ // char being received. Don't remove.
+ //
+
+ if (SerialProcessLSR(Extension) & SERIAL_LSR_THRE) {
+
+ if (!Extension->TXHolding &&
+ (Extension->WriteLength ||
+ Extension->TransmitImmediate)) {
+
+ goto doTrasmitStuff;
+
+ }
+
+ }
+
+ }
+
+ return ServicedAnInterrupt;
+
+}
+
+VOID
+SerialPutChar(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN UCHAR CharToPut
+ )
+
+/*++
+
+Routine Description:
+
+ This routine, which only runs at device level, takes care of
+ placing a character into the typeahead (receive) buffer.
+
+Arguments:
+
+ Extension - The serial device extension.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PREQUEST_CONTEXT reqContext = NULL;
+
+ //
+ // If we have dsr sensitivity enabled then
+ // we need to check the modem status register
+ // to see if it has changed.
+ //
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_DSR_SENSITIVITY) {
+
+ SerialHandleModemUpdate(
+ Extension,
+ FALSE
+ );
+
+ if (Extension->RXHolding & SERIAL_RX_DSR) {
+
+ //
+ // We simply act as if we haven't
+ // seen the character if we have dsr
+ // sensitivity and the dsr line is low.
+ //
+
+ return;
+
+ }
+
+ }
+
+ //
+ // If the xoff counter is non-zero then decrement it.
+ // If the counter then goes to zero, complete that request.
+ //
+
+ if (Extension->CountSinceXoff) {
+
+ Extension->CountSinceXoff--;
+
+ if (!Extension->CountSinceXoff) {
+ reqContext = SerialGetRequestContext(Extension->CurrentXoffRequest);
+ reqContext->Status = STATUS_SUCCESS;
+ reqContext->Information = 0;
+ SerialInsertQueueDpc(
+ Extension->XoffCountCompleteDpc
+ );
+
+ }
+
+ }
+
+ //
+ // Check to see if we are copying into the
+ // users buffer or into the interrupt buffer.
+ //
+ // If we are copying into the user buffer
+ // then we know there is always room for one more.
+ // (We know this because if there wasn't room
+ // then that read would have completed and we
+ // would be using the interrupt buffer.)
+ //
+ // If we are copying into the interrupt buffer
+ // then we will need to check if we have enough
+ // room.
+ //
+
+ if (Extension->ReadBufferBase !=
+ Extension->InterruptReadBuffer) {
+
+ //
+ // Increment the following value so
+ // that the interval timer (if one exists
+ // for this read) can know that a character
+ // has been read.
+ //
+
+ Extension->ReadByIsr++;
+
+ //
+ // We are in the user buffer. Place the
+ // character into the buffer. See if the
+ // read is complete.
+ //
+
+ *Extension->CurrentCharSlot = CharToPut;
+
+ if (Extension->CurrentCharSlot ==
+ Extension->LastCharSlot) {
+
+ //
+ // We've filled up the users buffer.
+ // Switch back to the interrupt buffer
+ // and send off a DPC to Complete the read.
+ //
+ // It is inherent that when we were using
+ // a user buffer that the interrupt buffer
+ // was empty.
+ //
+
+ Extension->ReadBufferBase =
+ Extension->InterruptReadBuffer;
+ Extension->CurrentCharSlot =
+ Extension->InterruptReadBuffer;
+ Extension->FirstReadableChar =
+ Extension->InterruptReadBuffer;
+ Extension->LastCharSlot =
+ Extension->InterruptReadBuffer +
+ (Extension->BufferSize - 1);
+ Extension->CharsInInterruptBuffer = 0;
+ reqContext = SerialGetRequestContext(Extension->CurrentReadRequest);
+ reqContext->Information = reqContext->Length;
+
+ SerialInsertQueueDpc(
+ Extension->CompleteReadDpc
+ );
+
+ } else {
+
+ //
+ // Not done with the users read.
+ //
+
+ Extension->CurrentCharSlot++;
+
+ }
+
+ } else {
+
+ //
+ // We need to see if we reached our flow
+ // control threshold. If we have then
+ // we turn on whatever flow control the
+ // owner has specified. If no flow
+ // control was specified, well..., we keep
+ // trying to receive characters and hope that
+ // we have enough room. Note that no matter
+ // what flow control protocol we are using, it
+ // will not prevent us from reading whatever
+ // characters are available.
+ //
+
+ if ((Extension->HandFlow.ControlHandShake
+ & SERIAL_DTR_MASK) ==
+ SERIAL_DTR_HANDSHAKE) {
+
+ //
+ // If we are already doing a
+ // dtr hold then we don't have
+ // to do anything else.
+ //
+
+ if (!(Extension->RXHolding &
+ SERIAL_RX_DTR)) {
+
+ if ((Extension->BufferSize -
+ Extension->HandFlow.XoffLimit)
+ <= (Extension->CharsInInterruptBuffer+1)) {
+
+ Extension->RXHolding |= SERIAL_RX_DTR;
+
+ SerialClrDTR(Extension->WdfInterrupt, Extension);
+
+ }
+
+ }
+
+ }
+
+ if ((Extension->HandFlow.FlowReplace
+ & SERIAL_RTS_MASK) ==
+ SERIAL_RTS_HANDSHAKE) {
+
+ //
+ // If we are already doing a
+ // rts hold then we don't have
+ // to do anything else.
+ //
+
+ if (!(Extension->RXHolding &
+ SERIAL_RX_RTS)) {
+
+ if ((Extension->BufferSize -
+ Extension->HandFlow.XoffLimit)
+ <= (Extension->CharsInInterruptBuffer+1)) {
+
+ Extension->RXHolding |= SERIAL_RX_RTS;
+
+ SerialClrRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ }
+
+ }
+
+ if (Extension->HandFlow.FlowReplace &
+ SERIAL_AUTO_RECEIVE) {
+
+ //
+ // If we are already doing a
+ // xoff hold then we don't have
+ // to do anything else.
+ //
+
+ if (!(Extension->RXHolding &
+ SERIAL_RX_XOFF)) {
+
+ if ((Extension->BufferSize -
+ Extension->HandFlow.XoffLimit)
+ <= (Extension->CharsInInterruptBuffer+1)) {
+
+ Extension->RXHolding |= SERIAL_RX_XOFF;
+
+ //
+ // If necessary cause an
+ // off to be sent.
+ //
+
+ SerialProdXonXoff(
+ Extension,
+ FALSE
+ );
+
+ }
+
+ }
+
+ }
+
+ if (Extension->CharsInInterruptBuffer <
+ Extension->BufferSize) {
+
+ *Extension->CurrentCharSlot = CharToPut;
+ Extension->CharsInInterruptBuffer++;
+
+ //
+ // If we've become 80% full on this character
+ // and this is an interesting event, note it.
+ //
+
+ if (Extension->CharsInInterruptBuffer ==
+ Extension->BufferSizePt8) {
+
+ if (Extension->IsrWaitMask &
+ SERIAL_EV_RX80FULL) {
+
+ Extension->HistoryMask |= SERIAL_EV_RX80FULL;
+
+ if (Extension->IrpMaskLocation) {
+
+ *Extension->IrpMaskLocation =
+ Extension->HistoryMask;
+ Extension->IrpMaskLocation = NULL;
+ Extension->HistoryMask = 0;
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest);
+ reqContext->Information = sizeof(ULONG);
+ SerialInsertQueueDpc(
+ Extension->CommWaitDpc
+ );
+
+ }
+
+ }
+
+ }
+
+ //
+ // Point to the next available space
+ // for a received character. Make sure
+ // that we wrap around to the beginning
+ // of the buffer if this last character
+ // received was placed at the last slot
+ // in the buffer.
+ //
+
+ if (Extension->CurrentCharSlot ==
+ Extension->LastCharSlot) {
+
+ Extension->CurrentCharSlot =
+ Extension->InterruptReadBuffer;
+
+ } else {
+
+ Extension->CurrentCharSlot++;
+
+ }
+
+ } else {
+
+ //
+ // We have a new character but no room for it.
+ //
+
+ Extension->PerfStats.BufferOverrunErrorCount++;
+ Extension->WmiPerfData.BufferOverrunErrorCount++;
+ Extension->ErrorWord |= SERIAL_ERROR_QUEUEOVERRUN;
+
+ if (Extension->HandFlow.FlowReplace &
+ SERIAL_ERROR_CHAR) {
+
+ //
+ // Place the error character into the last
+ // valid place for a character. Be careful!,
+ // that place might not be the previous location!
+ //
+
+ if (Extension->CurrentCharSlot ==
+ Extension->InterruptReadBuffer) {
+
+ *(Extension->InterruptReadBuffer+
+ (Extension->BufferSize-1)) =
+ Extension->SpecialChars.ErrorChar;
+
+ } else {
+
+ *(Extension->CurrentCharSlot-1) =
+ Extension->SpecialChars.ErrorChar;
+
+ }
+
+ }
+
+ //
+ // If the application has requested it, abort all reads
+ // and writes on an error.
+ //
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_ERROR_ABORT) {
+
+ SerialInsertQueueDpc(
+ Extension->CommErrorDpc
+ );
+
+ }
+
+ }
+
+ }
+
+}
+
+UCHAR
+SerialProcessLSR(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine, which only runs at device level, reads the
+ ISR and totally processes everything that might have
+ changed.
+
+Arguments:
+
+ Extension - The serial device extension.
+
+Return Value:
+
+ The value of the line status register.
+
+--*/
+
+{
+ PREQUEST_CONTEXT reqContext = NULL;
+
+ UCHAR LineStatus = READ_LINE_STATUS(Extension, Extension->Controller);
+
+
+ Extension->HoldingEmpty = (LineStatus & SERIAL_LSR_THRE) ? TRUE : FALSE;
+
+ //
+ // If the line status register is just the fact that
+ // the trasmit registers are empty or a character is
+ // received then we want to reread the interrupt
+ // identification register so that we just pick up that.
+ //
+
+ if (LineStatus & ~(SERIAL_LSR_THRE | SERIAL_LSR_TEMT
+ | SERIAL_LSR_DR)) {
+
+ //
+ // We have some sort of data problem in the receive.
+ // For any of these errors we may abort all current
+ // reads and writes.
+ //
+ //
+ // If we are inserting the value of the line status
+ // into the data stream then we should put the escape
+ // character in now.
+ //
+
+ if (Extension->EscapeChar) {
+
+ SerialPutChar(
+ Extension,
+ Extension->EscapeChar
+ );
+
+ SerialPutChar(
+ Extension,
+ (UCHAR)((LineStatus & SERIAL_LSR_DR)?
+ (SERIAL_LSRMST_LSR_DATA):(SERIAL_LSRMST_LSR_NODATA))
+ );
+
+ SerialPutChar(
+ Extension,
+ LineStatus
+ );
+
+ if (LineStatus & SERIAL_LSR_DR) {
+
+ Extension->PerfStats.ReceivedCount++;
+ Extension->WmiPerfData.ReceivedCount++;
+ SerialPutChar(
+ Extension,
+ READ_RECEIVE_BUFFER(Extension, Extension->Controller)
+ );
+
+ }
+
+ }
+
+ if (LineStatus & SERIAL_LSR_OE) {
+
+ Extension->PerfStats.SerialOverrunErrorCount++;
+ Extension->WmiPerfData.SerialOverrunErrorCount++;
+ Extension->ErrorWord |= SERIAL_ERROR_OVERRUN;
+
+ if (Extension->HandFlow.FlowReplace &
+ SERIAL_ERROR_CHAR) {
+
+ SerialPutChar(
+ Extension,
+ Extension->SpecialChars.ErrorChar
+ );
+
+ if (LineStatus & SERIAL_LSR_DR) {
+
+ Extension->PerfStats.ReceivedCount++;
+ Extension->WmiPerfData.ReceivedCount++;
+ READ_RECEIVE_BUFFER(Extension, Extension->Controller);
+
+ }
+
+ } else {
+
+ if (LineStatus & SERIAL_LSR_DR) {
+
+ Extension->PerfStats.ReceivedCount++;
+ Extension->WmiPerfData.ReceivedCount++;
+ SerialPutChar(
+ Extension,
+ READ_RECEIVE_BUFFER(Extension,
+ Extension->Controller
+ )
+ );
+
+ }
+
+ }
+
+ }
+
+ if (LineStatus & SERIAL_LSR_BI) {
+
+ Extension->ErrorWord |= SERIAL_ERROR_BREAK;
+
+ if (Extension->HandFlow.FlowReplace &
+ SERIAL_BREAK_CHAR) {
+
+ SerialPutChar(
+ Extension,
+ Extension->SpecialChars.BreakChar
+ );
+
+ }
+
+ } else {
+
+ //
+ // Framing errors only count if they
+ // occur exclusive of a break being
+ // received.
+ //
+
+ if (LineStatus & SERIAL_LSR_PE) {
+
+ Extension->PerfStats.ParityErrorCount++;
+ Extension->WmiPerfData.ParityErrorCount++;
+ Extension->ErrorWord |= SERIAL_ERROR_PARITY;
+
+ if (Extension->HandFlow.FlowReplace &
+ SERIAL_ERROR_CHAR) {
+
+ SerialPutChar(
+ Extension,
+ Extension->SpecialChars.ErrorChar
+ );
+
+ if (LineStatus & SERIAL_LSR_DR) {
+
+ Extension->PerfStats.ReceivedCount++;
+ Extension->WmiPerfData.ReceivedCount++;
+ READ_RECEIVE_BUFFER(Extension, Extension->Controller);
+
+ }
+
+ }
+
+ }
+
+ if (LineStatus & SERIAL_LSR_FE) {
+
+ Extension->PerfStats.FrameErrorCount++;
+ Extension->WmiPerfData.FrameErrorCount++;
+ Extension->ErrorWord |= SERIAL_ERROR_FRAMING;
+
+ if (Extension->HandFlow.FlowReplace &
+ SERIAL_ERROR_CHAR) {
+
+ SerialPutChar(
+ Extension,
+ Extension->SpecialChars.ErrorChar
+ );
+ if (LineStatus & SERIAL_LSR_DR) {
+
+ Extension->PerfStats.ReceivedCount++;
+ Extension->WmiPerfData.ReceivedCount++;
+ READ_RECEIVE_BUFFER(Extension, Extension->Controller);
+
+ }
+
+ }
+
+ }
+
+ }
+
+ //
+ // If the application has requested it,
+ // abort all the reads and writes
+ // on an error.
+ //
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_ERROR_ABORT) {
+
+ SerialInsertQueueDpc(
+ Extension->CommErrorDpc
+ );
+
+ }
+
+ //
+ // Check to see if we have a wait
+ // pending on the comm error events. If we
+ // do then we schedule a dpc to satisfy
+ // that wait.
+ //
+
+ if (Extension->IsrWaitMask) {
+
+ if ((Extension->IsrWaitMask & SERIAL_EV_ERR) &&
+ (LineStatus & (SERIAL_LSR_OE |
+ SERIAL_LSR_PE |
+ SERIAL_LSR_FE))) {
+
+ Extension->HistoryMask |= SERIAL_EV_ERR;
+
+ }
+
+ if ((Extension->IsrWaitMask & SERIAL_EV_BREAK) &&
+ (LineStatus & SERIAL_LSR_BI)) {
+
+ Extension->HistoryMask |= SERIAL_EV_BREAK;
+
+ }
+
+ if (Extension->IrpMaskLocation &&
+ Extension->HistoryMask) {
+
+ *Extension->IrpMaskLocation =
+ Extension->HistoryMask;
+ Extension->IrpMaskLocation = NULL;
+ Extension->HistoryMask = 0;
+ reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest);
+ reqContext->Information = sizeof(ULONG);
+ SerialInsertQueueDpc(
+ Extension->CommWaitDpc
+ );
+
+ }
+
+ }
+
+ if (LineStatus & SERIAL_LSR_THRE) {
+
+ //
+ // There is a hardware bug in some versions
+ // of the 16450 and 550. If THRE interrupt
+ // is pending, but a higher interrupt comes
+ // in it will only return the higher and
+ // *forget* about the THRE.
+ //
+ // A suitable workaround - whenever we
+ // are *all* done reading line status
+ // of the device we check to see if the
+ // transmit holding register is empty. If it is
+ // AND we are currently transmitting data
+ // enable the interrupts which should cause
+ // an interrupt indication which we quiet
+ // when we read the interrupt id register.
+ //
+
+ if (Extension->WriteLength |
+ Extension->TransmitImmediate) {
+
+ DISABLE_ALL_INTERRUPTS(Extension,
+ Extension->Controller
+ );
+ ENABLE_ALL_INTERRUPTS(Extension,
+ Extension->Controller
+ );
+ }
+
+ }
+
+ }
+
+ return LineStatus;
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/log.c b/tests/projects/windows/driver/kmdf/serial/log.c new file mode 100644 index 000000000..285c8b2f8 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/log.c @@ -0,0 +1,97 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ log.c
+
+Abstract:
+
+ Debug log Code for serial.
+
+Environment:
+
+ kernel mode only
+
+--*/
+
+#include "precomp.h"
+
+extern ULONG DebugLevel;
+extern ULONG DebugFlag;
+
+#if !defined(EVENT_TRACING)
+
+VOID
+SerialDbgPrintEx (
+ IN ULONG TraceEventsLevel,
+ IN ULONG TraceEventsFlag,
+ IN PCCHAR DebugMessage,
+ ...
+ )
+
+/*++
+
+Routine Description:
+
+ Debug print for the sample driver.
+
+Arguments:
+
+ TraceEventsLevel - print level between 0 and 3, with 3 the most verbose
+
+Return Value:
+
+ None.
+
+ --*/
+ {
+#if DBG
+
+#define TEMP_BUFFER_SIZE 1024
+
+ va_list list;
+ CHAR debugMessageBuffer [TEMP_BUFFER_SIZE];
+ NTSTATUS status;
+
+ va_start(list, DebugMessage);
+
+ if (DebugMessage) {
+
+ //
+ // Using new safe string functions instead of _vsnprintf.
+ // This function takes care of NULL terminating if the message
+ // is longer than the buffer.
+ //
+ status = RtlStringCbVPrintfA( debugMessageBuffer,
+ sizeof(debugMessageBuffer),
+ DebugMessage,
+ list );
+ if(!NT_SUCCESS(status)) {
+
+ KdPrint((_DRIVER_NAME_": RtlStringCbVPrintfA failed %x\n", status));
+ return;
+ }
+ if (TraceEventsLevel < TRACE_LEVEL_INFORMATION ||
+ (TraceEventsLevel <= DebugLevel &&
+ ((TraceEventsFlag & DebugFlag) == TraceEventsFlag))) {
+
+ KdPrint((debugMessageBuffer));
+ }
+ }
+ va_end(list);
+
+ return;
+
+#else
+
+ UNREFERENCED_PARAMETER(TraceEventsLevel);
+ UNREFERENCED_PARAMETER(TraceEventsFlag);
+ UNREFERENCED_PARAMETER(DebugMessage);
+
+#endif
+}
+
+#endif
+
diff --git a/tests/projects/windows/driver/kmdf/serial/log.h b/tests/projects/windows/driver/kmdf/serial/log.h new file mode 100644 index 000000000..eedcd08f3 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/log.h @@ -0,0 +1,37 @@ +/*++
+
+Copyright (c) 1993 Microsoft Corporation
+:ts=4
+
+Module Name:
+
+ log.h
+
+Abstract:
+
+ debug macros
+
+Environment:
+
+ Kernel & user mode
+
+--*/
+
+#ifndef __LOG_H__
+#define __LOG_H__
+
+#if !defined(EVENT_TRACING)
+
+VOID
+SerialDbgPrintEx (
+ IN ULONG DebugPrintLevel,
+ IN ULONG DebugPrintFlag,
+ IN PCCHAR DebugMessage,
+ ...
+ );
+
+#endif
+
+#endif // __LOG_H__
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/modmflow.c b/tests/projects/windows/driver/kmdf/serial/modmflow.c new file mode 100644 index 000000000..1c71ae3fd --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/modmflow.c @@ -0,0 +1,1714 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ modmflow.c
+
+Abstract:
+
+ This module contains *MOST* of the code used to manipulate
+ the modem control and status registers. The vast majority
+ of the remainder of flow control is concentrated in the
+ Interrupt service routine. A very small amount resides
+ in the read code that pull characters out of the interrupt
+ buffer.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "modmflow.tmh"
+#endif
+
+
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialDecrementRTSCounter;
+
+BOOLEAN
+SerialSetDTR(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine which is only called at interrupt level is used
+ to set the DTR in the modem control register.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+ UCHAR ModemControl;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller);
+
+ ModemControl |= SERIAL_MCR_DTR;
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS,
+ "Setting DTR for %p\n", Extension->Controller);
+
+ WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl);
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialClrDTR(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine which is only called at interrupt level is used
+ to clear the DTR in the modem control register.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+ UCHAR ModemControl;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller);
+
+ ModemControl &= ~SERIAL_MCR_DTR;
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing DTR for %p\n", Extension->Controller);
+
+ WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl);
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialSetRTS(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine which is only called at interrupt level is used
+ to set the RTS in the modem control register.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+ UCHAR ModemControl;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller);
+
+ ModemControl |= SERIAL_MCR_RTS;
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Setting Rts for %p\n", Extension->Controller);
+
+ WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl);
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialClrRTS(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine which is only called at interrupt level is used
+ to clear the RTS in the modem control register.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+ UCHAR ModemControl;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller);
+
+ ModemControl &= ~SERIAL_MCR_RTS;
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing Rts for %p\n", Extension->Controller);
+
+ WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl);
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialSetupNewHandFlow(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN PSERIAL_HANDFLOW NewHandFlow
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adjusts the flow control based on new
+ control flow.
+
+Arguments:
+
+ Extension - A pointer to the serial device extension.
+
+ NewHandFlow - A pointer to a serial handflow structure
+ that is to become the new setup for flow
+ control.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ SERIAL_HANDFLOW New = *NewHandFlow;
+
+ //
+ // If the Extension->DeviceIsOpened is FALSE that means
+ // we are entering this routine in response to an open request.
+ // If that is so, then we always proceed with the work regardless
+ // of whether things have changed.
+ //
+
+ //
+ // First we take care of the DTR flow control. We only
+ // do work if something has changed.
+ //
+
+ if ((!Extension->DeviceIsOpened) ||
+ ((Extension->HandFlow.ControlHandShake & SERIAL_DTR_MASK) !=
+ (New.ControlHandShake & SERIAL_DTR_MASK))) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Processing DTR flow for %p\n",
+ Extension->Controller);
+
+ if (New.ControlHandShake & SERIAL_DTR_MASK) {
+
+ //
+ // Well we might want to set DTR.
+ //
+ // Before we do, we need to check whether we are doing
+ // dtr flow control. If we are then we need to check
+ // if then number of characters in the interrupt buffer
+ // exceeds the XoffLimit. If it does then we don't
+ // enable DTR AND we set the RXHolding to record that
+ // we are holding because of the dtr.
+ //
+
+ if ((New.ControlHandShake & SERIAL_DTR_MASK)
+ == SERIAL_DTR_HANDSHAKE) {
+
+ if ((Extension->BufferSize - New.XoffLimit) >
+ Extension->CharsInInterruptBuffer) {
+
+ //
+ // However if we are already holding we don't want
+ // to turn it back on unless we exceed the Xon
+ // limit.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_DTR) {
+
+ //
+ // We can assume that its DTR line is already low.
+ //
+
+ if (Extension->CharsInInterruptBuffer >
+ (ULONG)New.XonLimit) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Removing DTR block on "
+ "reception for %p\n",
+ Extension->Controller);
+
+ Extension->RXHolding &= ~SERIAL_RX_DTR;
+ SerialSetDTR(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else {
+
+ SerialSetDTR(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Setting DTR block on reception "
+ "for %p\n", Extension->Controller);
+ Extension->RXHolding |= SERIAL_RX_DTR;
+ SerialClrDTR(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else {
+
+ //
+ // Note that if we aren't currently doing dtr flow control then
+ // we MIGHT have been. So even if we aren't currently doing
+ // DTR flow control, we should still check if RX is holding
+ // because of DTR. If it is, then we should clear the holding
+ // of this bit.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_DTR) {
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Removing dtr block of reception "
+ "for %p\n", Extension->Controller);
+ Extension->RXHolding &= ~SERIAL_RX_DTR;
+ }
+
+ SerialSetDTR(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else {
+
+ //
+ // The end result here will be that DTR is cleared.
+ //
+ // We first need to check whether reception is being held
+ // up because of previous DTR flow control. If it is then
+ // we should clear that reason in the RXHolding mask.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_DTR) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "removing dtr block of reception for"
+ " %p\n", Extension->Controller);
+ Extension->RXHolding &= ~SERIAL_RX_DTR;
+
+ }
+
+ SerialClrDTR(Extension->WdfInterrupt, Extension);
+
+ }
+
+ }
+
+ //
+ // Time to take care of the RTS Flow control.
+ //
+ // First we only do work if something has changed.
+ //
+
+ if ((!Extension->DeviceIsOpened) ||
+ ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) !=
+ (New.FlowReplace & SERIAL_RTS_MASK))) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Processing RTS flow %p\n",
+ Extension->Controller);
+
+ if ((New.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_RTS_HANDSHAKE) {
+
+ //
+ // Well we might want to set RTS.
+ //
+ // Before we do, we need to check whether we are doing
+ // rts flow control. If we are then we need to check
+ // if then number of characters in the interrupt buffer
+ // exceeds the XoffLimit. If it does then we don't
+ // enable RTS AND we set the RXHolding to record that
+ // we are holding because of the rts.
+ //
+
+ if ((Extension->BufferSize - New.XoffLimit) >
+ Extension->CharsInInterruptBuffer) {
+
+ //
+ // However if we are already holding we don't want
+ // to turn it back on unless we exceed the Xon
+ // limit.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_RTS) {
+
+ //
+ // We can assume that its RTS line is already low.
+ //
+
+ if (Extension->CharsInInterruptBuffer >
+ (ULONG)New.XonLimit) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Removing rts block of "
+ "reception for %p\n",
+ Extension->Controller);
+ Extension->RXHolding &= ~SERIAL_RX_RTS;
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else {
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Setting rts block of reception for "
+ "%p\n", Extension->Controller);
+ Extension->RXHolding |= SERIAL_RX_RTS;
+ SerialClrRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else if ((New.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_RTS_CONTROL) {
+
+ //
+ // Note that if we aren't currently doing rts flow control then
+ // we MIGHT have been. So even if we aren't currently doing
+ // RTS flow control, we should still check if RX is holding
+ // because of RTS. If it is, then we should clear the holding
+ // of this bit.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_RTS) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing rts block of reception for "
+ "%p\n", Extension->Controller);
+ Extension->RXHolding &= ~SERIAL_RX_RTS;
+
+ }
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ } else if ((New.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ //
+ // We first need to check whether reception is being held
+ // up because of previous RTS flow control. If it is then
+ // we should clear that reason in the RXHolding mask.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_RTS) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "TOGGLE Clearing rts block of "
+ "reception for %p\n", Extension->Controller);
+ Extension->RXHolding &= ~SERIAL_RX_RTS;
+
+ }
+
+ //
+ // We have to place the rts value into the Extension
+ // now so that the code that tests whether the
+ // rts line should be lowered will find that we
+ // are "still" doing transmit toggling. The code
+ // for lowering can be invoked later by a timer so
+ // it has to test whether it still needs to do its
+ // work.
+ //
+
+ Extension->HandFlow.FlowReplace &= ~SERIAL_RTS_MASK;
+ Extension->HandFlow.FlowReplace |= SERIAL_TRANSMIT_TOGGLE;
+
+ //
+ // The order of the tests is very important below.
+ //
+ // If there is a break then we should turn on the RTS.
+ //
+ // If there isn't a break but there are characters in
+ // the hardware, then turn on the RTS.
+ //
+ // If there are writes pending that aren't being held
+ // up, then turn on the RTS.
+ //
+
+ if ((Extension->TXHolding & SERIAL_TX_BREAK) ||
+ ((SerialProcessLSR(Extension) & (SERIAL_LSR_THRE |
+ SERIAL_LSR_TEMT)) !=
+ (SERIAL_LSR_THRE |
+ SERIAL_LSR_TEMT)) ||
+ (Extension->CurrentWriteRequest || Extension->TransmitImmediate ||
+ (!IsQueueEmpty(Extension->WriteQueue)) &&
+ (!Extension->TXHolding))) {
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ } else {
+
+ //
+ // This routine will check to see if it is time
+ // to lower the RTS because of transmit toggle
+ // being on. If it is ok to lower it, it will,
+ // if it isn't ok, it will schedule things so
+ // that it will get lowered later.
+ //
+
+ Extension->CountOfTryingToLowerRTS++;
+ SerialPerhapsLowerRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ } else {
+
+ //
+ // The end result here will be that RTS is cleared.
+ //
+ // We first need to check whether reception is being held
+ // up because of previous RTS flow control. If it is then
+ // we should clear that reason in the RXHolding mask.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_RTS) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing rts block of reception for"
+ " %p\n", Extension->Controller);
+ Extension->RXHolding &= ~SERIAL_RX_RTS;
+
+ }
+
+ SerialClrRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ }
+
+ //
+ // We now take care of automatic receive flow control.
+ // We only do work if things have changed.
+ //
+
+ if ((!Extension->DeviceIsOpened) ||
+ ((Extension->HandFlow.FlowReplace & SERIAL_AUTO_RECEIVE) !=
+ (New.FlowReplace & SERIAL_AUTO_RECEIVE))) {
+
+ if (New.FlowReplace & SERIAL_AUTO_RECEIVE) {
+
+ //
+ // We wouldn't be here if it had been on before.
+ //
+ // We should check to see whether we exceed the turn
+ // off limits.
+ //
+ // Note that since we are following the OS/2 flow
+ // control rules we will never send an xon if
+ // when enabling xon/xoff flow control we discover that
+ // we could receive characters but we are held up do
+ // to a previous Xoff.
+ //
+
+ if ((Extension->BufferSize - New.XoffLimit) <=
+ Extension->CharsInInterruptBuffer) {
+
+ //
+ // Cause the Xoff to be sent.
+ //
+
+ Extension->RXHolding |= SERIAL_RX_XOFF;
+
+ SerialProdXonXoff(
+ Extension,
+ FALSE
+ );
+
+ }
+
+ } else {
+
+ //
+ // The app has disabled automatic receive flow control.
+ //
+ // If transmission was being held up because of
+ // an automatic receive Xoff, then we should
+ // cause an Xon to be sent.
+ //
+
+ if (Extension->RXHolding & SERIAL_RX_XOFF) {
+
+ Extension->RXHolding &= ~SERIAL_RX_XOFF;
+
+ //
+ // Cause the Xon to be sent.
+ //
+
+ SerialProdXonXoff(
+ Extension,
+ TRUE
+ );
+
+ }
+
+ }
+
+ }
+
+ //
+ // We now take care of automatic transmit flow control.
+ // We only do work if things have changed.
+ //
+
+ if ((!Extension->DeviceIsOpened) ||
+ ((Extension->HandFlow.FlowReplace & SERIAL_AUTO_TRANSMIT) !=
+ (New.FlowReplace & SERIAL_AUTO_TRANSMIT))) {
+
+ if (New.FlowReplace & SERIAL_AUTO_TRANSMIT) {
+
+ //
+ // We wouldn't be here if it had been on before.
+ //
+ // There is some belief that if autotransmit
+ // was just enabled, I should go look in what we
+ // already received, and if we find the xoff character
+ // then we should stop transmitting. I think this
+ // is an application bug. For now we just care about
+ // what we see in the future.
+ //
+
+ ;
+
+ } else {
+
+ //
+ // The app has disabled automatic transmit flow control.
+ //
+ // If transmission was being held up because of
+ // an automatic transmit Xoff, then we should
+ // cause an Xon to be sent.
+ //
+
+ if (Extension->TXHolding & SERIAL_TX_XOFF) {
+
+ Extension->TXHolding &= ~SERIAL_TX_XOFF;
+
+ //
+ // Cause the Xon to be sent.
+ //
+
+ SerialProdXonXoff(
+ Extension,
+ TRUE
+ );
+
+ }
+
+ }
+
+ }
+
+ //
+ // At this point we can simply make sure that entire
+ // handflow structure in the extension is updated.
+ //
+
+ Extension->HandFlow = New;
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialSetHandFlow(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to set the handshake and control
+ flow in the device extension.
+
+Arguments:
+
+ Context - Pointer to a structure that contains a pointer to
+ the device extension and a pointer to a handflow
+ structure..
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_IOCTL_SYNC S = Context;
+ PSERIAL_DEVICE_EXTENSION Extension = S->Extension;
+ PSERIAL_HANDFLOW HandFlow = S->Data;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ SerialSetupNewHandFlow(
+ Extension,
+ HandFlow
+ );
+
+ SerialHandleModemUpdate(
+ Extension,
+ FALSE
+ );
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialTurnOnBreak(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will turn on break in the hardware and
+ record the fact the break is on, in the extension variable
+ that holds reasons that transmission is stopped.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UCHAR OldLineControl;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ OldLineControl = READ_LINE_CONTROL(Extension, Extension->Controller);
+
+ OldLineControl |= SERIAL_LCR_BREAK;
+
+ WRITE_LINE_CONTROL(Extension,
+ Extension->Controller,
+ OldLineControl
+ );
+
+ Extension->TXHolding |= SERIAL_TX_BREAK;
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialTurnOffBreak(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will turn off break in the hardware and
+ record the fact the break is off, in the extension variable
+ that holds reasons that transmission is stopped.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UCHAR OldLineControl;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ if (Extension->TXHolding & SERIAL_TX_BREAK) {
+
+ //
+ // We actually have a good reason for testing if transmission
+ // is holding instead of blindly clearing the bit.
+ //
+ // If transmission actually was holding and the result of
+ // clearing the bit is that we should restart transmission
+ // then we will poke the interrupt enable bit, which will
+ // cause an actual interrupt and transmission will then
+ // restart on its own.
+ //
+ // If transmission wasn't holding and we poked the bit
+ // then we would interrupt before a character actually made
+ // it out and we could end up over writing a character in
+ // the transmission hardware.
+
+ OldLineControl = READ_LINE_CONTROL(Extension, Extension->Controller);
+
+ OldLineControl &= ~SERIAL_LCR_BREAK;
+
+ WRITE_LINE_CONTROL(Extension,
+ Extension->Controller,
+ OldLineControl
+ );
+
+ Extension->TXHolding &= ~SERIAL_TX_BREAK;
+
+ if (!Extension->TXHolding &&
+ (Extension->TransmitImmediate ||
+ Extension->WriteLength) &&
+ Extension->HoldingEmpty) {
+
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+
+ } else {
+
+ //
+ // The following routine will lower the rts if we
+ // are doing transmit toggleing and there is no
+ // reason to keep it up.
+ //
+
+ Extension->CountOfTryingToLowerRTS++;
+ SerialPerhapsLowerRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ }
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialPretendXoff(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to process the Ioctl that request the
+ driver to act as if an Xoff was received. Even if the
+ driver does not have automatic Xoff/Xon flowcontrol - This
+ still will stop the transmission. This is the OS/2 behavior
+ and is not well specified for Windows. Therefore we adopt
+ the OS/2 behavior.
+
+ Note: If the driver does not have automatic Xoff/Xon enabled
+ then the only way to restart transmission is for the
+ application to request we "act" as if we saw the xon.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ Extension->TXHolding |= SERIAL_TX_XOFF;
+
+ if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+ }
+
+ return FALSE;
+
+}
+
+BOOLEAN
+SerialPretendXon(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to process the Ioctl that request the
+ driver to act as if an Xon was received.
+
+ Note: If the driver does not have automatic Xoff/Xon enabled
+ then the only way to restart transmission is for the
+ application to request we "act" as if we saw the xon.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ if (Extension->TXHolding) {
+
+ //
+ // We actually have a good reason for testing if transmission
+ // is holding instead of blindly clearing the bit.
+ //
+ // If transmission actually was holding and the result of
+ // clearing the bit is that we should restart transmission
+ // then we will poke the interrupt enable bit, which will
+ // cause an actual interrupt and transmission will then
+ // restart on its own.
+ //
+ // If transmission wasn't holding and we poked the bit
+ // then we would interrupt before a character actually made
+ // it out and we could end up over writing a character in
+ // the transmission hardware.
+
+ Extension->TXHolding &= ~SERIAL_TX_XOFF;
+
+ if (!Extension->TXHolding &&
+ (Extension->TransmitImmediate ||
+ Extension->WriteLength) &&
+ Extension->HoldingEmpty) {
+
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+
+ }
+
+ }
+
+ return FALSE;
+
+}
+
+VOID
+SerialHandleReducedIntBuffer(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to handle a reduction in the number
+ of characters in the interrupt (typeahead) buffer. It
+ will check the current output flow control and re-enable transmission
+ as needed.
+
+ NOTE: This routine assumes that it is working at interrupt level.
+
+Arguments:
+
+ Extension - A pointer to the device extension.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+
+ //
+ // If we are doing receive side flow control and we are
+ // currently "holding" then because we've emptied out
+ // some characters from the interrupt buffer we need to
+ // see if we can "re-enable" reception.
+ //
+
+ if (Extension->RXHolding) {
+
+ if (Extension->CharsInInterruptBuffer <=
+ (ULONG)Extension->HandFlow.XonLimit) {
+
+ if (Extension->RXHolding & SERIAL_RX_DTR) {
+
+ Extension->RXHolding &= ~SERIAL_RX_DTR;
+ SerialSetDTR(Extension->WdfInterrupt, Extension);
+
+ }
+
+ if (Extension->RXHolding & SERIAL_RX_RTS) {
+
+ Extension->RXHolding &= ~SERIAL_RX_RTS;
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ if (Extension->RXHolding & SERIAL_RX_XOFF) {
+
+ //
+ // Prod the transmit code to send xon.
+ //
+
+ SerialProdXonXoff(
+ Extension,
+ TRUE
+ );
+
+ }
+
+ }
+
+ }
+
+}
+
+VOID
+SerialProdXonXoff(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN BOOLEAN SendXon
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will set up the SendXxxxChar variables if
+ necessary and determine if we are going to be interrupting
+ because of current transmission state. It will cause an
+ interrupt to occur if neccessary, to send the xon/xoff char.
+
+ NOTE: This routine assumes that it is called at interrupt
+ level.
+
+Arguments:
+
+ Extension - A pointer to the serial device extension.
+
+ SendXon - If a character is to be send, this indicates whether
+ it should be an Xon or an Xoff.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ //
+ // We assume that if the prodding is called more than
+ // once that the last prod has set things up appropriately.
+ //
+ // We could get called before the character is sent out
+ // because the send of the character was blocked because
+ // of hardware flow control (or break).
+ //
+
+ if (!Extension->SendXonChar && !Extension->SendXoffChar
+ && Extension->HoldingEmpty) {
+
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+
+ }
+
+ if (SendXon) {
+
+ Extension->SendXonChar = TRUE;
+ Extension->SendXoffChar = FALSE;
+
+ } else {
+
+ Extension->SendXonChar = FALSE;
+ Extension->SendXoffChar = TRUE;
+
+ }
+
+}
+
+ULONG
+SerialHandleModemUpdate(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN BOOLEAN DoingTX
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will be to check on the modem status, and
+ handle any appropriate event notification as well as
+ any flow control appropriate to modem status lines.
+
+ NOTE: This routine assumes that it is called at interrupt
+ level.
+
+Arguments:
+
+ Extension - A pointer to the serial device extension.
+
+ DoingTX - This boolean is used to indicate that this call
+ came from the transmit processing code. If this
+ is true then there is no need to cause a new interrupt
+ since the code will be trying to send the next
+ character as soon as this call finishes.
+
+Return Value:
+
+ This returns the old value of the modem status register
+ (extended into a ULONG).
+
+--*/
+
+{
+
+ //
+ // We keep this local so that after we are done
+ // examining the modem status and we've updated
+ // the transmission holding value, we know whether
+ // we've changed from needing to hold up transmission
+ // to transmission being able to proceed.
+ //
+ ULONG OldTXHolding = Extension->TXHolding;
+
+ //
+ // Holds the value in the mode status register.
+ //
+ UCHAR ModemStatus;
+ PREQUEST_CONTEXT reqContext;
+
+ ModemStatus =
+ READ_MODEM_STATUS(Extension, Extension->Controller);
+
+
+ //
+ // If we are placeing the modem status into the data stream
+ // on every change, we should do it now.
+ //
+
+ if (Extension->EscapeChar) {
+
+ if (ModemStatus & (SERIAL_MSR_DCTS |
+ SERIAL_MSR_DDSR |
+ SERIAL_MSR_TERI |
+ SERIAL_MSR_DDCD)) {
+
+ SerialPutChar(
+ Extension,
+ Extension->EscapeChar
+ );
+ SerialPutChar(
+ Extension,
+ SERIAL_LSRMST_MST
+ );
+ SerialPutChar(
+ Extension,
+ ModemStatus
+ );
+
+ }
+
+ }
+
+
+ //
+ // Take care of input flow control based on sensitivity
+ // to the DSR. This is done so that the application won't
+ // see spurious data generated by odd devices.
+ //
+ // Basically, if we are doing dsr sensitivity then the
+ // driver should only accept data when the dsr bit is
+ // set.
+ //
+
+ if (Extension->HandFlow.ControlHandShake & SERIAL_DSR_SENSITIVITY) {
+
+ if (ModemStatus & SERIAL_MSR_DSR) {
+
+ //
+ // The line is high. Simply make sure that
+ // RXHolding does't have the DSR bit.
+ //
+
+ Extension->RXHolding &= ~SERIAL_RX_DSR;
+
+ } else {
+
+ Extension->RXHolding |= SERIAL_RX_DSR;
+
+ }
+
+ } else {
+
+ //
+ // We don't have sensitivity due to DSR. Make sure we
+ // arn't holding. (We might have been, but the app just
+ // asked that we don't hold for this reason any more.)
+ //
+
+ Extension->RXHolding &= ~SERIAL_RX_DSR;
+
+ }
+
+ //
+ // Check to see if we have a wait
+ // pending on the modem status events. If we
+ // do then we schedule a dpc to satisfy
+ // that wait.
+ //
+
+ if (Extension->IsrWaitMask) {
+
+ if ((Extension->IsrWaitMask & SERIAL_EV_CTS) &&
+ (ModemStatus & SERIAL_MSR_DCTS)) {
+
+ Extension->HistoryMask |= SERIAL_EV_CTS;
+
+ }
+
+ if ((Extension->IsrWaitMask & SERIAL_EV_DSR) &&
+ (ModemStatus & SERIAL_MSR_DDSR)) {
+
+ Extension->HistoryMask |= SERIAL_EV_DSR;
+
+ }
+
+ if ((Extension->IsrWaitMask & SERIAL_EV_RING) &&
+ (ModemStatus & SERIAL_MSR_TERI)) {
+
+ Extension->HistoryMask |= SERIAL_EV_RING;
+
+ }
+
+ if ((Extension->IsrWaitMask & SERIAL_EV_RLSD) &&
+ (ModemStatus & SERIAL_MSR_DDCD)) {
+
+ Extension->HistoryMask |= SERIAL_EV_RLSD;
+
+ }
+
+ if (Extension->IrpMaskLocation &&
+ Extension->HistoryMask) {
+
+ *Extension->IrpMaskLocation =
+ Extension->HistoryMask;
+ Extension->IrpMaskLocation = NULL;
+ Extension->HistoryMask = 0;
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest);
+ reqContext->Information = sizeof(ULONG);
+ SerialInsertQueueDpc(
+ Extension->CommWaitDpc
+ );
+
+ }
+
+ }
+
+ //
+ // If the app has modem line flow control then
+ // we check to see if we have to hold up transmission.
+ //
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_OUT_HANDSHAKEMASK) {
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_CTS_HANDSHAKE) {
+
+ if (ModemStatus & SERIAL_MSR_CTS) {
+
+ Extension->TXHolding &= ~SERIAL_TX_CTS;
+
+ } else {
+
+ Extension->TXHolding |= SERIAL_TX_CTS;
+
+ }
+
+ } else {
+
+ Extension->TXHolding &= ~SERIAL_TX_CTS;
+
+ }
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_DSR_HANDSHAKE) {
+
+ if (ModemStatus & SERIAL_MSR_DSR) {
+
+ Extension->TXHolding &= ~SERIAL_TX_DSR;
+
+ } else {
+
+ Extension->TXHolding |= SERIAL_TX_DSR;
+
+ }
+
+ } else {
+
+ Extension->TXHolding &= ~SERIAL_TX_DSR;
+
+ }
+
+ if (Extension->HandFlow.ControlHandShake &
+ SERIAL_DCD_HANDSHAKE) {
+
+ if (ModemStatus & SERIAL_MSR_DCD) {
+
+ Extension->TXHolding &= ~SERIAL_TX_DCD;
+
+ } else {
+
+ Extension->TXHolding |= SERIAL_TX_DCD;
+
+ }
+
+ } else {
+
+ Extension->TXHolding &= ~SERIAL_TX_DCD;
+
+ }
+
+ //
+ // If we hadn't been holding, and now we are then
+ // queue off a dpc that will lower the RTS line
+ // if we are doing transmit toggling.
+ //
+
+ if (!OldTXHolding && Extension->TXHolding &&
+ ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE)) {
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+ }
+
+ //
+ // We've done any adjusting that needed to be
+ // done to the holding mask given updates
+ // to the modem status. If the Holding mask
+ // is clear (and it wasn't clear to start)
+ // and we have "write" work to do set things
+ // up so that the transmission code gets invoked.
+ //
+
+ if (!DoingTX && OldTXHolding && !Extension->TXHolding) {
+
+ if (!Extension->TXHolding &&
+ (Extension->TransmitImmediate ||
+ Extension->WriteLength) &&
+ Extension->HoldingEmpty) {
+
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ }
+
+ }
+
+ } else {
+
+ //
+ // We need to check if transmission is holding
+ // up because of modem status lines. What
+ // could have occured is that for some strange
+ // reason, the app has asked that we no longer
+ // stop doing output flow control based on
+ // the modem status lines. If however, we
+ // *had* been held up because of the status lines
+ // then we need to clear up those reasons.
+ //
+
+ if (Extension->TXHolding & (SERIAL_TX_DCD |
+ SERIAL_TX_DSR |
+ SERIAL_TX_CTS)) {
+
+ Extension->TXHolding &= ~(SERIAL_TX_DCD |
+ SERIAL_TX_DSR |
+ SERIAL_TX_CTS);
+
+
+ if (!DoingTX && OldTXHolding && !Extension->TXHolding) {
+
+ if (!Extension->TXHolding &&
+ (Extension->TransmitImmediate ||
+ Extension->WriteLength) &&
+ Extension->HoldingEmpty) {
+
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ }
+
+ }
+
+ }
+
+ }
+
+ return ((ULONG)ModemStatus);
+}
+
+BOOLEAN
+SerialPerhapsLowerRTS(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks that the software reasons for lowering
+ the RTS lines are present. If so, it will then cause the
+ line status register to be read (and any needed processing
+ implied by the status register to be done), and if the
+ shift register is empty it will lower the line. If the
+ shift register isn't empty, this routine will queue off
+ a dpc that will start a timer, that will basically call
+ us back to try again.
+
+ NOTE: This routine assumes that it is called at interrupt
+ level.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ //
+ // We first need to test if we are actually still doing
+ // transmit toggle flow control. If we aren't then
+ // we have no reason to try be here.
+ //
+
+ if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ //
+ // The order of the tests is very important below.
+ //
+ // If there is a break then we should leave on the RTS,
+ // because when the break is turned off, it will submit
+ // the code to shut down the RTS.
+ //
+ // If there are writes pending that aren't being held
+ // up, then leave on the RTS, because the end of the write
+ // code will cause this code to be reinvoked. If the writes
+ // are being held up, its ok to lower the RTS because the
+ // upon trying to write the first character after transmission
+ // is restarted, we will raise the RTS line.
+ //
+
+ if ((Extension->TXHolding & SERIAL_TX_BREAK) ||
+ (Extension->CurrentWriteRequest || Extension->TransmitImmediate ||
+ (!IsQueueEmpty(Extension->WriteQueue)) &&
+ (!Extension->TXHolding))) {
+
+ NOTHING;
+
+ } else {
+
+ //
+ // Looks good so far. Call the line status check and processing
+ // code, it will return the "current" line status value. If
+ // the holding and shift register are clear, lower the RTS line,
+ // if they aren't clear, queue of a dpc that will cause a timer
+ // to reinvoke us later. We do this code here because no one
+ // but this routine cares about the characters in the hardware,
+ // so no routine by this routine will bother invoking to test
+ // if the hardware is empty.
+ //
+
+ if ((SerialProcessLSR(Extension) &
+ (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) !=
+ (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) {
+
+ //
+ // Well it's not empty, try again later.
+ //
+
+ SerialInsertQueueDpc(
+ Extension->StartTimerLowerRTSDpc
+ )?Extension->CountOfTryingToLowerRTS++:0;
+
+
+ } else {
+
+ //
+ // Nothing in the hardware, Lower the RTS.
+ //
+
+ SerialClrRTS(Extension->WdfInterrupt, Extension);
+
+
+ }
+
+ }
+
+ }
+
+ //
+ // We decement the counter to indicate that we've reached
+ // the end of the execution path that is trying to push
+ // down the RTS line.
+ //
+
+ Extension->CountOfTryingToLowerRTS--;
+
+ return FALSE;
+}
+
+VOID
+SerialStartTimerLowerRTS(
+ IN WDFDPC Dpc
+ )
+
+/*++
+
+Routine Description:
+
+ This routine starts a timer that when it expires will start
+ a dpc that will check if it can lower the rts line because
+ there are no characters in the hardware.
+
+Arguments:
+
+ Dpc - Not Used.
+
+ DeferredContext - Really points to the device extension.
+
+ SystemContext1 - Not Used.
+
+ SystemContext2 - Not Used.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LARGE_INTEGER CharTime;
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc));
+
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, ">SerialStartTimerLowerRTS(%p)\n",
+ Extension);
+
+
+ //
+ // Since all the callbacks into the driver are serialized, we don't have
+ // synchronize the access to any of the Extension variables.
+ //
+
+ CharTime = SerialGetCharTime(Extension);
+
+ CharTime.QuadPart = -CharTime.QuadPart;
+
+ if (SerialSetTimer(
+ Extension->LowerRTSTimer,
+ CharTime
+ )) {
+
+ //
+ // The timer was already in the timer queue. This implies
+ // that one path of execution that was trying to lower
+ // the RTS has "died". Synchronize with the ISR so that
+ // we can lower the count.
+ //
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialDecrementRTSCounter,
+ Extension
+ );
+
+ }
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "<SerialStartTimerLowerRTS\n");
+
+}
+
+VOID
+SerialInvokePerhapsLowerRTS(
+ IN WDFTIMER Timer
+ )
+
+/*++
+
+Routine Description:
+
+ This dpc routine exists solely to call the code that
+ tests if the rts line should be lowered when TRANSMIT
+ TOGGLE flow control is being used.
+
+Arguments:
+
+ WDFTIMER
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer));
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialPerhapsLowerRTS,
+ Extension
+ );
+
+}
+
+BOOLEAN
+SerialDecrementRTSCounter(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks that the software reasons for lowering
+ the RTS lines are present. If so, it will then cause the
+ line status register to be read (and any needed processing
+ implied by the status register to be done), and if the
+ shift register is empty it will lower the line. If the
+ shift register isn't empty, this routine will queue off
+ a dpc that will start a timer, that will basically call
+ us back to try again.
+
+ NOTE: This routine assumes that it is called at interrupt
+ level.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ Extension->CountOfTryingToLowerRTS--;
+
+ return FALSE;
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/openclos.c b/tests/projects/windows/driver/kmdf/serial/openclos.c new file mode 100644 index 000000000..477f07ac1 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/openclos.c @@ -0,0 +1,850 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ openclos.c
+
+Abstract:
+
+ This module contains the code that is very specific to
+ opening, closing, and cleaning up in the serial driver.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "openclos.tmh"
+#endif
+
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGESER,SerialGetCharTime)
+#pragma alloc_text(PAGESER,SerialEvtFileClose)
+#pragma alloc_text(PAGESER,SerialDrainUART)
+#pragma alloc_text(PAGESRP0,SerialEvtDeviceFileCreate)
+#pragma alloc_text(PAGESRP0,SerialCreateTimersAndDpcs)
+#endif // ALLOC_PRAGMA
+
+
+
+VOID
+SerialEvtDeviceFileCreate (
+ IN WDFDEVICE Device,
+ IN WDFREQUEST Request,
+ IN WDFFILEOBJECT FileObject
+ )
+/*++
+
+Routine Description:
+
+ The framework calls a driver's EvtDeviceFileCreate callback
+ when the framework receives an IRP_MJ_CREATE request.
+ The system sends this request when a user application opens the
+ device to perform an I/O operation, such as reading or writing a file.
+ This callback is called synchronously, in the context of the thread
+ that created the IRP_MJ_CREATE request.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+ FileObject - Pointer to fileobject that represents the open handle.
+ CreateParams - Copy of the Create IO_STACK_LOCATION
+
+Return Value:
+
+ VOID.
+
+--*/
+{
+ NTSTATUS status;
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension (Device);
+
+ UNREFERENCED_PARAMETER(FileObject);
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_CREATE_CLOSE,
+ "SerialEvtDeviceFileCreate %wZ\n", &extension->DeviceName);
+
+ status = SerialDeviceFileCreateWorker(Device);
+
+ //
+ // Complete the WDF request.
+ //
+ WdfRequestComplete(Request, status);
+
+ return;
+
+}
+
+
+NTSTATUS
+SerialWdmDeviceFileCreate (
+ IN WDFDEVICE Device,
+ IN PIRP Irp
+ )
+/*++
+
+Routine Description:
+
+ This is the dispatch routine for IRP_MJ_CREATE. The system sends this
+ request when a user application opens the device to perform an I/O
+ operation, such as reading or writing a file.
+
+Arguments:
+
+ DeviceObject - Pointer to the device object for this device
+ Irp - Pointer to the IRP for the current request
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ NTSTATUS status;
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension (Device);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_CREATE_CLOSE,
+ "SerialWdmDeviceFileCreate %wZ\n", &extension->DeviceName);
+
+ status = SerialDeviceFileCreateWorker(Device);
+
+ //
+ // Complete the WDM request.
+ //
+ Irp->IoStatus.Information = 0L;
+ Irp->IoStatus.Status = status;
+ IoCompleteRequest(Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+
+NTSTATUS
+SerialDeviceFileCreateWorker (
+ IN WDFDEVICE Device
+ )
+{
+ NTSTATUS status;
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension (Device);
+
+ //
+ // Create a buffer for the RX data when no reads are outstanding.
+ //
+
+ extension->InterruptReadBuffer = NULL;
+ extension->BufferSize = 0;
+
+ switch (MmQuerySystemSize()) {
+
+ case MmLargeSystem: {
+
+ extension->BufferSize = 4096;
+ extension->InterruptReadBuffer = ExAllocatePoolWithTag(
+ NonPagedPoolNx,
+ extension->BufferSize,
+ POOL_TAG
+ );
+
+ if (extension->InterruptReadBuffer) {
+ break;
+ }
+
+ }
+
+ case MmMediumSystem: {
+
+ extension->BufferSize = 1024;
+ extension->InterruptReadBuffer = ExAllocatePoolWithTag(
+ NonPagedPoolNx,
+ extension->BufferSize,
+ POOL_TAG
+ );
+
+ if (extension->InterruptReadBuffer) {
+ break;
+ }
+
+ }
+
+ case MmSmallSystem: {
+
+ extension->BufferSize = 128;
+ extension->InterruptReadBuffer = ExAllocatePoolWithTag(
+ NonPagedPoolNx,
+ extension->BufferSize,
+ POOL_TAG
+ );
+
+ }
+
+ }
+
+ if (!extension->InterruptReadBuffer) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ //
+ // By taking a power reference by calling WdfDeviceStopIdle, we prevent the
+ // framework from powering down our device due to idle timeout when there
+ // is an open handle. Power reference also moves the device to D0 if we are
+ // idled out. If you fail create anywhere later in this routine, do make sure
+ // drop the reference.
+ //
+ status = WdfDeviceStopIdle(Device, TRUE);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ //
+ // wakeup is not currently enabled
+ //
+
+ extension->IsWakeEnabled = FALSE;
+
+ //
+ // On a new open we "flush" the read queue by initializing the
+ // count of characters.
+ //
+
+ extension->CharsInInterruptBuffer = 0;
+ extension->LastCharSlot = extension->InterruptReadBuffer +
+ (extension->BufferSize - 1);
+
+ extension->ReadBufferBase = extension->InterruptReadBuffer;
+ extension->CurrentCharSlot = extension->InterruptReadBuffer;
+ extension->FirstReadableChar = extension->InterruptReadBuffer;
+
+ extension->TotalCharsQueued = 0;
+
+ //
+ // We set up the default xon/xoff limits.
+ //
+
+ extension->HandFlow.XoffLimit = extension->BufferSize >> 3;
+ extension->HandFlow.XonLimit = extension->BufferSize >> 1;
+
+ extension->WmiCommData.XoffXmitThreshold = extension->HandFlow.XoffLimit;
+ extension->WmiCommData.XonXmitThreshold = extension->HandFlow.XonLimit;
+
+ extension->BufferSizePt8 = ((3*(extension->BufferSize>>2))+
+ (extension->BufferSize>>4));
+
+ //
+ // Mark the device as busy for WMI
+ //
+
+ extension->WmiCommData.IsBusy = TRUE;
+
+ extension->IrpMaskLocation = NULL;
+ extension->HistoryMask = 0;
+ extension->IsrWaitMask = 0;
+
+ extension->SendXonChar = FALSE;
+ extension->SendXoffChar = FALSE;
+
+#if !DBG
+ //
+ // Clear out the statistics.
+ //
+
+ WdfInterruptSynchronize(
+ extension->WdfInterrupt,
+ SerialClearStats,
+ extension
+ );
+#endif
+
+ //
+ // The escape char replacement must be reset upon every open.
+ //
+
+ extension->EscapeChar = 0;
+
+ //
+ // We don't want the device to be removed or stopped when there is an handle
+ //
+ // Note to anyone copying this sample as a starting point:
+ //
+ // This works in this driver simply because this driver supports exactly
+ // one open handle at a time. If it supported more, then it would need
+ // counting logic to determine when all the reasons for failing Stop/Remove
+ // were gone.
+ //
+ WdfDeviceSetStaticStopRemove(Device, FALSE);
+
+ //
+ // Synchronize with the ISR and let it know that the device
+ // has been successfully opened.
+ //
+
+ WdfInterruptSynchronize(
+ extension->WdfInterrupt,
+ SerialMarkOpen,
+ extension
+ );
+
+ return STATUS_SUCCESS;
+
+}
+
+
+VOID
+SerialEvtFileClose(
+ IN WDFFILEOBJECT FileObject
+ )
+
+/*++
+
+ EvtFileClose is called when all the handles represented by the FileObject
+ is closed and all the references to FileObject is removed. This callback
+ may get called in an arbitrary thread context instead of the thread that
+ called CloseHandle. If you want to delete any per FileObject context that
+ must be done in the context of the user thread that made the Create call,
+ you should do that in the EvtDeviceCleanp callback.
+
+Arguments:
+
+ FileObject - Pointer to fileobject that represents the open handle.
+
+Return Value:
+
+ VOID
+
+--*/
+
+{
+ PAGED_CODE();
+
+ SerialFileCloseWorker(WdfFileObjectGetDevice(FileObject));
+ return;
+}
+
+
+NTSTATUS
+SerialWdmFileClose (
+ IN WDFDEVICE Device,
+ IN PIRP Irp
+ )
+/*++
+
+Routine Description:
+
+ This is the dispatch routine for IRP_MJ_CLOSE. This is called when all the
+ handles represented by the FileObject is closed and all the references to
+ the FileObject is removed.
+
+Arguments:
+
+ DeviceObject - Pointer to the device object for this device
+ Irp - Pointer to the IRP for the current request
+
+Return Value:
+
+ NT status code
+
+--*/
+{
+ SerialFileCloseWorker(Device);
+
+ Irp->IoStatus.Information = 0L;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Irp, IO_NO_INCREMENT);
+
+ return STATUS_SUCCESS;
+}
+
+
+VOID
+SerialFileCloseWorker(
+ IN WDFDEVICE Device
+ )
+{
+ ULONG flushCount;
+
+ //
+ // This "timer value" is used to wait 10 character times
+ // after the hardware is empty before we actually "run down"
+ // all of the flow control/break junk.
+ //
+ LARGE_INTEGER tenCharDelay;
+
+ //
+ // Holds a character time.
+ //
+ LARGE_INTEGER charTime;
+
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(Device);
+ PSERIAL_INTERRUPT_CONTEXT interruptContext = SerialGetInterruptContext(extension->WdfInterrupt);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_CREATE_CLOSE, "In SerialEvtFileClose %wZ\n",
+ &extension->DeviceName);
+
+ //
+ // Acquire the interrupt state lock.
+ //
+ WdfWaitLockAcquire(interruptContext->InterruptStateLock, NULL);
+
+ //
+ // If the Interrupts are connected, then the hardware state has to be
+ // cleaned up now. Note that the EvtFileClose callback gets called for
+ // an open file object even though the interrupts have been disabled
+ // possibly due to a Surprise Remove PNP event. In such a case, the
+ // Interrupt object should not be used.
+ //
+ if (interruptContext->IsInterruptConnected) {
+
+ charTime.QuadPart = -SerialGetCharTime(extension).QuadPart;
+
+ //
+ // Do this now so that if the isr gets called it won't do anything
+ // to cause more chars to get sent. We want to run down the hardware.
+ //
+
+ SetDeviceIsOpened(extension, FALSE, FALSE);
+
+ //
+ // Synchronize with the isr to turn off break if it
+ // is already on.
+ //
+
+ WdfInterruptSynchronize(
+ extension->WdfInterrupt,
+ SerialTurnOffBreak,
+ extension
+ );
+
+ //
+ // Wait a reasonable amount of time (20 * fifodepth) until all characters
+ // have been emptied out of the hardware.
+ //
+
+ for (flushCount = (20 * 16); flushCount != 0; flushCount--) {
+ if ((READ_LINE_STATUS(extension, extension->Controller) &
+ (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) !=
+ (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) {
+
+ KeDelayExecutionThread(KernelMode, FALSE, &charTime);
+ } else {
+ break;
+ }
+ }
+
+ if (flushCount == 0) {
+ SerialMarkHardwareBroken(extension);
+ }
+
+ //
+ // Synchronize with the ISR to let it know that interrupts are
+ // no longer important.
+ //
+
+ WdfInterruptSynchronize(
+ extension->WdfInterrupt,
+ SerialMarkClose,
+ extension
+ );
+
+
+ //
+ // If the driver has automatically transmitted an Xoff in
+ // the context of automatic receive flow control then we
+ // should transmit an Xon.
+ //
+
+ if (extension->RXHolding & SERIAL_RX_XOFF) {
+
+ //
+ // Loop until the holding register is empty.
+ //
+ while (!(READ_LINE_STATUS(extension, extension->Controller) &
+ SERIAL_LSR_THRE)) {
+ KeDelayExecutionThread(
+ KernelMode,
+ FALSE,
+ &charTime
+ );
+
+ }
+
+ WRITE_TRANSMIT_HOLDING(extension,
+ extension->Controller,
+ extension->SpecialChars.XonChar
+ );
+
+ //
+ // Wait a reasonable amount of time for the characters
+ // to be emptied out of the hardware.
+ //
+
+ for (flushCount = (20 * 16); flushCount != 0; flushCount--) {
+ if ((READ_LINE_STATUS(extension, extension->Controller) &
+ (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) !=
+ (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) {
+ KeDelayExecutionThread(KernelMode, FALSE, &charTime);
+ } else {
+ break;
+ }
+ }
+
+ if (flushCount == 0) {
+ SerialMarkHardwareBroken(extension);
+ }
+ }
+
+
+ //
+ // The hardware is empty. Delay 10 character times before
+ // shut down all the flow control.
+ //
+
+ tenCharDelay.QuadPart = charTime.QuadPart * 10;
+
+ KeDelayExecutionThread(
+ KernelMode,
+ TRUE,
+ &tenCharDelay
+ );
+
+#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_LOW, "This warning is because we are calling interrupt synchronize routine directly.")
+ SerialClrDTR(extension->WdfInterrupt, extension);
+
+ //
+ // We have to be very careful how we clear the RTS line.
+ // Transmit toggling might have been on at some point.
+ //
+ // We know that there is nothing left that could start
+ // out the "polling" execution path. We need to
+ // check the counter that indicates that the execution
+ // path is active. If it is then we loop delaying one
+ // character time. After each delay we check to see if
+ // the counter has gone to zero. When it has we know that
+ // the execution path should be just about finished. We
+ // make sure that we still aren't in the routine that
+ // synchronized execution with the ISR by synchronizing
+ // ourselve with the ISR.
+ //
+
+ if (extension->CountOfTryingToLowerRTS) {
+
+ do {
+#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_HIGH, "This warning is due to suppressing the previous one.")
+ KeDelayExecutionThread(
+ KernelMode,
+ FALSE,
+ &charTime
+ );
+
+ } while (extension->CountOfTryingToLowerRTS);
+
+ //
+ // The execution path should no longer exist that
+ // is trying to push down the RTS. Well just
+ // make sure it's down by falling through to
+ // code that forces it down.
+ //
+
+ }
+
+#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_LOW, "This warning is because we are calling interrupt synchronize routine directly.")
+ SerialClrRTS(extension->WdfInterrupt, extension);
+
+ //
+ // Clean out the holding reasons (since we are closed).
+ //
+
+ extension->RXHolding = 0;
+ extension->TXHolding = 0;
+
+ //
+ // Mark device as not busy for WMI
+ //
+
+ extension->WmiCommData.IsBusy = FALSE;
+
+ }
+
+ //
+ // Release the Interrupt state lock.
+ //
+ WdfWaitLockRelease(interruptContext->InterruptStateLock);
+
+ //
+ // All is done. The port has been disabled from interrupting
+ // so there is no point in keeping the memory around.
+ //
+
+ extension->BufferSize = 0;
+ if (extension->InterruptReadBuffer != NULL) {
+ ExFreePool(extension->InterruptReadBuffer);
+ }
+ extension->InterruptReadBuffer = NULL;
+
+ //
+ // Make sure the wake is disabled.
+ //
+ ASSERT(!extension->IsWakeEnabled);
+
+ SerialDrainTimersAndDpcs(extension);
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_CREATE_CLOSE, "DPC's drained:\n");
+
+ //
+ // It's fine for the device to be powered off if there are no open handles.
+ //
+ WdfDeviceResumeIdle(Device);
+
+ //
+ // It's okay to allow the device to be stopped or removed.
+ //
+ // Note to anyone copying this sample as a starting point:
+ //
+ // This works in this driver simply because this driver supports exactly
+ // one open handle at a time. If it supported more, then it would need
+ // counting logic to determine when all the reasons for failing Stop/Remove
+ // were gone.
+ //
+ WdfDeviceSetStaticStopRemove(Device, TRUE);
+
+ return;
+
+}
+
+BOOLEAN
+SerialMarkOpen(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine merely sets a boolean to true to mark the fact that
+ somebody opened the device and its worthwhile to pay attention
+ to interrupts.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ SerialReset(extension->WdfInterrupt, extension);
+
+ //
+ // Prepare for the opening by re-enabling interrupts.
+ //
+ // We do this my modifying the OUT2 line in the modem control.
+ // In PC's this bit is "anded" with the interrupt line.
+ //
+
+ WRITE_MODEM_CONTROL(extension,
+ extension->Controller,
+ (UCHAR)(READ_MODEM_CONTROL(extension, extension->Controller) | SERIAL_MCR_OUT2)
+ );
+
+ extension->DeviceIsOpened = TRUE;
+ extension->ErrorWord = 0;
+
+ return FALSE;
+
+}
+
+VOID
+SerialDrainUART(IN PSERIAL_DEVICE_EXTENSION PDevExt,
+ IN PLARGE_INTEGER PDrainTime)
+{
+ PAGED_CODE();
+
+ //
+ // Wait until all characters have been emptied out of the hardware.
+ //
+
+ while ((READ_LINE_STATUS(PDevExt, PDevExt->Controller) &
+ (SERIAL_LSR_THRE | SERIAL_LSR_TEMT))
+ != (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) {
+ KeDelayExecutionThread(KernelMode, FALSE, PDrainTime);
+ }
+}
+
+VOID
+SerialDisableUART(IN PVOID Context)
+
+/*++
+
+Routine Description:
+
+ This routine disables the UART and puts it in a "safe" state when
+ not in use (like a close or powerdown).
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION extension = Context;
+
+ //
+ // Prepare for the closing by stopping interrupts.
+ //
+ // We do this by adjusting the OUT2 line in the modem control.
+ // In PC's this bit is "anded" with the interrupt line.
+ //
+
+ WRITE_MODEM_CONTROL(extension, extension->Controller,
+ (UCHAR)(READ_MODEM_CONTROL(extension, extension->Controller)
+ & ~SERIAL_MCR_OUT2));
+
+ if (extension->FifoPresent) {
+ WRITE_FIFO_CONTROL(extension, extension->Controller, (UCHAR)0);
+ }
+}
+
+
+
+BOOLEAN
+SerialMarkClose(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine merely sets a boolean to false to mark the fact that
+ somebody closed the device and it's no longer worthwhile to pay attention
+ to interrupts. It also disables the UART.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ SerialDisableUART(Context);
+ extension->DeviceIsOpened = FALSE;
+ extension->DeviceState.Reopen = FALSE;
+
+ return FALSE;
+
+}
+
+LARGE_INTEGER
+SerialGetCharTime(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This function will return the number of 100 nanosecond intervals
+ there are in one character time (based on the present form
+ of flow control.
+
+Arguments:
+
+ Extension - Just what it says.
+
+Return Value:
+
+ 100 nanosecond intervals in a character time.
+
+--*/
+
+{
+ ULONG dataSize = 0;
+ ULONG paritySize;
+ ULONG stopSize;
+ ULONG charTime;
+ ULONG bitTime;
+ LARGE_INTEGER tmp;
+
+ PAGED_CODE();
+
+ if ((Extension->LineControl & SERIAL_DATA_MASK) == SERIAL_5_DATA) {
+ dataSize = 5;
+ } else if ((Extension->LineControl & SERIAL_DATA_MASK)
+ == SERIAL_6_DATA) {
+ dataSize = 6;
+ } else if ((Extension->LineControl & SERIAL_DATA_MASK)
+ == SERIAL_7_DATA) {
+ dataSize = 7;
+ } else if ((Extension->LineControl & SERIAL_DATA_MASK)
+ == SERIAL_8_DATA) {
+ dataSize = 8;
+ }
+
+ paritySize = 1;
+ if ((Extension->LineControl & SERIAL_PARITY_MASK)
+ == SERIAL_NONE_PARITY) {
+
+ paritySize = 0;
+
+ }
+
+ if (Extension->LineControl & SERIAL_2_STOP) {
+
+ //
+ // Even if it is 1.5, for sanities sake were going
+ // to say 2.
+ //
+
+ stopSize = 2;
+
+ } else {
+
+ stopSize = 1;
+
+ }
+
+ //
+ // First we calculate the number of 100 nanosecond intervals
+ // are in a single bit time (Approximately).
+ //
+
+ bitTime = (10000000+(Extension->CurrentBaud-1))/Extension->CurrentBaud;
+ charTime = bitTime + ((dataSize+paritySize+stopSize)*bitTime);
+
+ tmp.QuadPart = charTime;
+ return tmp;
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/pnp.c b/tests/projects/windows/driver/kmdf/serial/pnp.c new file mode 100644 index 000000000..8773f30ac --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/pnp.c @@ -0,0 +1,2804 @@ +/*++
+
+Copyright (c) 1991, 1992, 1993 - 1997 Microsoft Corporation
+
+Module Name:
+
+ pnp.c
+
+Abstract:
+
+ This module contains the code that handles the plug and play
+ IRPs for the serial driver.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+#include <initguid.h>
+#include <ntddser.h>
+#include <stdlib.h>
+
+#if defined(EVENT_TRACING)
+#include "pnp.tmh"
+#endif
+
+static const PHYSICAL_ADDRESS SerialPhysicalZero = {0};
+static const SUPPORTED_BAUD_RATES SupportedBaudRates[] = {
+ {75, SERIAL_BAUD_075},
+ {110, SERIAL_BAUD_110},
+ {135, SERIAL_BAUD_134_5},
+ {150, SERIAL_BAUD_150},
+ {300, SERIAL_BAUD_300},
+ {600, SERIAL_BAUD_600},
+ {1200, SERIAL_BAUD_1200},
+ {1800, SERIAL_BAUD_1800},
+ {2400, SERIAL_BAUD_2400},
+ {4800, SERIAL_BAUD_4800},
+ {7200, SERIAL_BAUD_7200},
+ {9600, SERIAL_BAUD_9600},
+ {14400, SERIAL_BAUD_14400},
+ {19200, SERIAL_BAUD_19200},
+ {38400, SERIAL_BAUD_38400},
+ {56000, SERIAL_BAUD_56K},
+ {57600, SERIAL_BAUD_57600},
+ {115200, SERIAL_BAUD_115200},
+ {128000, SERIAL_BAUD_128K},
+ {SERIAL_BAUD_INVALID, SERIAL_BAUD_USER}
+ };
+
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGESRP0, SerialEvtDeviceAdd)
+#pragma alloc_text(PAGESRP0, SerialEvtPrepareHardware)
+#pragma alloc_text(PAGESRP0, SerialEvtReleaseHardware)
+#pragma alloc_text(PAGESRP0, SerialEvtDeviceD0ExitPreInterruptsDisabled)
+#pragma alloc_text(PAGESRP0, SerialMapHWResources)
+#pragma alloc_text(PAGESRP0, SerialUnmapHWResources)
+#pragma alloc_text(PAGESRP0, SerialEvtDeviceContextCleanup)
+#pragma alloc_text(PAGESRP0, SerialDoExternalNaming)
+#pragma alloc_text(PAGESRP0, SerialReportMaxBaudRate)
+#pragma alloc_text(PAGESRP0, SerialUndoExternalNaming)
+#pragma alloc_text(PAGESRP0, SerialInitController)
+#pragma alloc_text(PAGESRP0, SerialGetMappedAddress)
+#pragma alloc_text(PAGESRP0, SerialSetPowerPolicy)
+#pragma alloc_text(PAGESRP0, SerialReadSymName)
+
+#endif // ALLOC_PRAGMA
+
+PVOID LocalMmMapIoSpace(
+ _In_ PHYSICAL_ADDRESS PhysicalAddress,
+ _In_ SIZE_T NumberOfBytes
+ )
+{
+ typedef
+ PVOID
+ (*PFN_MM_MAP_IO_SPACE_EX) (
+ _In_ PHYSICAL_ADDRESS PhysicalAddress,
+ _In_ SIZE_T NumberOfBytes,
+ _In_ ULONG Protect
+ );
+
+ UNICODE_STRING name;
+ PFN_MM_MAP_IO_SPACE_EX pMmMapIoSpaceEx;
+
+ RtlInitUnicodeString(&name, L"MmMapIoSpaceEx");
+ pMmMapIoSpaceEx = (PFN_MM_MAP_IO_SPACE_EX) (ULONG_PTR)MmGetSystemRoutineAddress(&name);
+
+ if (pMmMapIoSpaceEx != NULL){
+ //
+ // Call WIN10 API if available
+ //
+ return pMmMapIoSpaceEx(PhysicalAddress,
+ NumberOfBytes,
+ PAGE_READWRITE | PAGE_NOCACHE);
+ }
+
+ //
+ // Supress warning that MmMapIoSpace allocates executable memory.
+ // This function is only used if the preferred API, MmMapIoSpaceEx
+ // is not present. MmMapIoSpaceEx is available starting in WIN10.
+ //
+ #pragma warning(suppress: 30029)
+ return MmMapIoSpace(PhysicalAddress, NumberOfBytes, MmNonCached);
+}
+
+NTSTATUS
+SerialEvtDeviceAdd(
+ IN WDFDRIVER Driver,
+ IN PWDFDEVICE_INIT DeviceInit
+ )
+/*++
+
+Routine Description:
+
+ EvtDeviceAdd is called by the framework in response to AddDevice
+ call from the PnP manager.
+
+
+Arguments:
+
+ Driver - Handle to a framework driver object created in DriverEntry
+
+ DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+
+{
+ NTSTATUS status;
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+ static ULONG currentInstance = 0;
+ WDF_FILEOBJECT_CONFIG fileobjectConfig;
+ WDFDEVICE device;
+ WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ WDF_IO_QUEUE_CONFIG queueConfig;
+ WDFQUEUE defaultqueue;
+ ULONG isMulti;
+ PULONG countSoFar;
+ WDF_INTERRUPT_CONFIG interruptConfig;
+ PSERIAL_INTERRUPT_CONTEXT interruptContext;
+ ULONG relinquishPowerPolicy;
+
+ DECLARE_UNICODE_STRING_SIZE(deviceName, DEVICE_OBJECT_NAME_LENGTH);
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "-->SerialEvtDeviceAdd\n");
+
+ status = RtlUnicodeStringPrintf(&deviceName, L"%ws%u",
+ L"\\Device\\Serial",
+ currentInstance++);
+
+
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ status = WdfDeviceInitAssignName(DeviceInit,& deviceName);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ WdfDeviceInitSetExclusive(DeviceInit, TRUE);
+ WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_SERIAL_PORT);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, REQUEST_CONTEXT);
+
+ WdfDeviceInitSetRequestAttributes(DeviceInit, &attributes);
+
+ //
+ // Zero out the PnpPowerCallbacks structure.
+ //
+ WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+
+ //
+ // Set Callbacks for any of the functions we are interested in.
+ // If no callback is set, Framework will take the default action
+ // by itself. These next two callbacks set up and tear down hardware state,
+ // specifically that which only has to be done once.
+ //
+
+ pnpPowerCallbacks.EvtDevicePrepareHardware = SerialEvtPrepareHardware;
+ pnpPowerCallbacks.EvtDeviceReleaseHardware = SerialEvtReleaseHardware;
+
+ //
+ // These two callbacks set up and tear down hardware state that must be
+ // done every time the device moves in and out of the D0-working state.
+ //
+
+ pnpPowerCallbacks.EvtDeviceD0Entry = SerialEvtDeviceD0Entry;
+ pnpPowerCallbacks.EvtDeviceD0Exit = SerialEvtDeviceD0Exit;
+
+ //
+ // Specify the callback for monitoring when the device's interrupt are
+ // enabled or about to be disabled.
+ //
+
+ pnpPowerCallbacks.EvtDeviceD0EntryPostInterruptsEnabled = SerialEvtDeviceD0EntryPostInterruptsEnabled;
+ pnpPowerCallbacks.EvtDeviceD0ExitPreInterruptsDisabled = SerialEvtDeviceD0ExitPreInterruptsDisabled;
+
+ //
+ // Register the PnP and power callbacks.
+ //
+ WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
+
+ if ( !NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "WdfDeviceInitSetPnpPowerEventCallbacks failed %!STATUS!\n",
+ status);
+ return status;
+ }
+
+ //
+ // Find out if we own power policy
+ //
+ SerialGetFdoRegistryKeyValue( DeviceInit,
+ L"SerialRelinquishPowerPolicy",
+ &relinquishPowerPolicy );
+
+ if(relinquishPowerPolicy) {
+ //
+ // FDO's are assumed to be power policy owner by default. So tell
+ // the framework explicitly to relinquish the power policy ownership.
+ //
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "RelinquishPowerPolicy due to registry settings\n");
+
+ WdfDeviceInitSetPowerPolicyOwnership(DeviceInit, FALSE);
+ }
+
+ //
+ // For Windows XP and below, we will register for the WDM Preprocess callback
+ // for IRP_MJ_CREATE. This is done because, the Serenum filter doesn't handle
+ // creates that are marked pending. Since framework always marks the IRP pending,
+ // we are registering this WDM preprocess handler so that we can bypass the
+ // framework and handle the create and close ourself. This workaround is need
+ // only if you intend to install the Serenum as an upper filter.
+ //
+ if (RtlIsNtDdiVersionAvailable(NTDDI_VISTA) == FALSE) {
+
+ status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+ DeviceInit,
+ SerialWdmDeviceFileCreate,
+ IRP_MJ_CREATE,
+ NULL, // pointer minor function table
+ 0); // number of entries in the table
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n",
+ status);
+ return status;
+ }
+
+ status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+ DeviceInit,
+ SerialWdmFileClose,
+ IRP_MJ_CLOSE,
+ NULL, // pointer minor function table
+ 0); // number of entries in the table
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n",
+ status);
+ return status;
+ }
+
+ } else {
+
+ //
+ // FileEvents can opt for Device level synchronization only if the ExecutionLevel
+ // of the Device is passive. Since we can't choose passive execution-level for
+ // device because we have chose to synchronize timers & dpcs with the device,
+ // we will opt out of synchonization with the device for fileobjects.
+ // Note: If the driver has to synchronize Create with the other I/O events,
+ // it can create a queue and configure-dispatch create requests to the queue.
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.SynchronizationScope = WdfSynchronizationScopeNone;
+
+ //
+ // Set Entry points for Create and Close..
+ //
+ WDF_FILEOBJECT_CONFIG_INIT(
+ &fileobjectConfig,
+ SerialEvtDeviceFileCreate,
+ SerialEvtFileClose,
+ WDF_NO_EVENT_CALLBACK // Cleanup
+ );
+
+ WdfDeviceInitSetFileObjectConfig(
+ DeviceInit,
+ &fileobjectConfig,
+ &attributes
+ );
+ }
+
+
+ //
+ // Since framework queues doesn't handle IRP_MJ_FLUSH_BUFFERS,
+ // IRP_MJ_QUERY_INFORMATION and IRP_MJ_SET_INFORMATION requests,
+ // we will register a preprocess callback to handle them.
+ //
+ status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+ DeviceInit,
+ SerialFlush,
+ IRP_MJ_FLUSH_BUFFERS,
+ NULL, // pointer minor function table
+ 0); // number of entries in the table
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n",
+ status);
+ return status;
+ }
+
+ status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+ DeviceInit,
+ SerialQueryInformationFile,
+ IRP_MJ_QUERY_INFORMATION,
+ NULL, // pointer minor function table
+ 0); // number of entries in the table
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n",
+ status);
+ return status;
+ }
+ status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+ DeviceInit,
+ SerialSetInformationFile,
+ IRP_MJ_SET_INFORMATION,
+ NULL, // pointer minor function table
+ 0); // number of entries in the table
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n",
+ status);
+ return status;
+ }
+
+
+ //
+ // Create a device
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE (&attributes,
+ SERIAL_DEVICE_EXTENSION);
+ //
+ // Provide a callback to cleanup the context. This will be called
+ // when the device is removed.
+ //
+ attributes.EvtCleanupCallback = SerialEvtDeviceContextCleanup;
+ //
+ // By opting for SynchronizationScopeDevice, we tell the framework to
+ // synchronize callbacks events of all the objects directly associated
+ // with the device. In this driver, we will associate queues, dpcs,
+ // and timers. By doing that we don't have to worrry about synchronizing
+ // access to device-context by Io Events, cancel-routine, timer and dpc
+ // callbacks.
+ //
+ attributes.SynchronizationScope = WdfSynchronizationScopeDevice;
+
+ status = WdfDeviceCreate(&DeviceInit, &attributes, &device);
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "SerialAddDevice - WdfDeviceCreate failed %!STATUS!\n",
+ status);
+ return status;
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "Created device (%p) %wZ\n", device, &deviceName);
+
+ pDevExt = SerialGetDeviceExtension (device);
+
+ pDevExt->DriverObject = WdfDriverWdmGetDriverObject(Driver);
+
+ //
+ // This sample doesn't support multiport serial devices.
+ // Multiport devices allow other pseudo-serial devices with extra
+ // resources to specify another range of I/O ports.
+ //
+ if(!SerialGetRegistryKeyValue(device, L"MultiportDevice", &isMulti)) {
+ isMulti = 0;
+ }
+
+ if(isMulti) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "This sample doesn't support multiport devices\n");
+ return STATUS_DEVICE_CONFIGURATION_ERROR;
+ }
+
+ //
+ // Set up the device extension.
+ //
+
+ pDevExt = SerialGetDeviceExtension (device);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "AddDevice PDO(0x%p) FDO(0x%p), Lower(0x%p) DevExt (0x%p)\n",
+ WdfDeviceWdmGetPhysicalDevice (device),
+ WdfDeviceWdmGetDeviceObject (device),
+ WdfDeviceWdmGetAttachedDevice(device),
+ pDevExt);
+
+ pDevExt->DeviceIsOpened = FALSE;
+ pDevExt->DeviceObject = WdfDeviceWdmGetDeviceObject(device);
+ pDevExt->WdfDevice = device;
+
+ pDevExt->TxFifoAmount = driverDefaults.TxFIFODefault;
+ pDevExt->UartRemovalDetect = driverDefaults.UartRemovalDetect;
+ pDevExt->CreatedSymbolicLink = FALSE;
+ pDevExt->OwnsPowerPolicy = relinquishPowerPolicy ? FALSE : TRUE;
+
+ status = SerialSetPowerPolicy(pDevExt);
+ if(!NT_SUCCESS(status)){
+ return status;
+ }
+
+ //
+ // We create four manual queues below.
+ // Read Queue..(how about using serial queue for read). Since requests
+ // jump from queue to queue, we cannot configure the queues to receive a
+ // particular type of request. For example, some of the IOCTLs end up
+ // in read and write queue.
+ //
+ WDF_IO_QUEUE_CONFIG_INIT(&queueConfig,
+ WdfIoQueueDispatchManual);
+
+ queueConfig.EvtIoStop = SerialEvtIoStop;
+ queueConfig.EvtIoResume = SerialEvtIoResume;
+ queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue;
+
+ status = WdfIoQueueCreate (device,
+ &queueConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &pDevExt->ReadQueue
+ );
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Read failed %!STATUS!\n", status);
+ return status;
+ }
+
+ //
+ // Write Queue..
+ //
+ WDF_IO_QUEUE_CONFIG_INIT(&queueConfig,
+ WdfIoQueueDispatchManual);
+
+ queueConfig.EvtIoStop = SerialEvtIoStop;
+ queueConfig.EvtIoResume = SerialEvtIoResume;
+ queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue;
+
+ status = WdfIoQueueCreate (device,
+ &queueConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &pDevExt->WriteQueue
+ );
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Write failed %!STATUS!\n", status);
+ return status;
+ }
+
+ //
+ // Mask Queue...
+ //
+ WDF_IO_QUEUE_CONFIG_INIT(&queueConfig,
+ WdfIoQueueDispatchManual
+ );
+
+ queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue;
+
+ queueConfig.EvtIoStop = SerialEvtIoStop;
+ queueConfig.EvtIoResume = SerialEvtIoResume;
+
+ status = WdfIoQueueCreate (device,
+ &queueConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &pDevExt->MaskQueue
+ );
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Mask failed %!STATUS!\n", status);
+ return status;
+ }
+
+ //
+ // Purge Queue..
+ //
+ WDF_IO_QUEUE_CONFIG_INIT(&queueConfig,
+ WdfIoQueueDispatchManual
+ );
+
+ queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue;
+
+ queueConfig.EvtIoStop = SerialEvtIoStop;
+ queueConfig.EvtIoResume = SerialEvtIoResume;
+
+ status = WdfIoQueueCreate (device,
+ &queueConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &pDevExt->PurgeQueue
+ );
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Purge failed %!STATUS!\n", status);
+ return status;
+ }
+
+ //
+ // All the incoming I/O requests are routed to the default queue and dispatch to the
+ // appropriate callback events. These callback event will check to see if another
+ // request is currently active. If so then it will forward it to other manual queues.
+ // All the queues are auto managed by the framework in response to the PNP
+ // and Power events.
+ //
+ WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
+ &queueConfig,
+ WdfIoQueueDispatchParallel
+ );
+ queueConfig.EvtIoRead = SerialEvtIoRead;
+ queueConfig.EvtIoWrite = SerialEvtIoWrite;
+ queueConfig.EvtIoDeviceControl = SerialEvtIoDeviceControl;
+ queueConfig.EvtIoInternalDeviceControl = SerialEvtIoInternalDeviceControl;
+ queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue;
+
+ queueConfig.EvtIoStop = SerialEvtIoStop;
+ queueConfig.EvtIoResume = SerialEvtIoResume;
+
+ status = WdfIoQueueCreate(device,
+ &queueConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &defaultqueue
+ );
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfIoQueueCreate failed %!STATUS!\n", status);
+ return status;
+ }
+
+ //
+ // Create WDFINTERRUPT object. Let us leave the ShareVector to default value and
+ // let the framework decide whether to share the interrupt or not based on the
+ // ShareDisposition provided by the bus driver in the resource descriptor.
+ //
+
+ WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,
+ SerialISR,
+ NULL);
+
+ interruptConfig.EvtInterruptDisable = SerialEvtInterruptDisable;
+ interruptConfig.EvtInterruptEnable = SerialEvtInterruptEnable;
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SERIAL_INTERRUPT_CONTEXT);
+
+ status = WdfInterruptCreate(device,
+ &interruptConfig,
+ &attributes,
+ &pDevExt->WdfInterrupt);
+
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't create interrupt for %wZ\n",
+ &pDevExt->DeviceName);
+ return status;
+ }
+
+ //
+ // Interrupt state wait lock...
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = pDevExt->WdfInterrupt;
+
+ interruptContext = SerialGetInterruptContext(pDevExt->WdfInterrupt);
+
+ status = WdfWaitLockCreate(&attributes,
+ &interruptContext->InterruptStateLock
+ );
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfWaitLockCreate for InterruptStateLock failed %!STATUS!\n", status);
+ return status;
+ }
+
+ //
+ // Set interrupt policy
+ //
+ SerialSetInterruptPolicy(pDevExt->WdfInterrupt);
+
+ //
+ // Timers and DPCs...
+ //
+ status = SerialCreateTimersAndDpcs(pDevExt);
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "SerialCreateTimersAndDpcs failed %x\n", status);
+ return status;
+ }
+
+ //
+ // Register with WMI.
+ //
+ status = SerialWmiRegistration(device);
+ if(!NT_SUCCESS (status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "SerialWmiRegistration failed %!STATUS!\n", status);
+ return status;
+
+ }
+
+ //
+ // Upto this point, if we fail, we don't have to worry about freeing any resource because
+ // framework will free all the objects.
+ //
+ //
+ // Do the external naming.
+ //
+
+ status = SerialDoExternalNaming(pDevExt);
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "External Naming Failed - Status %!STATUS!\n",
+ status);
+ return status;
+ }
+
+ //
+ // Finally increment the global system configuration that keeps track of number of serial ports.
+ //
+ countSoFar = &IoGetConfigurationInformation()->SerialCount;
+ (*countSoFar)++;
+ pDevExt->IsSystemConfigInfoUpdated = TRUE;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<--SerialEvtDeviceAdd\n");
+
+ return status;
+
+}
+#pragma warning(push)
+#pragma warning(disable:28118) // this callback will run at IRQL=PASSIVE_LEVEL
+_Use_decl_annotations_
+VOID
+SerialEvtDeviceContextCleanup (
+ WDFOBJECT Device
+ )
+/*++
+
+Routine Description:
+
+ EvtDeviceContextCleanup event callback cleans up anything done in
+ EvtDeviceAdd, except those things that are automatically cleaned
+ up by the Framework.
+
+ In a driver derived from this sample, it's quite likely that this function could
+ be deleted.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION deviceExtension;
+ PULONG countSoFar;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialDeviceContextCleanup\n");
+
+ PAGED_CODE();
+
+ deviceExtension = SerialGetDeviceExtension (Device);
+
+ if (deviceExtension->InterruptReadBuffer != NULL) {
+ ExFreePool(deviceExtension->InterruptReadBuffer);
+ deviceExtension->InterruptReadBuffer = NULL;
+ }
+
+ //
+ // Update the global configuration count for serial device.
+ //
+ if(deviceExtension->IsSystemConfigInfoUpdated) {
+ countSoFar = &IoGetConfigurationInformation()->SerialCount;
+ (*countSoFar)--;
+ }
+
+ SerialUndoExternalNaming(deviceExtension);
+
+ return;
+}
+#pragma warning(pop) // enable 28118 again
+
+NTSTATUS
+SerialEvtPrepareHardware(
+ WDFDEVICE Device,
+ WDFCMRESLIST Resources,
+ WDFCMRESLIST ResourcesTranslated
+ )
+/*++
+
+Routine Description:
+
+ SerialEvtPrepareHardware event callback performs operations that are necessary
+ to make the device operational. The framework calls the driver's
+ SerialEvtPrepareHardware callback when the PnP manager sends an IRP_MN_START_DEVICE
+ request to the driver stack.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ Resources - Handle to a collection of framework resource objects.
+ This collection identifies the raw (bus-relative) hardware
+ resources that have been assigned to the device.
+
+ ResourcesTranslated - Handle to a collection of framework resource objects.
+ This collection identifies the translated (system-physical)
+ hardware resources that have been assigned to the device.
+ The resources appear from the CPU's point of view.
+ Use this list of resources to map I/O space and
+ device-accessible memory into virtual address space
+
+Return Value:
+
+ WDF status code
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+ NTSTATUS status;
+ CONFIG_DATA config;
+ PCONFIG_DATA pConfig = &config;
+ ULONG defaultClockRate = 1843200;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx (TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialEvtPrepareHardware\n");
+ //
+ // Get the Device Extension..
+ //
+ pDevExt = SerialGetDeviceExtension (Device);
+
+ RtlZeroMemory(pConfig, sizeof(CONFIG_DATA));
+
+ //
+ // Initialize a config data structure with default values for those that
+ // may not already be initialized.
+ //
+
+ pConfig->LogFifo = driverDefaults.LogFifoDefault;
+
+
+ //
+ // Get the hw resources for the device.
+ //
+
+ status = SerialMapHWResources(Device, Resources, ResourcesTranslated, pConfig);
+
+ if (!NT_SUCCESS(status)) {
+ goto End;
+ }
+
+ //
+ // Open the "Device Parameters" section of registry for this device and get parameters.
+ //
+
+ if(!SerialGetRegistryKeyValue (Device,
+ L"DisablePort",
+ &pConfig->DisablePort)){
+ pConfig->DisablePort = 0;
+ }
+
+ if(!SerialGetRegistryKeyValue (Device,
+ L"ForceFifoEnable",
+ &pConfig->ForceFifoEnable)){
+ pConfig->ForceFifoEnable = driverDefaults.ForceFifoEnableDefault;
+ }
+
+ if(!SerialGetRegistryKeyValue (Device,
+ L"RxFIFO",
+ &pConfig->RxFIFO)){
+ pConfig->RxFIFO = driverDefaults.RxFIFODefault;
+ }
+
+ if(!SerialGetRegistryKeyValue (Device,
+ L"TxFIFO",
+ &pConfig->TxFIFO)){
+ pConfig->TxFIFO = driverDefaults.TxFIFODefault;
+ }
+
+ if(!SerialGetRegistryKeyValue (Device,
+ L"Share System Interrupt",
+ &pConfig->PermitShare)){
+ pConfig->PermitShare = driverDefaults.PermitShareDefault;
+ }
+
+ if(!SerialGetRegistryKeyValue (Device,
+ L"ClockRate",
+ &pConfig->ClockRate)) {
+ pConfig->ClockRate = defaultClockRate;
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "Com Port ClockRate: %x\n",
+ pConfig->ClockRate);
+
+ if(!SerialGetRegistryKeyValue(Device,
+ L"TL16C550C Auto Flow Control",
+ &pConfig->TL16C550CAFC)){
+ pConfig->TL16C550CAFC = 0;
+ }
+
+ status = SerialInitController(pDevExt, pConfig);
+
+ if (NT_SUCCESS(status)) {
+ }
+End:
+
+ SerialDbgPrintEx (TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialEvtPrepareHardware 0x%x\n", status);
+
+ return status;
+}
+
+NTSTATUS
+SerialEvtReleaseHardware(
+ IN WDFDEVICE Device,
+ IN WDFCMRESLIST ResourcesTranslated
+ )
+/*++
+
+Routine Description:
+
+ EvtDeviceReleaseHardware is called by the framework whenever the PnP manager
+ is revoking ownership of our resources. This may be in response to either
+ IRP_MN_STOP_DEVICE or IRP_MN_REMOVE_DEVICE. The callback is made before
+ passing down the IRP to the lower driver.
+
+ In this callback, do anything necessary to free those resources.
+ In this driver, we will not receive this callback when there is open handle to
+ the device. We explicitly tell the framework (WdfDeviceSetStaticStopRemove) to
+ fail stop and query-remove when handle is open.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ ResourcesTranslated - Handle to a collection of framework resource objects.
+ This collection identifies the translated (system-physical)
+ hardware resources that have been assigned to the device.
+ The resources appear from the CPU's point of view.
+ Use this list of resources to map I/O space and
+ device-accessible memory into virtual address space
+
+Return Value:
+
+ NTSTATUS - Failures will be logged, but not acted on.
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+
+ UNREFERENCED_PARAMETER(ResourcesTranslated);
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "--> SerialEvtReleaseHardware\n");
+
+ pDevExt = SerialGetDeviceExtension (Device);
+
+ //
+ // Reset and put the device into a known initial state before releasing the hw resources.
+ // In this driver we can recieve this callback only when there is no handle open because
+ // we tell the framework to disable stop by calling WdfDeviceSetStaticStopRemove.
+ // Since we have already reset the device in our close handler, we don't have to
+ // do anything other than unmapping the I/O resources.
+ //
+
+ //
+ // Unmap any Memory-Mapped registers. Disconnecting from the interrupt will
+ // be done automatically by the framework.
+ //
+ SerialUnmapHWResources(pDevExt);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "<-- SerialEvtReleaseHardware\n");
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+SerialEvtDeviceD0EntryPostInterruptsEnabled(
+ IN WDFDEVICE Device,
+ IN WDF_POWER_DEVICE_STATE PreviousState
+ )
+/*++
+
+Routine Description:
+
+ EvtDeviceD0EntryPostInterruptsEnabled is called by the framework after the
+ driver has enabled the device's hardware interrupts.
+
+ This function is not marked pageable because this function is in the
+ device power up path. When a function is marked pagable and the code
+ section is paged out, it will generate a page fault which could impact
+ the fast resume behavior because the client driver will have to wait
+ until the system drivers can service this page fault.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ PreviousState - A WDF_POWER_DEVICE_STATE-typed enumerator that identifies
+ the previous device power state.
+
+Return Value:
+
+ NTSTATUS - Failures will be logged, but not acted on.
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(Device);
+ PSERIAL_INTERRUPT_CONTEXT interruptContext = SerialGetInterruptContext(extension->WdfInterrupt);
+ WDF_INTERRUPT_INFO info;
+
+ UNREFERENCED_PARAMETER(PreviousState);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "--> SerialEvtDeviceD0EntryPostInterruptsEnabled\n");
+ //
+ // The following lines of code show how to call WdfInterruptGetInfo.
+ //
+ WDF_INTERRUPT_INFO_INIT(&info);
+ WdfInterruptGetInfo(extension->WdfInterrupt, &info);
+
+ WdfWaitLockAcquire(interruptContext->InterruptStateLock, NULL);
+ interruptContext->IsInterruptConnected = TRUE;
+ WdfWaitLockRelease(interruptContext->InterruptStateLock);
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+SerialEvtDeviceD0ExitPreInterruptsDisabled(
+ IN WDFDEVICE Device,
+ IN WDF_POWER_DEVICE_STATE TargetState
+ )
+/*++
+
+Routine Description:
+
+ EvtDeviceD0ExitPreInterruptsDisabled is called by the framework before the
+ driver disables the device's hardware interrupts.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ TargetState - A WDF_POWER_DEVICE_STATE-typed enumerator that identifies the
+ device power state that the device is about to enter.
+
+Return Value:
+
+ NTSTATUS - Failures will be logged, but not acted on.
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(Device);
+ PSERIAL_INTERRUPT_CONTEXT interruptContext = SerialGetInterruptContext(extension->WdfInterrupt);
+
+ UNREFERENCED_PARAMETER(TargetState);
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "--> SerialEvtDeviceD0ExitPreInterruptsDisabled\n");
+
+ WdfWaitLockAcquire(interruptContext->InterruptStateLock, NULL);
+ interruptContext->IsInterruptConnected = FALSE;
+ WdfWaitLockRelease(interruptContext->InterruptStateLock);
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+SerialSetPowerPolicy(
+ IN PSERIAL_DEVICE_EXTENSION DeviceExtension
+ )
+{
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings;
+ //WDF_POWER_POLICY_EVENT_CALLBACKS powerPolicyCallbacks;
+ NTSTATUS status = STATUS_SUCCESS;
+ WDFDEVICE hDevice = DeviceExtension->WdfDevice;
+ ULONG powerOnClose;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "--> SerialSetPowerPolicy\n");
+
+ PAGED_CODE();
+
+ //
+ // Find out whether we want to power down the device when there no handles open.
+ //
+ SerialGetRegistryKeyValue(hDevice, L"EnablePowerManagement", &powerOnClose);
+ DeviceExtension->RetainPowerOnClose = powerOnClose ? TRUE : FALSE;
+
+ //
+ // In some drivers, the device must be specifically programmed to enable
+ // wake signals. UARTs were designed long, long before such a concept. So
+ // this driver, which just drives UARTs, doesn't register wake arm/disarm
+ // callbacks. Arming or disarming for UARTs has to be handled by side-band
+ // code that controls hardware designed more recently. In this case, ACPI
+ // is handling it. If one were to write a driver which implemented a more
+ // modern serial device, one might need to use these callbacks.
+ //
+
+ //
+ // Init the power policy callbacks
+ //
+ //WDF_POWER_POLICY_EVENT_CALLBACKS_INIT(&powerPolicyCallbacks);
+
+ //
+ // This group of three callbacks allows this sample driver to manage
+ // arming the device for wake from the S0 state.
+ //
+
+ //powerPolicyCallbacks.EvtDeviceArmWakeFromS0 = SerialEvtDeviceWakeArmS0;
+ //powerPolicyCallbacks.EvtDeviceDisarmWakeFromS0 = SerialEvtDeviceWakeDisarmS0;
+ //powerPolicyCallbacks.EvtDeviceWakeFromS0Triggered = SerialEvtDeviceWakeTriggeredS0;
+
+ //
+ // This group of three callbacks allows the device to be armed for wake
+ // from Sx (S1, S2, S3 or S4.) Networking devices can optionally be put
+ // into a state where a packet sent to them will cause the device's wake
+ // signal to be triggered, which causes the machine to wake, moving back
+ // into the S0 state.
+ //
+
+ //powerPolicyCallbacks.EvtDeviceArmWakeFromSx = SerialEvtDeviceWakeArmSx;
+ //powerPolicyCallbacks.EvtDeviceDisarmWakeFromSx = SerialEvtDeviceWakeDisarmSx;
+ //powerPolicyCallbacks.EvtDeviceWakeFromSxTriggered = SerialEvtDeviceWakeTriggeredSx;
+
+ //
+ // Register the power policy callbacks.
+ //
+ //WdfDeviceSetPowerPolicyEventCallbacks(hDevice, &powerPolicyCallbacks);
+
+ //
+ // Init the idle policy structure. By setting IdleCannotWakeFromS0 we tell the framework
+ // to power down the device without arming for wake. The only way the device can come
+ // back to D0 is when we call WdfDeviceStopIdle in SerialEvtDeviceFileCreate.
+ // We can't choose IdleCanWakeFromS0 by default is because onboard serial ports typically
+ // don't have wake capability. If the driver is used for plugin boards that does support
+ // wait-wake, you can update the settings to match that. If MS provided modem driver
+ // is used on ports that does support wake on ring, then it will update the settings
+ // by sending an internal ioctl to us.
+ //
+ WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0);
+ if(DeviceExtension->OwnsPowerPolicy && !DeviceExtension->RetainPowerOnClose) {
+ //
+ // Since we don't have to retain power when there are no open handles, we
+ // register for idle power management to save power. Check the use of
+ // WdfDeviceStopIdle in SerialEvtDeviceFileCreate.
+ //
+ idleSettings.UserControlOfIdleSettings = IdleAllowUserControl;
+
+ status = WdfDeviceAssignS0IdleSettings(hDevice, &idleSettings);
+ if ( !NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "WdfDeviceSetPowerPolicyS0IdlePolicy failed %x \n", status);
+ return status;
+ }
+ }
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialSetPowerPolicy\n");
+
+ return status;
+}
+
+UINT32
+SerialReportMaxBaudRate(ULONG Bauds)
+/*++
+
+Routine Description:
+
+ This routine returns the max baud rate given a selection of rates
+
+Arguments:
+
+ Bauds - Bit-encoded list of supported bauds
+
+
+ Return Value:
+
+ The max baud rate listed in Bauds
+
+--*/
+{
+ int i;
+
+ PAGED_CODE();
+
+ for(i=0; SupportedBaudRates[i].BaudRate != SERIAL_BAUD_INVALID; i++) {
+
+ if(Bauds & SupportedBaudRates[i].Mask) {
+ return SupportedBaudRates[i].BaudRate;
+ }
+ }
+
+ //
+ // We're in bad shape
+ //
+
+ return 0;
+}
+
+NTSTATUS
+SerialInitController(
+ IN PSERIAL_DEVICE_EXTENSION pDevExt,
+ IN PCONFIG_DATA PConfigData
+ )
+/*++
+
+Routine Description:
+
+ Really too many things to mention here. In general initializes
+ kernel synchronization structures, allocates the typeahead buffer,
+ sets up defaults, etc.
+
+Arguments:
+
+ PDevObj - Device object for the device to be started
+
+ PConfigData - Pointer to a record for a single port.
+
+Return Value:
+
+ STATUS_SUCCCESS if everything went ok. A !NT_SUCCESS status
+ otherwise.
+
+--*/
+
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ SHORT junk;
+ int i;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialInitController for %wZ\n",
+ &pDevExt->DeviceName);
+
+ //
+ // Save the value of clock input to the part. We use this to calculate
+ // the divisor latch value. The value is in Hertz.
+ //
+
+ pDevExt->ClockRate = PConfigData->ClockRate;
+
+
+ //
+ // Save if we have to enable TI's auto flow control
+ //
+
+
+ pDevExt->TL16C550CAFC = PConfigData->TL16C550CAFC;
+
+
+ //
+ // Map the memory for the control registers for the serial device
+ // into virtual memory.
+ //
+ pDevExt->Controller =
+ SerialGetMappedAddress(PConfigData->TrController,
+ PConfigData->SpanOfController,
+ (BOOLEAN)PConfigData->AddressSpace,
+ &pDevExt->UnMapRegisters);
+
+
+ if (!pDevExt->Controller) {
+
+ SerialLogError(
+ pDevExt->DriverObject,
+ pDevExt->DeviceObject,
+ PConfigData->TrController,
+ SerialPhysicalZero,
+ 0,
+ 0,
+ 0,
+ 7,
+ STATUS_SUCCESS,
+ SERIAL_REGISTERS_NOT_MAPPED,
+ pDevExt->DeviceName.Length+sizeof(WCHAR),
+ pDevExt->DeviceName.Buffer,
+ 0,
+ NULL
+ );
+
+ SerialDbgPrintEx(TRACE_LEVEL_WARNING, DBG_PNP, "Could not map memory for device "
+ "registers for %wZ\n", &pDevExt->DeviceName);
+
+ pDevExt->UnMapRegisters = FALSE;
+ status = STATUS_NONE_MAPPED;
+ goto ExtensionCleanup;
+
+ }
+
+ pDevExt->AddressSpace = PConfigData->AddressSpace;
+ pDevExt->SpanOfController = PConfigData->SpanOfController;
+
+ //
+ // Save off the interface type and the bus number.
+ //
+
+ pDevExt->Vector = PConfigData->TrVector;
+ pDevExt->Irql = (UCHAR)PConfigData->TrIrql;
+ pDevExt->InterruptMode = PConfigData->InterruptMode;
+ pDevExt->Affinity = PConfigData->Affinity;
+
+ //
+ // If the user said to permit sharing within the device, propagate this
+ // through.
+ //
+
+ pDevExt->PermitShare = PConfigData->PermitShare;
+
+
+ //
+ // Before we test whether the port exists (which will enable the FIFO)
+ // convert the rx trigger value to what should be used in the register.
+ //
+ // If a bogus value was given - crank them down to 1.
+ //
+ // If this is a "souped up" UART with like a 64 byte FIFO, they
+ // should use the appropriate "spoofing" value to get the desired
+ // results. I.e., if on their chip 0xC0 in the FCR is for 64 bytes,
+ // they should specify 14 in the registry.
+ //
+
+ switch (PConfigData->RxFIFO) {
+
+ case 1:
+
+ pDevExt->RxFifoTrigger = SERIAL_1_BYTE_HIGH_WATER;
+ break;
+
+ case 4:
+
+ pDevExt->RxFifoTrigger = SERIAL_4_BYTE_HIGH_WATER;
+ break;
+
+ case 8:
+
+ pDevExt->RxFifoTrigger = SERIAL_8_BYTE_HIGH_WATER;
+ break;
+
+ case 14:
+
+ pDevExt->RxFifoTrigger = SERIAL_14_BYTE_HIGH_WATER;
+ break;
+
+ default:
+
+ pDevExt->RxFifoTrigger = SERIAL_1_BYTE_HIGH_WATER;
+ break;
+
+ }
+
+
+ if (PConfigData->TxFIFO < 1) {
+
+ pDevExt->TxFifoAmount = 1;
+
+ } else {
+
+ pDevExt->TxFifoAmount = PConfigData->TxFIFO;
+
+ }
+
+ if (!SerialDoesPortExist(
+ pDevExt,
+ &pDevExt->DeviceName,
+ PConfigData->ForceFifoEnable,
+ PConfigData->LogFifo
+ )) {
+
+ //
+ // We couldn't verify that there was actually a
+ // port. No need to log an error as the port exist
+ // code will log exactly why.
+ //
+
+ SerialDbgPrintEx(TRACE_LEVEL_WARNING, DBG_PNP, "DoesPortExist test failed for "
+ "%wZ\n", &pDevExt->DeviceName);
+
+ status = STATUS_NO_SUCH_DEVICE;
+ goto ExtensionCleanup;
+
+ }
+
+
+ //
+ // If the user requested that we disable the port, then
+ // do it now. Log the fact that the port has been disabled.
+ //
+
+ if (PConfigData->DisablePort) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "disabled port %wZ as requested in "
+ "configuration\n", &pDevExt->DeviceName);
+
+ status = STATUS_NO_SUCH_DEVICE;
+
+ SerialLogError(
+ pDevExt->DriverObject,
+ pDevExt->DeviceObject,
+ PConfigData->TrController,
+ SerialPhysicalZero,
+ 0,
+ 0,
+ 0,
+ 57,
+ STATUS_SUCCESS,
+ SERIAL_DISABLED_PORT,
+ pDevExt->DeviceName.Length+sizeof(WCHAR),
+ pDevExt->DeviceName.Buffer,
+ 0,
+ NULL
+ );
+
+ goto ExtensionCleanup;
+
+ }
+
+
+
+ //
+ // Set up the default device control fields.
+ // Note that if the values are changed after
+ // the file is open, they do NOT revert back
+ // to the old value at file close.
+ //
+
+ pDevExt->SpecialChars.XonChar = SERIAL_DEF_XON;
+ pDevExt->SpecialChars.XoffChar = SERIAL_DEF_XOFF;
+ pDevExt->HandFlow.ControlHandShake = SERIAL_DTR_CONTROL;
+ pDevExt->HandFlow.FlowReplace = SERIAL_RTS_CONTROL;
+
+
+ //
+ // Default Line control protocol. 7E1
+ //
+ // Seven data bits.
+ // Even parity.
+ // 1 Stop bits.
+ //
+
+ pDevExt->LineControl = SERIAL_7_DATA |
+ SERIAL_EVEN_PARITY |
+ SERIAL_NONE_PARITY;
+
+ pDevExt->ValidDataMask = 0x7f;
+ pDevExt->CurrentBaud = 1200;
+
+
+ //
+ // We set up the default xon/xoff limits.
+ //
+ // This may be a bogus value. It looks like the BufferSize
+ // is not set up until the device is actually opened.
+ //
+
+ pDevExt->HandFlow.XoffLimit = pDevExt->BufferSize >> 3;
+ pDevExt->HandFlow.XonLimit = pDevExt->BufferSize >> 1;
+
+ pDevExt->BufferSizePt8 = ((3*(pDevExt->BufferSize>>2))+
+ (pDevExt->BufferSize>>4));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, " The default interrupt read buffer size is: %d\n"
+ "------ The XoffLimit is : %d\n"
+ "------ The XonLimit is : %d\n"
+ "------ The pt 8 size is : %d\n",
+ pDevExt->BufferSize, pDevExt->HandFlow.XoffLimit,
+ pDevExt->HandFlow.XonLimit, pDevExt->BufferSizePt8);
+
+
+ //
+ // Go through all the "named" baud rates to find out which ones
+ // can be supported with this port.
+ //
+ //
+
+ pDevExt->SupportedBauds = SERIAL_BAUD_USER;
+
+
+ for(i=0; SupportedBaudRates[i].BaudRate != SERIAL_BAUD_INVALID; i++) {
+
+ if (!NT_ERROR(SerialGetDivisorFromBaud(
+ pDevExt->ClockRate,
+ (LONG)SupportedBaudRates[i].BaudRate,
+ &junk
+ ))) {
+
+ pDevExt->SupportedBauds |= SupportedBaudRates[i].Mask;
+ }
+ }
+
+
+
+
+ //
+ // Mark this device as not being opened by anyone. We keep a
+ // variable around so that spurious interrupts are easily
+ // dismissed by the ISR.
+ //
+
+ SetDeviceIsOpened(pDevExt, FALSE, FALSE);
+
+ //
+ // Store values into the extension for interval timing.
+ //
+
+ //
+ // If the interval timer is less than a second then come
+ // in with a short "polling" loop.
+ //
+ // For large (> then 2 seconds) use a 1 second poller.
+ //
+
+ pDevExt->ShortIntervalAmount.QuadPart = -1;
+ pDevExt->LongIntervalAmount.QuadPart = -10000000;
+ pDevExt->CutOverAmount.QuadPart = 200000000;
+
+ DISABLE_ALL_INTERRUPTS (pDevExt, pDevExt->Controller);
+
+ WRITE_MODEM_CONTROL(pDevExt, pDevExt->Controller, (UCHAR)0);
+
+ // make sure there is no escape character currently set
+ pDevExt->EscapeChar = 0;
+ //
+ // This should set up everything as it should be when
+ // a device is to be opened. We do need to lower the
+ // modem lines, and disable the recalcitrant fifo
+ // so that it will show up if the user boots to dos.
+ //
+
+ // __WARNING_IRQ_SET_TOO_HIGH: we are calling interrupt synchronize routine directly. Suppress it because interrupt is not connected yet.
+ // __WARNING_INVALID_PARAM_VALUE_1: Interrupt is UNREFERENCED_PARAMETER, so it can be NULL
+#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1)
+ SerialReset(NULL, pDevExt);
+
+#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1)
+ SerialMarkClose(NULL, pDevExt);
+
+#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1)
+ SerialClrRTS(NULL, pDevExt);
+
+#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1)
+ SerialClrDTR(NULL, pDevExt);
+
+ //
+ // Fill in WMI hardware data
+ //
+ pDevExt->WmiHwData.IrqNumber = pDevExt->Irql;
+ pDevExt->WmiHwData.IrqLevel = pDevExt->Irql;
+ pDevExt->WmiHwData.IrqVector = pDevExt->Vector;
+ pDevExt->WmiHwData.IrqAffinityMask = pDevExt->Affinity;
+ pDevExt->WmiHwData.InterruptType = pDevExt->InterruptMode == Latched
+ ? SERIAL_WMI_INTTYPE_LATCHED : SERIAL_WMI_INTTYPE_LEVEL;
+ pDevExt->WmiHwData.BaseIOAddress = (ULONG_PTR)pDevExt->Controller;
+
+ //
+ // Fill in WMI device state data (as defaults)
+ //
+
+ pDevExt->WmiCommData.BaudRate = pDevExt->CurrentBaud;
+ pDevExt->WmiCommData.BitsPerByte = (pDevExt->LineControl & 0x03) + 5;
+ pDevExt->WmiCommData.ParityCheckEnable = (pDevExt->LineControl & 0x08)
+ ? TRUE : FALSE;
+
+ switch (pDevExt->LineControl & SERIAL_PARITY_MASK) {
+ case SERIAL_NONE_PARITY:
+ pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_NONE;
+ break;
+
+ case SERIAL_ODD_PARITY:
+ pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_ODD;
+ break;
+
+ case SERIAL_EVEN_PARITY:
+ pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_EVEN;
+ break;
+
+ case SERIAL_MARK_PARITY:
+ pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_MARK;
+ break;
+
+ case SERIAL_SPACE_PARITY:
+ pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_SPACE;
+ break;
+
+ default:
+ ASSERTMSG(0, "Illegal Parity setting for WMI");
+ pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_NONE;
+ break;
+ }
+
+ pDevExt->WmiCommData.StopBits = pDevExt->LineControl & SERIAL_STOP_MASK
+ ? (pDevExt->WmiCommData.BitsPerByte == 5 ? SERIAL_WMI_STOP_1_5
+ : SERIAL_WMI_STOP_2) : SERIAL_WMI_STOP_1;
+ pDevExt->WmiCommData.XoffCharacter = pDevExt->SpecialChars.XoffChar;
+ pDevExt->WmiCommData.XoffXmitThreshold = pDevExt->HandFlow.XoffLimit;
+ pDevExt->WmiCommData.XonCharacter = pDevExt->SpecialChars.XonChar;
+ pDevExt->WmiCommData.XonXmitThreshold = pDevExt->HandFlow.XonLimit;
+ pDevExt->WmiCommData.MaximumBaudRate
+ = SerialReportMaxBaudRate(pDevExt->SupportedBauds);
+ pDevExt->WmiCommData.MaximumOutputBufferSize = (UINT32)((ULONG)-1);
+ pDevExt->WmiCommData.MaximumInputBufferSize = (UINT32)((ULONG)-1);
+ pDevExt->WmiCommData.Support16BitMode = FALSE;
+ pDevExt->WmiCommData.SupportDTRDSR = TRUE;
+ pDevExt->WmiCommData.SupportIntervalTimeouts = TRUE;
+ pDevExt->WmiCommData.SupportParityCheck = TRUE;
+ pDevExt->WmiCommData.SupportRTSCTS = TRUE;
+ pDevExt->WmiCommData.SupportXonXoff = TRUE;
+ pDevExt->WmiCommData.SettableBaudRate = TRUE;
+ pDevExt->WmiCommData.SettableDataBits = TRUE;
+ pDevExt->WmiCommData.SettableFlowControl = TRUE;
+ pDevExt->WmiCommData.SettableParity = TRUE;
+ pDevExt->WmiCommData.SettableParityCheck = TRUE;
+ pDevExt->WmiCommData.SettableStopBits = TRUE;
+ pDevExt->WmiCommData.IsBusy = FALSE;
+
+ //
+ // Common error path cleanup. If the status is
+ // bad, get rid of the device extension, device object
+ // and any memory associated with it.
+ //
+
+ExtensionCleanup: ;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialInitController %x\n", status);
+
+ return status;
+}
+
+
+NTSTATUS
+SerialMapHWResources(
+ IN WDFDEVICE Device,
+ IN WDFCMRESLIST PResList,
+ IN WDFCMRESLIST PTrResList,
+ OUT PCONFIG_DATA PConfig
+ )
+/*++
+
+Routine Description:
+
+ This routine will get the configuration information and put
+ it and the translated values into CONFIG_DATA structures.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ Resources - Handle to a collection of framework resource objects.
+ This collection identifies the raw (bus-relative) hardware
+ resources that have been assigned to the device.
+
+ ResourcesTranslated - Handle to a collection of framework resource objects.
+ This collection identifies the translated (system-physical)
+ hardware resources that have been assigned to the device.
+ The resources appear from the CPU's point of view.
+ Use this list of resources to map I/O space and
+ device-accessible memory into virtual address space
+
+Return Value:
+
+ STATUS_SUCCESS if consistant configuration was found - otherwise.
+ returns STATUS_SERIAL_NO_DEVICE_INITED.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG i;
+ PCM_PARTIAL_RESOURCE_DESCRIPTOR pPartialTrResourceDesc, pPartialRawResourceDesc;
+ ULONG gotInt = 0;
+ ULONG gotIO = 0;
+ ULONG ioResIndex = 0;
+ ULONG curIoIndex = 0;
+ ULONG gotMem = 0;
+ BOOLEAN DebugPortInUse = FALSE;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialMapHWResources\n");
+
+ //
+ // Get the DeviceExtension..
+ //
+ pDevExt = SerialGetDeviceExtension (Device);
+
+ if ((PResList == NULL) || (PTrResList == NULL)) {
+ ASSERT(PResList != NULL);
+ ASSERT(PTrResList != NULL);
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto End;
+ }
+
+ for (i = 0; i < WdfCmResourceListGetCount(PTrResList); i++) {
+
+ pPartialTrResourceDesc = WdfCmResourceListGetDescriptor(PTrResList, i);
+ pPartialRawResourceDesc = WdfCmResourceListGetDescriptor(PResList, i);
+
+ switch (pPartialTrResourceDesc->Type) {
+ case CmResourceTypePort:
+
+ ASSERT(!(pPartialTrResourceDesc->u.Port.Length == SERIAL_STATUS_LENGTH));
+
+ if (gotIO == 0) {
+
+ if (curIoIndex == ioResIndex) {
+
+ gotIO = 1;
+ PConfig->TrController = pPartialTrResourceDesc->u.Port.Start;
+
+ if (!PConfig->TrController.LowPart) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Bogus port address %x\n",
+ PConfig->TrController.LowPart);
+ status = STATUS_DEVICE_CONFIGURATION_ERROR;
+ goto End;
+ }
+ //
+ // We need the raw address to check if the debugger is using the com port
+ //
+ PConfig->Controller = pPartialRawResourceDesc->u.Port.Start;
+ PConfig->AddressSpace = pPartialTrResourceDesc->Flags;
+ pDevExt->SerialReadUChar = SerialReadPortUChar;
+ pDevExt->SerialWriteUChar = SerialWritePortUChar;
+
+ } else {
+ curIoIndex++;
+ }
+ }
+
+ break;
+
+ //
+ // If this is 8 bytes long and we haven't found any I/O range,
+ // then this is probably a fancy-pants machine with memory replacing
+ // IO space
+ //
+ case CmResourceTypeMemory:
+
+ ASSERT(!(pPartialTrResourceDesc->u.Port.Length == SERIAL_STATUS_LENGTH));
+
+ if ((gotMem == 0) && (gotIO == 0)
+ && (pPartialTrResourceDesc->u.Memory.Length
+ == (SERIAL_REGISTER_SPAN + SERIAL_STATUS_LENGTH))) {
+ gotMem = 1;
+ PConfig->TrController = pPartialTrResourceDesc->u.Memory.Start;
+
+ if (!PConfig->TrController.LowPart) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Bogus I/O memory address %x\n",
+ PConfig->TrController.LowPart);
+ status = STATUS_DEVICE_CONFIGURATION_ERROR;
+ goto End;
+ }
+
+ PConfig->Controller = pPartialRawResourceDesc->u.Memory.Start;
+ PConfig->AddressSpace = CM_RESOURCE_PORT_MEMORY;
+ PConfig->SpanOfController = SERIAL_REGISTER_SPAN;
+ pDevExt->SerialReadUChar = SerialReadRegisterUChar;
+ pDevExt->SerialWriteUChar = SerialWriteRegisterUChar;
+ }
+ break;
+
+ case CmResourceTypeInterrupt:
+ if (gotInt == 0) {
+ gotInt = 1;
+ PConfig->TrVector = pPartialTrResourceDesc->u.Interrupt.Vector;
+
+ if (!PConfig->TrVector) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Bogus vector 0\n");
+ status = STATUS_DEVICE_CONFIGURATION_ERROR;
+ goto End;
+ }
+
+ if (pPartialTrResourceDesc->ShareDisposition == CmResourceShareShared) {
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "Sharing interrupt with other devices \n");
+ } else {
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "Interrupt is not shared with other devices\n");
+ }
+
+ PConfig->TrIrql = pPartialTrResourceDesc->u.Interrupt.Level;
+ PConfig->Affinity = pPartialTrResourceDesc->u.Interrupt.Affinity;
+ }
+ break;
+
+ default: break;
+ } // switch (pPartialTrResourceDesc->Type)
+
+ } // for (i = 0; i < WdfCollectionGetCount
+
+ if(!((gotMem || gotIO) && gotInt) )
+ {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto End;
+ }
+
+ //
+ // First check what type of AddressSpace this port is in. Then check
+ // if the debugger is using this port. If it is, set DebugPortInUse to TRUE.
+ //
+ if(PConfig->AddressSpace == CM_RESOURCE_PORT_MEMORY) {
+
+ PHYSICAL_ADDRESS KdComPhysical;
+
+ KdComPhysical = MmGetPhysicalAddress(*KdComPortInUse);
+
+ if(KdComPhysical.LowPart == PConfig->Controller.LowPart) {
+ DebugPortInUse = TRUE;
+ }
+
+ } else {
+ //
+ // This compare is done using **untranslated** values since that is what
+ // the kernel shoves in regardless of the architecture.
+ //
+
+ if ((*KdComPortInUse) == (ULongToPtr(PConfig->Controller.LowPart))) {
+ DebugPortInUse = TRUE;
+ }
+ }
+
+ if (DebugPortInUse) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Kernel debugger is using port at "
+ "address %p\n", *KdComPortInUse);
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Serial driver will not load port\n");
+
+ SerialLogError(
+ pDevExt->DriverObject,
+ NULL,
+ PConfig->TrController,
+ SerialPhysicalZero,
+ 0,
+ 0,
+ 0,
+ 3,
+ STATUS_SUCCESS,
+ SERIAL_KERNEL_DEBUGGER_ACTIVE,
+ pDevExt->DeviceName.Length+sizeof(WCHAR),
+ pDevExt->DeviceName.Buffer,
+ 0,
+ NULL
+ );
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto End;
+ }
+
+End:
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialMapHWResources %x\n", status);
+
+ return status;
+}
+
+VOID
+SerialUnmapHWResources(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ )
+/*++
+
+Routine Description:
+
+ Releases resources (not pool) stored in the device extension.
+
+Arguments:
+
+ PDevExt - Pointer to the device extension to release resources from.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "-->SerialUnMapResources(%p)\n",
+ PDevExt);
+ PAGED_CODE();
+
+ //
+ // If necessary, unmap the device registers.
+ //
+
+ if (PDevExt->UnMapRegisters) {
+ MmUnmapIoSpace(PDevExt->Controller, PDevExt->SpanOfController);
+ PDevExt->UnMapRegisters = FALSE;
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<--SerialUnMapResources\n");
+}
+
+
+NTSTATUS
+SerialReadSymName(
+ IN WDFDEVICE Device,
+ _Out_writes_bytes_(*SizeOfRegName) PWSTR RegName,
+ _Inout_ PUSHORT SizeOfRegName
+ )
+{
+ NTSTATUS status;
+ WDFKEY hKey;
+ UNICODE_STRING value;
+ UNICODE_STRING valueName;
+ USHORT requiredLength;
+
+ PAGED_CODE();
+
+ value.Buffer = RegName;
+ value.MaximumLength = *SizeOfRegName;
+ value.Length = 0;
+
+ status = WdfDeviceOpenRegistryKey(Device,
+ PLUGPLAY_REGKEY_DEVICE,
+ STANDARD_RIGHTS_ALL,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &hKey);
+
+ if (NT_SUCCESS (status)) {
+ //
+ // Fetch PortName which contains the suggested REG_SZ symbolic name.
+ //
+
+
+ RtlInitUnicodeString(&valueName, L"PortName");
+
+ status = WdfRegistryQueryUnicodeString (hKey,
+ &valueName,
+ &requiredLength,
+ &value);
+
+ if (!NT_SUCCESS (status)) {
+ //
+ // This is for PCMCIA which currently puts the name under Identifier.
+ //
+
+ RtlInitUnicodeString(&valueName, L"Identifier");
+ status = WdfRegistryQueryUnicodeString (hKey,
+ &valueName,
+ &requiredLength,
+ &value);
+
+ if (!NT_SUCCESS(status)) {
+ //
+ // Hmm. Either we have to pick a name or bail...
+ //
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Getting PortName/Identifier failed - %x\n", status);
+ }
+ }
+
+ WdfRegistryClose(hKey);
+ }
+
+ if(NT_SUCCESS(status)) {
+ //
+ // NULL terminate the string and return number of characters in the string.
+ //
+ if(value.Length > *SizeOfRegName - sizeof(WCHAR)) {
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ *SizeOfRegName = value.Length;
+ RegName[*SizeOfRegName/sizeof(WCHAR)] = UNICODE_NULL;
+ }
+ return status;
+}
+
+
+NTSTATUS
+SerialDoExternalNaming(IN PSERIAL_DEVICE_EXTENSION PDevExt)
+
+/*++
+
+Routine Description:
+
+ This routine will be used to create a symbolic link
+ to the driver name in the given object directory.
+
+ It will also create an entry in the device map for
+ this device - IF we could create the symbolic link.
+
+Arguments:
+
+ Extension - Pointer to the device extension.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ WCHAR pRegName[SYMBOLIC_NAME_LENGTH];
+ USHORT nameSize = sizeof(pRegName);
+ WDFSTRING stringHandle = NULL;
+ WDF_OBJECT_ATTRIBUTES attributes;
+ DECLARE_UNICODE_STRING_SIZE(symbolicLinkName,SYMBOLIC_NAME_LENGTH ) ;
+
+ PAGED_CODE();
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+ attributes.ParentObject = PDevExt->WdfDevice;
+ status = WdfStringCreate(NULL, &attributes, &stringHandle);
+ if(!NT_SUCCESS(status)){
+ goto SerialDoExternalNamingError;
+ }
+
+ status = WdfDeviceRetrieveDeviceName(PDevExt->WdfDevice, stringHandle);
+ if(!NT_SUCCESS(status)){
+ goto SerialDoExternalNamingError;
+ }
+
+ //
+ // Since we are storing the buffer pointer of the string handle in our
+ // extension, we will hold onto string handle until the device is deleted.
+ //
+ WdfStringGetUnicodeString(stringHandle, &PDevExt->DeviceName);
+
+ SerialGetRegistryKeyValue(PDevExt->WdfDevice, L"SerialSkipExternalNaming", &PDevExt->SkipNaming);
+
+ if (PDevExt->SkipNaming) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Skipping external naming due to registry settings\n");
+ return STATUS_SUCCESS;
+ }
+
+ status = SerialReadSymName(PDevExt->WdfDevice, pRegName, &nameSize);
+ if (!NT_SUCCESS(status)) {
+ goto SerialDoExternalNamingError;
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "DosName is %ws\n", pRegName);
+
+ status = RtlUnicodeStringPrintf(&symbolicLinkName,
+ L"%ws%ws",
+ L"\\DosDevices\\",
+ pRegName);
+
+ if (!NT_SUCCESS(status)) {
+ goto SerialDoExternalNamingError;
+ }
+
+ status = WdfDeviceCreateSymbolicLink(PDevExt->WdfDevice, &symbolicLinkName);
+
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't create the symbolic link for port %wZ\n", &symbolicLinkName);
+
+ goto SerialDoExternalNamingError;
+
+ }
+
+
+ PDevExt->CreatedSymbolicLink = TRUE;
+
+ status = RtlWriteRegistryValue(RTL_REGISTRY_DEVICEMAP, SERIAL_DEVICE_MAP,
+ PDevExt->DeviceName.Buffer,
+ REG_SZ,
+ pRegName,
+ nameSize + sizeof(WCHAR));
+
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't create the device map entry\n"
+ "------- for port %ws\n", PDevExt->DeviceName.Buffer);
+
+ goto SerialDoExternalNamingError;
+ }
+
+ PDevExt->CreatedSerialCommEntry = TRUE;
+
+ //
+ // Make the device visible via a device association as well.
+ // The reference string is the eight digit device index
+ //
+ status = WdfDeviceCreateDeviceInterface(PDevExt->WdfDevice,
+ (LPGUID) &GUID_DEVINTERFACE_COMPORT,
+ NULL);
+
+ if (!NT_SUCCESS (status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't register class association\n"
+ "for port %wZ\n", &PDevExt->DeviceName);
+
+ goto SerialDoExternalNamingError;
+ }
+
+ return status;
+
+ SerialDoExternalNamingError:;
+
+ //
+ // Clean up error conditions
+ //
+
+ PDevExt->DeviceName.Buffer = NULL;
+
+ if (PDevExt->CreatedSerialCommEntry) {
+ _Analysis_assume_(NULL != PDevExt->DeviceName.Buffer);
+ RtlDeleteRegistryValue(RTL_REGISTRY_DEVICEMAP, SERIAL_DEVICE_MAP,
+ PDevExt->DeviceName.Buffer);
+ }
+
+ if(stringHandle) {
+ WdfObjectDelete(stringHandle);
+ }
+
+ return status;
+}
+
+
+VOID
+SerialUndoExternalNaming(IN PSERIAL_DEVICE_EXTENSION Extension)
+
+/*++
+
+Routine Description:
+
+ This routine will be used to delete a symbolic link
+ to the driver name in the given object directory.
+
+ It will also delete an entry in the device map for
+ this device if the symbolic link had been created.
+
+Arguments:
+
+ Extension - Pointer to the device extension.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ NTSTATUS status;
+ PWCHAR deviceName = Extension->DeviceName.Buffer;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "In SerialUndoExternalNaming for extension: "
+ "%p of port %ws\n", Extension, deviceName);
+
+ //
+ // Maybe there is nothing for us to do
+ //
+
+ if (Extension->SkipNaming) {
+ return;
+ }
+
+ //
+ // We're cleaning up here. One reason we're cleaning up
+ // is that we couldn't allocate space for the NtNameOfPort.
+ //
+
+ if ((deviceName != NULL) && Extension->CreatedSerialCommEntry) {
+
+ status = RtlDeleteRegistryValue(RTL_REGISTRY_DEVICEMAP,
+ SERIAL_DEVICE_MAP,
+ deviceName);
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP,
+ "Couldn't delete value entry %ws\n",
+ deviceName);
+
+ }
+ }
+}
+
+VOID
+SerialPurgePendingRequests(PSERIAL_DEVICE_EXTENSION pDevExt)
+/*++
+
+Routine Description:
+
+ This routine completes any irps pending for the passed device object.
+
+Arguments:
+
+ PDevObj - Pointer to the device object whose irps must die.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ NTSTATUS status;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ ">SerialPurgePendingRequests(%p)\n", pDevExt);
+
+ //
+ // Then cancel all the reads and writes.
+ //
+
+ SerialPurgeRequests(pDevExt->WriteQueue, &pDevExt->CurrentWriteRequest);
+
+ SerialPurgeRequests(pDevExt->ReadQueue, &pDevExt->CurrentReadRequest);
+
+ //
+ // Next get rid of purges.
+ //
+
+ SerialPurgeRequests(pDevExt->PurgeQueue, &pDevExt->CurrentPurgeRequest);
+
+ //
+ // Get rid of any mask operations.
+ //
+
+ SerialPurgeRequests( pDevExt->MaskQueue, &pDevExt->CurrentMaskRequest);
+
+ //
+ // Now get rid of pending wait mask request.
+ //
+
+ if (pDevExt->CurrentWaitRequest) {
+
+ status = SerialClearCancelRoutine(pDevExt->CurrentWaitRequest, TRUE );
+ if (NT_SUCCESS(status)) {
+
+ SerialCompleteRequest(pDevExt->CurrentWaitRequest, STATUS_CANCELLED, 0);
+ pDevExt->CurrentWaitRequest = NULL;
+
+ }
+
+ }
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<SerialPurgePendingRequests\n");
+}
+
+BOOLEAN
+SerialDoesPortExist(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN PUNICODE_STRING InsertString,
+ IN ULONG ForceFifo,
+ IN ULONG LogFifo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine examines several of what might be the serial device
+ registers. It ensures that the bits that should be zero are zero.
+
+ In addition, this routine will determine if the device supports
+ fifo's. If it does it will enable the fifo's and turn on a boolean
+ in the extension that indicates the fifo's presence.
+
+ NOTE: If there is indeed a serial port at the address specified
+ it will absolutely have interrupts inhibited upon return
+ from this routine.
+
+ NOTE: Since this routine should be called fairly early in
+ the device driver initialization, the only element
+ that needs to be filled in is the base register address.
+
+ NOTE: These tests all assume that this code is the only
+ code that is looking at these ports or this memory.
+
+ This is a not to unreasonable assumption even on
+ multiprocessor systems.
+
+Arguments:
+
+ Extension - A pointer to a serial device extension.
+ InsertString - String to place in an error log entry.
+ ForceFifo - !0 forces the fifo to be left on if found.
+ LogFifo - !0 forces a log message if fifo found.
+
+Return Value:
+
+ Will return true if the port really exists, otherwise it
+ will return false.
+
+--*/
+
+{
+
+
+ UCHAR regContents;
+ BOOLEAN returnValue = TRUE;
+ UCHAR oldIERContents;
+ UCHAR oldLCRContents;
+ USHORT value1;
+ USHORT value2;
+ KIRQL oldIrql;
+
+ //
+ // Save of the line control.
+ //
+
+ oldLCRContents = READ_LINE_CONTROL(Extension, Extension->Controller);
+
+ //
+ // Make sure that we are *aren't* accessing the divsior latch.
+ //
+
+ WRITE_LINE_CONTROL(Extension,
+ Extension->Controller,
+ (UCHAR)(oldLCRContents & ~SERIAL_LCR_DLAB)
+ );
+
+ oldIERContents = READ_INTERRUPT_ENABLE(Extension, Extension->Controller);
+
+ //
+ // Go up to power level for a very short time to prevent
+ // any interrupts from this device from coming in.
+ //
+
+ KeRaiseIrql(
+ POWER_LEVEL,
+ &oldIrql
+ );
+
+ WRITE_INTERRUPT_ENABLE(Extension,
+ Extension->Controller,
+ 0x0f
+ );
+
+ value1 = READ_INTERRUPT_ENABLE(Extension, Extension->Controller);
+ value1 = value1 << 8;
+ value1 |= READ_RECEIVE_BUFFER(Extension, Extension->Controller);
+
+ READ_DIVISOR_LATCH(Extension,
+ Extension->Controller,
+ (PSHORT) &value2
+ );
+
+ WRITE_LINE_CONTROL(Extension,
+ Extension->Controller,
+ oldLCRContents
+ );
+
+ //
+ // Put the ier back to where it was before. If we are on a
+ // level sensitive port this should prevent the interrupts
+ // from coming in. If we are on a latched, we don't care
+ // cause the interrupts generated will just get dropped.
+ //
+
+ WRITE_INTERRUPT_ENABLE(Extension,
+ Extension->Controller,
+ oldIERContents
+ );
+
+ KeLowerIrql(oldIrql);
+
+ if (value1 == value2) {
+
+ SerialLogError(
+ Extension->DeviceObject->DriverObject,
+ Extension->DeviceObject,
+ SerialPhysicalZero,
+ SerialPhysicalZero,
+ 0,
+ 0,
+ 0,
+ 62,
+ STATUS_SUCCESS,
+ SERIAL_DLAB_INVALID,
+ InsertString->Length+sizeof(WCHAR),
+ InsertString->Buffer,
+ 0,
+ NULL
+ );
+ returnValue = FALSE;
+ goto AllDone;
+
+ }
+
+ AllDone: ;
+
+
+ //
+ // If we think that there is a serial device then we determine
+ // if a fifo is present.
+ //
+
+ if (returnValue) {
+
+ //
+ // Well, we think it's a serial device. Absolutely
+ // positively, prevent interrupts from occuring.
+ //
+ // We disable all the interrupt enable bits, and
+ // push down all the lines in the modem control
+ // We only needed to push down OUT2 which in
+ // PC's must also be enabled to get an interrupt.
+ //
+
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+
+ WRITE_MODEM_CONTROL(Extension, Extension->Controller, (UCHAR)0);
+
+ //
+ // See if this is a 16550. We do this by writing to
+ // what would be the fifo control register with a bit
+ // pattern that tells the device to enable fifo's.
+ // We then read the iterrupt Id register to see if the
+ // bit pattern is present that identifies the 16550.
+ //
+
+ WRITE_FIFO_CONTROL(Extension,
+ Extension->Controller,
+ SERIAL_FCR_ENABLE
+ );
+
+ regContents = READ_INTERRUPT_ID_REG(Extension, Extension->Controller);
+
+ if (regContents & SERIAL_IIR_FIFOS_ENABLED) {
+
+ //
+ // Save off that the device supports fifos.
+ //
+
+ Extension->FifoPresent = TRUE;
+
+ //
+ // There is a fine new "super" IO chip out there that
+ // will get stuck with a line status interrupt if you
+ // attempt to clear the fifo and enable it at the same
+ // time if data is present. The best workaround seems
+ // to be that you should turn off the fifo read a single
+ // byte, and then re-enable the fifo.
+ //
+
+ WRITE_FIFO_CONTROL(Extension,
+ Extension->Controller,
+ (UCHAR)0
+ );
+
+ READ_RECEIVE_BUFFER(Extension, Extension->Controller);
+
+ //
+ // There are fifos on this card. Set the value of the
+ // receive fifo to interrupt when 4 characters are present.
+ //
+
+ WRITE_FIFO_CONTROL(Extension, Extension->Controller,
+ (UCHAR)(SERIAL_FCR_ENABLE
+ | Extension->RxFifoTrigger
+ | SERIAL_FCR_RCVR_RESET
+ | SERIAL_FCR_TXMT_RESET));
+
+ }
+
+ //
+ // The !Extension->FifoPresent is included in the test so that
+ // broken chips like the WinBond will still work after we test
+ // for the fifo.
+ //
+
+ if (!ForceFifo || !Extension->FifoPresent) {
+
+ Extension->FifoPresent = FALSE;
+ WRITE_FIFO_CONTROL(Extension,
+ Extension->Controller,
+ (UCHAR)0
+ );
+
+ }
+
+ if (Extension->FifoPresent) {
+
+ if (LogFifo) {
+
+ SerialLogError(
+ Extension->DeviceObject->DriverObject,
+ Extension->DeviceObject,
+ SerialPhysicalZero,
+ SerialPhysicalZero,
+ 0,
+ 0,
+ 0,
+ 15,
+ STATUS_SUCCESS,
+ SERIAL_FIFO_PRESENT,
+ InsertString->Length+sizeof(WCHAR),
+ InsertString->Buffer,
+ 0,
+ NULL
+ );
+
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP,
+ "Fifo's detected at port address: %p\n",
+ Extension->Controller);
+ }
+ }
+
+ return returnValue;
+}
+
+
+
+BOOLEAN
+SerialReset(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This places the hardware in a standard configuration.
+
+ NOTE: This assumes that it is called at interrupt level.
+
+
+Arguments:
+
+ Context - The device extension for serial device
+ being managed.
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = Context;
+ UCHAR regContents;
+ UCHAR oldModemControl;
+ ULONG i;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ //
+ // Adjust the out2 bit.
+ // This will also prevent any interrupts from occuring.
+ //
+
+ oldModemControl = READ_MODEM_CONTROL(extension, extension->Controller);
+
+ WRITE_MODEM_CONTROL(extension, extension->Controller,
+ (UCHAR)(oldModemControl & ~SERIAL_MCR_OUT2));
+
+ //
+ // Reset the fifo's if there are any.
+ //
+
+ if (extension->FifoPresent) {
+
+ //
+ // There is a fine new "super" IO chip out there that
+ // will get stuck with a line status interrupt if you
+ // attempt to clear the fifo and enable it at the same
+ // time if data is present. The best workaround seems
+ // to be that you should turn off the fifo read a single
+ // byte, and then re-enable the fifo.
+ //
+
+ WRITE_FIFO_CONTROL(extension,
+ extension->Controller,
+ (UCHAR)0
+ );
+
+ READ_RECEIVE_BUFFER(extension, extension->Controller);
+
+ WRITE_FIFO_CONTROL(extension,
+ extension->Controller,
+ (UCHAR)(SERIAL_FCR_ENABLE | extension->RxFifoTrigger |
+ SERIAL_FCR_RCVR_RESET | SERIAL_FCR_TXMT_RESET)
+ );
+
+ }
+
+ //
+ // Make sure that the line control set up correct.
+ //
+ // 1) Make sure that the Divisor latch select is set
+ // up to select the transmit and receive register.
+ //
+ // 2) Make sure that we aren't in a break state.
+ //
+
+ regContents = READ_LINE_CONTROL(extension, extension->Controller);
+ regContents &= ~(SERIAL_LCR_DLAB | SERIAL_LCR_BREAK);
+
+ WRITE_LINE_CONTROL(extension,
+ extension->Controller,
+ regContents
+ );
+
+ //
+ // Read the receive buffer until the line status is
+ // clear. (Actually give up after a 5 reads.)
+ //
+
+ for (i = 0;
+ i < 5;
+ i++
+ ) {
+ #pragma warning(disable: 4127)
+ if (IsNotNEC_98) {
+ #pragma warning(default: 4127)
+ READ_RECEIVE_BUFFER(extension, extension->Controller);
+ if (!(READ_LINE_STATUS(extension, extension->Controller) & 1)) {
+
+ break;
+
+ }
+ } else {
+ //
+ // I get incorrect data when read enpty buffer.
+ // But do not read no data! for PC98!
+ //
+ if (!(READ_LINE_STATUS(extension, extension->Controller) & 1)) {
+
+ break;
+
+ }
+ READ_RECEIVE_BUFFER(extension, extension->Controller);
+ }
+
+ }
+
+ //
+ // Read the modem status until the low 4 bits are
+ // clear. (Actually give up after a 5 reads.)
+ //
+
+ for (i = 0;
+ i < 1000;
+ i++
+ ) {
+
+ if (!(READ_MODEM_STATUS(extension, extension->Controller) & 0x0f)) {
+
+ break;
+
+ }
+
+ }
+
+ //
+ // Now we set the line control, modem control, and the
+ // baud to what they should be.
+ //
+
+ //
+ // See if we have to enable special Auto Flow Control
+ //
+
+ if (extension->TL16C550CAFC) {
+ oldModemControl = READ_MODEM_CONTROL(extension, extension->Controller);
+
+ WRITE_MODEM_CONTROL(extension, extension->Controller,
+ (UCHAR)(oldModemControl | SERIAL_MCR_TL16C550CAFE));
+ }
+
+
+
+ SerialSetLineControl(extension->WdfInterrupt, extension);
+
+ SerialSetupNewHandFlow(
+ extension,
+ &extension->HandFlow
+ );
+
+ SerialHandleModemUpdate(
+ extension,
+ FALSE
+ );
+
+ {
+ SHORT appropriateDivisor;
+ SERIAL_IOCTL_SYNC s;
+
+ SerialGetDivisorFromBaud( extension->ClockRate,
+ extension->CurrentBaud,
+ &appropriateDivisor );
+
+ s.Extension = extension;
+ s.Data = (PVOID) (ULONG_PTR) appropriateDivisor;
+ SerialSetBaud(extension->WdfInterrupt, &s);
+ }
+
+ //
+ // Enable which interrupts we want to receive.
+ //
+ // NOTE NOTE: This does not actually let interrupts
+ // occur. We must still raise the OUT2 bit in the
+ // modem control register. We will do that on open.
+ //
+
+ ENABLE_ALL_INTERRUPTS(extension, extension->Controller);
+
+ //
+ // Read the interrupt id register until the low bit is
+ // set. (Actually give up after a 5 reads.)
+ //
+
+ for (i = 0;
+ i < 5;
+ i++
+ ) {
+
+ if (READ_INTERRUPT_ID_REG(extension, extension->Controller) & 0x01) {
+
+ break;
+
+ }
+
+ }
+
+ //
+ // Now we know that nothing could be transmitting at this point
+ // so we set the HoldingEmpty indicator.
+ //
+
+ extension->HoldingEmpty = TRUE;
+
+ return FALSE;
+}
+
+
+
+PVOID
+SerialGetMappedAddress(
+ PHYSICAL_ADDRESS IoAddress,
+ ULONG NumberOfBytes,
+ ULONG AddressSpace,
+ PBOOLEAN MappedAddress
+ )
+
+/*++
+
+Routine Description:
+
+ This routine maps an IO address to system address space.
+
+Arguments:
+
+ IoAddress - base device address to be mapped.
+ NumberOfBytes - number of bytes for which address is valid.
+ AddressSpace - Denotes whether the address is in io space or memory.
+ MappedAddress - indicates whether the address was mapped.
+ This only has meaning if the address returned
+ is non-null.
+
+Return Value:
+
+ Mapped address
+
+--*/
+
+{
+ PVOID address;
+
+ PAGED_CODE();
+
+ //
+ // Map the device base address into the virtual address space
+ // if the address is in memory space.
+ //
+
+ if (!AddressSpace) {
+
+ address = LocalMmMapIoSpace(IoAddress,
+ NumberOfBytes);
+
+ *MappedAddress = (BOOLEAN)((address)?(TRUE):(FALSE));
+
+
+ } else {
+
+ address = ULongToPtr(IoAddress.LowPart);
+ *MappedAddress = FALSE;
+
+ }
+
+ return address;
+}
+
+VOID
+SerialSetInterruptPolicy(
+ _In_ WDFINTERRUPT WdfInterrupt
+ )
+/*++
+
+Routine Description:
+
+ This routine shows how to set the interrupt policy preferences.
+
+Arguments:
+
+ WdfInterrupt - Interrupt object handle.
+
+Return Value:
+
+ None
+
+--*/
+{
+ WDF_INTERRUPT_EXTENDED_POLICY policyAndGroup;
+#ifdef SERIAL_SELECT_INTERRUPT_GROUP
+ USHORT groupCount = 1;
+ USHORT group = 0;
+ UNICODE_STRING funcName;
+ PFN_KE_GET_ACTIVE_GROUP_COUNT fnKeQueryActiveGroupCount;
+ PFN_KE_QUERY_GROUP_AFFINITY fnKeQueryGroupAffinity;
+ KAFFINITY groupAffinity = (KAFFINITY)1;
+#endif
+
+ WDF_INTERRUPT_EXTENDED_POLICY_INIT(&policyAndGroup);
+ policyAndGroup.Priority = WdfIrqPriorityNormal;
+
+#ifdef SERIAL_SELECT_INTERRUPT_GROUP
+ //
+ // If OS supports groups, find how many they are.
+ //
+ RtlInitUnicodeString(&funcName, L"KeQueryActiveGroupCount");
+ fnKeQueryActiveGroupCount = (PFN_KE_GET_ACTIVE_GROUP_COUNT)
+ MmGetSystemRoutineAddress(&funcName);
+
+ if (fnKeQueryActiveGroupCount != NULL) {
+ groupCount = fnKeQueryActiveGroupCount();
+
+ //
+ // Make sure there is at least one group for the boot processor.
+ //
+ if (0 == groupCount) {
+ groupCount = 1;
+ }
+ }
+
+ if (groupCount <= SERIAL_PREFERRED_INTERRUPT_GROUP) {
+ group = groupCount - 1;
+ }
+ else {
+ group = SERIAL_PREFERRED_INTERRUPT_GROUP;
+ }
+
+ //
+ // Get the group affinity.
+ //
+ RtlInitUnicodeString(&funcName, L"KeQueryGroupAffinity");
+ fnKeQueryGroupAffinity = (PFN_KE_QUERY_GROUP_AFFINITY)
+ MmGetSystemRoutineAddress(&funcName);
+
+ if (fnKeQueryGroupAffinity != NULL) {
+ groupAffinity = fnKeQueryGroupAffinity(group);
+
+ //
+ // Active groups have at least one processor.
+ //
+ if ((KAFFINITY)0 == groupAffinity) {
+ groupAffinity = (KAFFINITY)1;
+ }
+ }
+
+ //
+ // Initialize group.
+ //
+ policyAndGroup.Policy = WdfIrqPolicySpecifiedProcessors;
+ policyAndGroup.TargetProcessorSetAndGroup.Group = group;
+ policyAndGroup.TargetProcessorSetAndGroup.Mask = groupAffinity;
+#endif
+
+ //
+ // Set interrupt policy and group preference.
+ //
+ WdfInterruptSetExtendedPolicy(WdfInterrupt, &policyAndGroup);
+}
+
diff --git a/tests/projects/windows/driver/kmdf/serial/power.c b/tests/projects/windows/driver/kmdf/serial/power.c new file mode 100644 index 000000000..68aad3efa --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/power.c @@ -0,0 +1,331 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ power.c
+
+Abstract:
+
+ This module contains the code that handles the power IRPs for the serial
+ driver.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+
+#if defined(EVENT_TRACING)
+#include "power.tmh"
+#endif
+
+
+PCHAR
+DbgDevicePowerString(
+ IN WDF_POWER_DEVICE_STATE Type
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGESER,SerialEvtDeviceD0Exit)
+#pragma alloc_text(PAGESER,SerialSaveDeviceState)
+#endif // ALLOC_PRAGMA
+
+PCHAR
+DbgDevicePowerString(
+ IN WDF_POWER_DEVICE_STATE Type
+ )
+/*++
+
+Updated Routine Description:
+ DbgDevicePowerString does not change in this stage of the function driver.
+
+--*/
+{
+
+ switch (Type)
+ {
+ case WdfPowerDeviceInvalid:
+ return "WdfPowerDeviceInvalid";
+ case WdfPowerDeviceD0:
+ return "WdfPowerDeviceD0";
+ case WdfPowerDeviceD1:
+ return "WdfPowerDeviceD1";
+ case WdfPowerDeviceD2:
+ return "WdfPowerDeviceD2";
+ case WdfPowerDeviceD3:
+ return "WdfPowerDeviceD3";
+ case WdfPowerDeviceD3Final:
+ return "WdfPowerDeviceD3Final";
+ case WdfPowerDevicePrepareForHibernation:
+ return "WdfPowerDevicePrepareForHibernation";
+ case WdfPowerDeviceMaximum:
+ return "WdfPowerDeviceMaximum";
+ default:
+ return "UnKnown Device Power State";
+ }
+}
+
+NTSTATUS
+SerialEvtDeviceD0Entry(
+ IN WDFDEVICE Device,
+ IN WDF_POWER_DEVICE_STATE PreviousState
+ )
+/*++
+
+Routine Description:
+
+ EvtDeviceD0Entry event callback must perform any operations that are
+ necessary before the specified device is used. It will be called every
+ time the hardware needs to be (re-)initialized. This includes after
+ IRP_MN_START_DEVICE, IRP_MN_CANCEL_STOP_DEVICE, IRP_MN_CANCEL_REMOVE_DEVICE,
+ IRP_MN_SET_POWER-D0.
+
+ This function is not marked pageable because this function is in the
+ device power up path. When a function is marked pagable and the code
+ section is paged out, it will generate a page fault which could impact
+ the fast resume behavior because the client driver will have to wait
+ until the system drivers can service this page fault.
+
+ This function runs at PASSIVE_LEVEL, even though it is not paged. A
+ driver can optionally make this function pageable if DO_POWER_PAGABLE
+ is set. Even if DO_POWER_PAGABLE isn't set, this function still runs
+ at PASSIVE_LEVEL. In this case, though, the function absolutely must
+ not do anything that will cause a page fault.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ PreviousState - Device power state which the device was in most recently.
+ If the device is being newly started, this will be
+ PowerDeviceUnspecified.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION deviceExtension;
+ PSERIAL_DEVICE_STATE pDevState;
+ SHORT divisor;
+ SERIAL_IOCTL_SYNC S;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER,
+ "-->SerialEvtDeviceD0Entry - coming from %s\n", DbgDevicePowerString(PreviousState));
+
+ deviceExtension = SerialGetDeviceExtension (Device);
+ pDevState = &deviceExtension->DeviceState;
+
+ //
+ // Restore the state of the UART. First, that involves disabling
+ // interrupts both via OUT2 and IER.
+ //
+
+ WRITE_MODEM_CONTROL(deviceExtension, deviceExtension->Controller, 0);
+ DISABLE_ALL_INTERRUPTS(deviceExtension, deviceExtension->Controller);
+
+ //
+ // Set the baud rate
+ //
+
+ SerialGetDivisorFromBaud(deviceExtension->ClockRate, deviceExtension->CurrentBaud, &divisor);
+ S.Extension = deviceExtension;
+ S.Data = (PVOID) (ULONG_PTR) divisor;
+
+#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_LOW, "PFD warning that we are calling interrupt synchronize routine directly. Suppress it because interrupt is disabled above.")
+ SerialSetBaud(deviceExtension->WdfInterrupt, &S);
+
+ //
+ // Reset / Re-enable the FIFO's
+ //
+
+ if (deviceExtension->FifoPresent) {
+ WRITE_FIFO_CONTROL(deviceExtension, deviceExtension->Controller, (UCHAR)0);
+ READ_RECEIVE_BUFFER(deviceExtension, deviceExtension->Controller);
+ WRITE_FIFO_CONTROL(deviceExtension, deviceExtension->Controller,
+ (UCHAR)(SERIAL_FCR_ENABLE | deviceExtension->RxFifoTrigger
+ | SERIAL_FCR_RCVR_RESET
+ | SERIAL_FCR_TXMT_RESET));
+ } else {
+ WRITE_FIFO_CONTROL(deviceExtension, deviceExtension->Controller, (UCHAR)0);
+ }
+
+ //
+ // Restore a couple more registers
+ //
+
+ WRITE_INTERRUPT_ENABLE(deviceExtension, deviceExtension->Controller, pDevState->IER);
+ WRITE_LINE_CONTROL(deviceExtension, deviceExtension->Controller, pDevState->LCR);
+
+ //
+ // Clear out any stale interrupts
+ //
+
+ READ_INTERRUPT_ID_REG(deviceExtension, deviceExtension->Controller);
+ READ_LINE_STATUS(deviceExtension, deviceExtension->Controller);
+ READ_MODEM_STATUS(deviceExtension, deviceExtension->Controller);
+
+ //
+ // TODO: move this code to EvtInterruptEnable.
+ //
+
+ if (deviceExtension->DeviceState.Reopen == TRUE) {
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "Reopening device\n");
+
+ SetDeviceIsOpened(deviceExtension, TRUE, FALSE);
+
+ //
+ // This enables interrupts on the device!
+ //
+
+ WRITE_MODEM_CONTROL(deviceExtension, deviceExtension->Controller,
+ (UCHAR)(pDevState->MCR | SERIAL_MCR_OUT2));
+
+ //
+ // Refire the state machine
+ //
+
+ DISABLE_ALL_INTERRUPTS(deviceExtension, deviceExtension->Controller);
+ ENABLE_ALL_INTERRUPTS(deviceExtension, deviceExtension->Controller);
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "<--SerialEvtDeviceD0Entry\n");
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+SerialEvtDeviceD0Exit(
+ IN WDFDEVICE Device,
+ IN WDF_POWER_DEVICE_STATE TargetState
+ )
+/*++
+
+Routine Description:
+
+ EvtDeviceD0Exit event callback must perform any operations that are
+ necessary before the specified device is moved out of the D0 state. If the
+ driver needs to save hardware state before the device is powered down, then
+ that should be done here.
+
+ This function runs at PASSIVE_LEVEL, though it is generally not paged. A
+ driver can optionally make this function pageable if DO_POWER_PAGABLE is set.
+
+ Even if DO_POWER_PAGABLE isn't set, this function still runs at
+ PASSIVE_LEVEL. In this case, though, the function absolutely must not do
+ anything that will cause a page fault.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+ TargetState - Device power state which the device will be put in once this
+ callback is complete.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ PSERIAL_DEVICE_EXTENSION deviceExtension;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER,
+ "-->SerialEvtDeviceD0Exit - moving to %s\n", DbgDevicePowerString(TargetState));
+
+ PAGED_CODE();
+
+ deviceExtension = SerialGetDeviceExtension (Device);
+
+ if (deviceExtension->DeviceIsOpened == TRUE) {
+ LARGE_INTEGER charTime;
+
+ SetDeviceIsOpened(deviceExtension, FALSE, TRUE);
+
+ charTime.QuadPart = -SerialGetCharTime(deviceExtension).QuadPart;
+
+ //
+ // Shut down the chip
+ //
+
+ SerialDisableUART(deviceExtension);
+
+ //
+ // Drain the device
+ //
+
+ SerialDrainUART(deviceExtension, &charTime);
+
+ //
+ // Save the device state
+ //
+
+ SerialSaveDeviceState(deviceExtension);
+ }
+ else
+ {
+ SetDeviceIsOpened(deviceExtension, FALSE, FALSE);
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "<--SerialEvtDeviceD0Exit\n");
+
+ return STATUS_SUCCESS;
+}
+
+
+VOID
+SerialSaveDeviceState(IN PSERIAL_DEVICE_EXTENSION PDevExt)
+/*++
+
+Routine Description:
+
+ This routine saves the device state of the UART
+
+Arguments:
+
+ PDevExt - Pointer to the device extension for the devobj to save the state
+ for.
+
+Return Value:
+
+ VOID
+
+
+--*/
+{
+ PSERIAL_DEVICE_STATE pDevState = &PDevExt->DeviceState;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "Entering SerialSaveDeviceState\n");
+
+ //
+ // Read necessary registers direct
+ //
+
+ pDevState->IER = READ_INTERRUPT_ENABLE(PDevExt, PDevExt->Controller);
+ pDevState->MCR = READ_MODEM_CONTROL(PDevExt, PDevExt->Controller);
+ pDevState->LCR = READ_LINE_CONTROL(PDevExt, PDevExt->Controller);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "Leaving SerialSaveDeviceState\n");
+}
+
+
+VOID
+SetDeviceIsOpened(IN PSERIAL_DEVICE_EXTENSION PDevExt, IN BOOLEAN DeviceIsOpened, IN BOOLEAN Reopen)
+{
+
+ PDevExt->DeviceIsOpened = DeviceIsOpened;
+ PDevExt->DeviceState.Reopen = Reopen;
+
+}
+
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/precomp.h b/tests/projects/windows/driver/kmdf/serial/precomp.h new file mode 100644 index 000000000..7c7c5d5ef --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/precomp.h @@ -0,0 +1,18 @@ +
+#include <stddef.h>
+#include <stdarg.h>
+#define WIN9X_COMPAT_SPINLOCK
+#include "ntddk.h"
+#include <wdf.h>
+#define NTSTRSAFE_LIB
+#include <ntstrsafe.h>
+#include "ntddser.h"
+#include <wmilib.h>
+#include <initguid.h> // required for GUID definitions
+#include <wmidata.h>
+#include "serial.h"
+#include "serialp.h"
+#include "serlog.h"
+#include "log.h"
+#include "trace.h"
+
diff --git a/tests/projects/windows/driver/kmdf/serial/precompsrc.c b/tests/projects/windows/driver/kmdf/serial/precompsrc.c new file mode 100644 index 000000000..5944cf515 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/precompsrc.c @@ -0,0 +1 @@ +#include "precomp.h"
\ No newline at end of file diff --git a/tests/projects/windows/driver/kmdf/serial/purge.c b/tests/projects/windows/driver/kmdf/serial/purge.c new file mode 100644 index 000000000..fcc32148e --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/purge.c @@ -0,0 +1,175 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ purge.c
+
+Abstract:
+
+ This module contains the code that is very specific to purge
+ operations in the serial driver
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "purge.tmh"
+#endif
+
+
+VOID
+SerialStartPurge(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ Depending on the mask in the current request, purge the interrupt
+ buffer, the read queue, or the write queue, or all of the above.
+
+Arguments:
+
+ Extension - Pointer to the device extension.
+
+Return Value:
+
+ Will return STATUS_SUCCESS always. This is reasonable
+ since the DPC completion code that calls this routine doesn't
+ care and the purge request always goes through to completion
+ once it's started.
+
+--*/
+
+{
+
+ WDFREQUEST NewRequest;
+ PREQUEST_CONTEXT reqContext;
+
+ do {
+
+ ULONG Mask;
+ reqContext = SerialGetRequestContext(Extension->CurrentPurgeRequest);
+ Mask = *((ULONG *) (reqContext->SystemBuffer));
+
+ if (Mask & SERIAL_PURGE_TXABORT) {
+
+ SerialFlushRequests(
+ Extension->WriteQueue,
+ &Extension->CurrentWriteRequest
+ );
+
+ SerialFlushRequests(
+ Extension->WriteQueue,
+ &Extension->CurrentXoffRequest
+ );
+
+ }
+
+ if (Mask & SERIAL_PURGE_RXABORT) {
+
+ SerialFlushRequests(
+ Extension->ReadQueue,
+ &Extension->CurrentReadRequest
+ );
+
+ }
+
+ if (Mask & SERIAL_PURGE_RXCLEAR) {
+
+ //
+ // Clean out the interrupt buffer.
+ //
+ // Note that we do this under protection of the
+ // the drivers control lock so that we don't hose
+ // the pointers if there is currently a read that
+ // is reading out of the buffer.
+ //
+
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialPurgeInterruptBuff,
+ Extension
+ );
+
+ }
+
+ reqContext->Status = STATUS_SUCCESS;
+ reqContext->Information = 0;
+
+ SerialGetNextRequest(
+ &Extension->CurrentPurgeRequest,
+ Extension->PurgeQueue,
+ &NewRequest,
+ TRUE,
+ Extension
+ );
+
+ } while (NewRequest);
+
+ return;
+
+}
+
+BOOLEAN
+SerialPurgeInterruptBuff(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine simply resets the interrupt (typeahead) buffer.
+
+ NOTE: This routine is being called from WdfInterruptSynchronize.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always false.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ //
+ // The typeahead buffer is by definition empty if there
+ // currently is a read owned by the isr.
+ //
+
+
+ if (Extension->ReadBufferBase == Extension->InterruptReadBuffer) {
+
+ Extension->CurrentCharSlot = Extension->InterruptReadBuffer;
+ Extension->FirstReadableChar = Extension->InterruptReadBuffer;
+ Extension->LastCharSlot = Extension->InterruptReadBuffer +
+ (Extension->BufferSize - 1);
+ Extension->CharsInInterruptBuffer = 0;
+
+ SerialHandleReducedIntBuffer(Extension);
+
+ }
+
+ return FALSE;
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/qsfile.c b/tests/projects/windows/driver/kmdf/serial/qsfile.c new file mode 100644 index 000000000..ad1604131 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/qsfile.c @@ -0,0 +1,180 @@ +/*++
+
+Copyright (c) 1991, 1992, 1993 - 1997 Microsoft Corporation
+
+Module Name:
+
+ qsfile.c
+
+Abstract:
+
+ This module contains the code that is very specific to query/set file
+ operations in the serial driver.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "qsfile.tmh"
+#endif
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGESRP0,SerialQueryInformationFile)
+#pragma alloc_text(PAGESRP0,SerialSetInformationFile)
+#endif
+
+
+NTSTATUS
+SerialQueryInformationFile(
+ IN WDFDEVICE Device,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to query the end of file information on
+ the opened serial port. Any other file information request
+ is retured with an invalid parameter.
+
+ This routine always returns an end of file of 0.
+
+Arguments:
+
+ DeviceObject - Pointer to the device object for this device
+
+ Irp - Pointer to the IRP for the current request
+
+Return Value:
+
+ The function value is the final status of the call
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, ">SerialQueryInformationFile(%p, %p)\n", Device, Irp);
+
+ PAGED_CODE();
+
+
+ IrpSp = IoGetCurrentIrpStackLocation(Irp);
+ Irp->IoStatus.Information = 0L;
+ Status = STATUS_SUCCESS;
+
+ if (IrpSp->Parameters.QueryFile.FileInformationClass ==
+ FileStandardInformation) {
+
+ if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(FILE_STANDARD_INFORMATION))
+ {
+ Status = STATUS_BUFFER_TOO_SMALL;
+ }
+ else
+ {
+ PFILE_STANDARD_INFORMATION Buf = Irp->AssociatedIrp.SystemBuffer;
+
+ Buf->AllocationSize.QuadPart = 0;
+ Buf->EndOfFile = Buf->AllocationSize;
+ Buf->NumberOfLinks = 0;
+ Buf->DeletePending = FALSE;
+ Buf->Directory = FALSE;
+ Irp->IoStatus.Information = sizeof(FILE_STANDARD_INFORMATION);
+ }
+
+ } else if (IrpSp->Parameters.QueryFile.FileInformationClass ==
+ FilePositionInformation) {
+
+ if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(FILE_POSITION_INFORMATION))
+ {
+ Status = STATUS_BUFFER_TOO_SMALL;
+ }
+ else
+ {
+
+ ((PFILE_POSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->
+ CurrentByteOffset.QuadPart = 0;
+ Irp->IoStatus.Information = sizeof(FILE_POSITION_INFORMATION);
+ }
+
+ } else {
+ Status = STATUS_INVALID_PARAMETER;
+ }
+
+ Irp->IoStatus.Status = Status;
+
+ IoCompleteRequest(Irp, IO_NO_INCREMENT);
+
+ return Status;
+
+}
+
+NTSTATUS
+SerialSetInformationFile(
+ IN WDFDEVICE Device,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to set the end of file information on
+ the opened parallel port. Any other file information request
+ is retured with an invalid parameter.
+
+ This routine always ignores the actual end of file since
+ the query information code always returns an end of file of 0.
+
+Arguments:
+
+ DeviceObject - Pointer to the device object for this device
+
+ Irp - Pointer to the IRP for the current request
+
+Return Value:
+
+The function value is the final status of the call
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, ">SerialSetInformationFile(%p, %p)\n", Device, Irp);
+
+ Irp->IoStatus.Information = 0L;
+ if ((IoGetCurrentIrpStackLocation(Irp)->
+ Parameters.SetFile.FileInformationClass ==
+ FileEndOfFileInformation) ||
+ (IoGetCurrentIrpStackLocation(Irp)->
+ Parameters.SetFile.FileInformationClass ==
+ FileAllocationInformation)) {
+
+ Status = STATUS_SUCCESS;
+
+ } else {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ }
+
+ Irp->IoStatus.Status = Status;
+
+ IoCompleteRequest(Irp, IO_NO_INCREMENT);
+
+ return Status;
+
+}
+
diff --git a/tests/projects/windows/driver/kmdf/serial/read.c b/tests/projects/windows/driver/kmdf/serial/read.c new file mode 100644 index 000000000..ab745fdd7 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/read.c @@ -0,0 +1,1748 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ read.c
+
+Abstract:
+
+ This module contains the code that is very specific to read
+ operations in the serial driver
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "read.tmh"
+#endif
+
+EVT_WDF_REQUEST_CANCEL SerialCancelCurrentRead;
+
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabReadFromIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateReadByIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateInterruptBuffer;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateAndSwitchToUser;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateAndSwitchToNew;
+
+ULONG
+SerialGetCharsFromIntBuffer(
+ PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+
+NTSTATUS
+SerialResizeBuffer(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+ULONG
+SerialMoveToNewIntBuffer(
+ PSERIAL_DEVICE_EXTENSION Extension,
+ PUCHAR NewBuffer
+ );
+
+VOID
+SerialEvtIoRead(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t Length
+ )
+
+/*++
+
+Routine Description:
+
+ This is the dispatch routine for reading. It validates the parameters
+ for the read request and if all is ok then it places the request
+ on the work queue.
+
+Arguments:
+
+ Queue - Queue handle
+ Request - Handle to the read request
+ Lenght - Length of the data buffer associated with the request.
+ The default property of the queue is to not dispatch
+ zero lenght read & write requests to the driver and
+ complete is with status success. So we will never get
+ a zero length request.
+
+Return Value:
+
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension;
+ NTSTATUS status;
+ WDFDEVICE hDevice;
+ WDF_REQUEST_PARAMETERS params;
+ PREQUEST_CONTEXT reqContext;
+ size_t bufLen;
+
+ hDevice = WdfIoQueueGetDevice(Queue);
+ extension = SerialGetDeviceExtension(hDevice);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ,
+ ">SerialEvtIoRead(%p, 0x%I64x)\n", Request, Length);
+
+ if (SerialCompleteIfError(extension, Request) != STATUS_SUCCESS) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "<SerialEvtIoRead (2) %d\n", STATUS_CANCELLED);
+ return;
+
+ }
+
+ WDF_REQUEST_PARAMETERS_INIT(¶ms);
+
+ WdfRequestGetParameters(
+ Request,
+ ¶ms
+ );
+
+ //
+ // Initialize the scratch area of the request.
+ //
+ reqContext = SerialGetRequestContext(Request);
+ reqContext->MajorFunction = params.Type;
+ reqContext->Length = (ULONG) Length;
+
+ status = WdfRequestRetrieveOutputBuffer (Request, Length, &reqContext->SystemBuffer, &bufLen);
+
+ if (!NT_SUCCESS (status)) {
+
+ SerialCompleteRequest(Request , status, 0);
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_READ, "<SerialEvtIoRead (5) %X\n", status);
+ return;
+ }
+
+ ASSERT(bufLen == reqContext->Length);
+
+ //
+ // Well it looks like we actually have to do some
+ // work. Put the read on the queue so that we can
+ // process it when our previous reads are done.
+ //
+ SerialStartOrQueue(extension, Request, extension->ReadQueue,
+ &extension->CurrentReadRequest, SerialStartRead);
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "<SerialEvtIoRead (3) %X\n", status);
+
+ return;
+
+}
+
+VOID
+SerialStartRead(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to start off any read. It initializes
+ the Iostatus fields of the request. It will set up any timers
+ that are used to control the read. It will attempt to complete
+ the read from data already in the interrupt buffer. If the
+ read can be completed quickly it will start off another if
+ necessary.
+
+Arguments:
+
+ Extension - Simply a pointer to the serial device extension.
+
+Return Value:
+
+ This routine will return the status of the first read
+ request. This is useful in that if we have a read that can
+ complete right away (AND there had been nothing in the
+ queue before it) the read could return SUCCESS and the
+ application won't have to do a wait.
+
+--*/
+
+{
+
+ SERIAL_UPDATE_CHAR updateChar;
+
+ WDFREQUEST newRequest;
+
+ BOOLEAN returnWithWhatsPresent;
+ BOOLEAN os2ssreturn;
+ BOOLEAN crunchDownToOne;
+ BOOLEAN useTotalTimer;
+ BOOLEAN useIntervalTimer;
+
+ ULONG multiplierVal = 0;
+ ULONG constantVal = 0;
+
+ LARGE_INTEGER totalTime = {0};
+
+ SERIAL_TIMEOUTS timeoutsForIrp;
+
+ PREQUEST_CONTEXT reqContext;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ,
+ ">SerialStartRead(%p)\n", Extension);
+
+ updateChar.Extension = Extension;
+
+
+ do {
+
+ reqContext = SerialGetRequestContext(Extension->CurrentReadRequest);
+
+ //
+ // Check to see if this is a resize request. If it is
+ // then go to a routine that specializes in that.
+ //
+
+ if (reqContext->MajorFunction != IRP_MJ_READ) {
+
+ NTSTATUS localStatus = SerialResizeBuffer(Extension);
+ UNREFERENCED_PARAMETER(localStatus);
+ ASSERT(NT_SUCCESS(localStatus));
+
+ } else {
+
+ Extension->NumberNeededForRead = reqContext->Length;
+
+ //
+ // Calculate the timeout value needed for the
+ // request. Note that the values stored in the
+ // timeout record are in milliseconds.
+ //
+
+ useTotalTimer = FALSE;
+ returnWithWhatsPresent = FALSE;
+ os2ssreturn = FALSE;
+ crunchDownToOne = FALSE;
+ useIntervalTimer = FALSE;
+
+ //
+ //
+ // CIMEXCIMEX -- this is a lie
+ //
+ // Always initialize the timer objects so that the
+ // completion code can tell when it attempts to
+ // cancel the timers whether the timers had ever
+ // been Set.
+ //
+ // CIMEXCIMEX -- this is the truth
+ //
+ // What we want to do is just make sure the timers are
+ // cancelled to the best of our ability and move on with
+ // life.
+ //
+
+ SerialCancelTimer(Extension->ReadRequestTotalTimer, Extension);
+ SerialCancelTimer(Extension->ReadRequestIntervalTimer, Extension);
+
+ //
+ // We get the *current* timeout values to use for timing
+ // this read.
+ //
+
+
+ timeoutsForIrp = Extension->Timeouts;
+
+ //
+ // Calculate the interval timeout for the read.
+ //
+
+ if (timeoutsForIrp.ReadIntervalTimeout &&
+ (timeoutsForIrp.ReadIntervalTimeout !=
+ MAXULONG)) {
+
+ useIntervalTimer = TRUE;
+
+ Extension->IntervalTime.QuadPart =
+ UInt32x32To64(
+ timeoutsForIrp.ReadIntervalTimeout,
+ 10000
+ );
+
+
+ if (Extension->IntervalTime.QuadPart >=
+ Extension->CutOverAmount.QuadPart) {
+
+ Extension->IntervalTimeToUse =
+ &Extension->LongIntervalAmount;
+
+ } else {
+
+ Extension->IntervalTimeToUse =
+ &Extension->ShortIntervalAmount;
+
+ }
+
+ }
+
+ if (timeoutsForIrp.ReadIntervalTimeout == MAXULONG) {
+
+ //
+ // We need to do special return quickly stuff here.
+ //
+ // 1) If both constant and multiplier are
+ // 0 then we return immediately with whatever
+ // we've got, even if it was zero.
+ //
+ // 2) If constant and multiplier are not MAXULONG
+ // then return immediately if any characters
+ // are present, but if nothing is there, then
+ // use the timeouts as specified.
+ //
+ // 3) If multiplier is MAXULONG then do as in
+ // "2" but return when the first character
+ // arrives.
+ //
+
+ if (!timeoutsForIrp.ReadTotalTimeoutConstant &&
+ !timeoutsForIrp.ReadTotalTimeoutMultiplier) {
+
+ returnWithWhatsPresent = TRUE;
+
+ } else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG)
+ &&
+ (timeoutsForIrp.ReadTotalTimeoutMultiplier
+ != MAXULONG)) {
+
+ useTotalTimer = TRUE;
+ os2ssreturn = TRUE;
+ multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier;
+ constantVal = timeoutsForIrp.ReadTotalTimeoutConstant;
+
+ } else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG)
+ &&
+ (timeoutsForIrp.ReadTotalTimeoutMultiplier
+ == MAXULONG)) {
+
+ useTotalTimer = TRUE;
+ os2ssreturn = TRUE;
+ crunchDownToOne = TRUE;
+ multiplierVal = 0;
+ constantVal = timeoutsForIrp.ReadTotalTimeoutConstant;
+
+ }
+
+ } else {
+
+ //
+ // If both the multiplier and the constant are
+ // zero then don't do any total timeout processing.
+ //
+
+ if (timeoutsForIrp.ReadTotalTimeoutMultiplier ||
+ timeoutsForIrp.ReadTotalTimeoutConstant) {
+
+ //
+ // We have some timer values to calculate.
+ //
+
+ useTotalTimer = TRUE;
+ multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier;
+ constantVal = timeoutsForIrp.ReadTotalTimeoutConstant;
+
+ }
+
+ }
+
+ if (useTotalTimer) {
+
+ totalTime.QuadPart = ((LONGLONG)(UInt32x32To64(
+ Extension->NumberNeededForRead,
+ multiplierVal
+ )
+ + constantVal))
+ * -10000;
+
+ }
+
+
+ //
+ // We do this copy in the hope of getting most (if not
+ // all) of the characters out of the interrupt buffer.
+ //
+ // Note that we need to protect this operation with a
+ // spinlock since we don't want a purge to hose us.
+ //
+
+ updateChar.CharsCopied = SerialGetCharsFromIntBuffer(Extension);
+
+ //
+ // See if we have any cause to return immediately.
+ //
+
+ if (returnWithWhatsPresent || (!Extension->NumberNeededForRead) ||
+ (os2ssreturn &&
+ reqContext->Information)) {
+
+ //
+ // We got all we needed for this read.
+ // Update the number of characters in the
+ // interrupt read buffer.
+ //
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialUpdateInterruptBuffer,
+ &updateChar
+ );
+
+ reqContext->Status = STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // The request might go under control of the isr. It
+ // won't hurt to initialize the reference count
+ // right now.
+ //
+
+ SERIAL_INIT_REFERENCE(reqContext);
+
+ //
+ // If we are supposed to crunch the read down to
+ // one character, then update the read length
+ // in the request and truncate the number needed for
+ // read down to one. Note that if we are doing
+ // this crunching, then the information must be
+ // zero (or we would have completed above) and
+ // the number needed for the read must still be
+ // equal to the read length.
+ //
+
+ if (crunchDownToOne) {
+
+ ASSERT(
+ (!reqContext->Information)
+ &&
+ (Extension->NumberNeededForRead == reqContext->Length)
+ );
+
+ Extension->NumberNeededForRead = 1;
+ reqContext->Length = 1;
+
+ }
+
+ //
+ // We still need to get more characters for this read.
+ // synchronize with the isr so that we can update the
+ // number of characters and if necessary it will have the
+ // isr switch to copying into the users buffer.
+ //
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialUpdateAndSwitchToUser,
+ &updateChar
+ );
+
+ if (!updateChar.Completed) {
+
+ SerialSetCancelRoutine(Extension->CurrentReadRequest,
+ SerialCancelCurrentRead);
+
+ //
+ // The request still isn't complete. The
+ // completion routines will end up reinvoking
+ // this routine. So we simply leave.
+ //
+ // First thought we should start off the total
+ // timer for the read and increment the reference
+ // count that the total timer has on the current
+ // request. Note that this is safe, because even if
+ // the io has been satisfied by the isr it can't
+ // complete yet because we still own the cancel
+ // spinlock.
+ //
+
+ if (useTotalTimer) {
+ BOOLEAN result;
+
+ result = SerialSetTimer(
+ Extension->ReadRequestTotalTimer,
+ totalTime
+ );
+
+ if(result == FALSE) {
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_TOTAL_TIMER
+ );
+ }
+
+ }
+
+ if (useIntervalTimer) {
+
+ BOOLEAN result;
+
+ KeQuerySystemTime(
+ &Extension->LastReadTime
+
+ );
+ result = SerialSetTimer(
+ Extension->ReadRequestIntervalTimer,
+ *Extension->IntervalTimeToUse
+ );
+
+ if(result == FALSE) {
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_INT_TIMER
+ );
+ }
+
+ }
+
+ break;
+
+ } else {
+
+ reqContext->Status = STATUS_SUCCESS;
+ }
+
+ }
+
+ }
+
+ //
+ // Well the operation is complete.
+ //
+
+ SerialGetNextRequest(&Extension->CurrentReadRequest,
+ Extension->ReadQueue,
+ &newRequest, TRUE, Extension);
+
+ } while (newRequest);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "<SerialStartRead \n");
+
+ return;
+
+}
+
+
+VOID
+SerialCompleteRead(
+ IN WDFDPC Dpc
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is merely used to complete any read that
+ ended up being used by the Isr. It assumes that the
+ status and the information fields of the request are already
+ correctly filled in.
+
+Arguments:
+
+ Dpc - Not Used.
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = NULL;
+
+ extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc));
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, ">SerialCompleteRead(%p)\n",
+ extension);
+
+ //
+ // We set this to indicate to the interval timer
+ // that the read has completed.
+ //
+ // Recall that the interval timer dpc can be lurking in some
+ // DPC queue.
+ //
+
+ extension->CountOnLastRead = SERIAL_COMPLETE_READ_COMPLETE;
+
+ SerialTryToCompleteCurrent(
+ extension,
+ NULL,
+ STATUS_SUCCESS,
+ &extension->CurrentReadRequest,
+ extension->ReadQueue,
+ extension->ReadRequestIntervalTimer,
+ extension->ReadRequestTotalTimer,
+ SerialStartRead,
+ SerialGetNextRequest,
+ SERIAL_REF_ISR
+ );
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "<SerialCompleteRead\n");
+}
+
+
+VOID
+SerialCancelCurrentRead(
+ WDFREQUEST Request
+ )
+/*++
+
+Routine Description:
+
+ This routine is used to cancel the current read.
+
+Arguments:
+
+ Device - Wdf device handle
+
+ Request - Pointer to the WDFREQUEST to be canceled.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = NULL;
+ WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request));
+
+ UNREFERENCED_PARAMETER(Request);
+
+ extension = SerialGetDeviceExtension(device);
+
+ //
+ // We set this to indicate to the interval timer
+ // that the read has encountered a cancel.
+ //
+ // Recall that the interval timer dpc can be lurking in some
+ // DPC queue.
+ //
+
+ extension->CountOnLastRead = SERIAL_COMPLETE_READ_CANCEL;
+
+ SerialTryToCompleteCurrent(
+ extension,
+ SerialGrabReadFromIsr,
+ STATUS_CANCELLED,
+ &extension->CurrentReadRequest,
+ extension->ReadQueue,
+ extension->ReadRequestIntervalTimer,
+ extension->ReadRequestTotalTimer,
+ SerialStartRead,
+ SerialGetNextRequest,
+ SERIAL_REF_CANCEL
+ );
+
+}
+
+
+BOOLEAN
+SerialGrabReadFromIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to grab (if possible) the request from the
+ isr. If it finds that the isr still owns the request it grabs
+ the ipr away (updating the number of characters copied into the
+ users buffer). If it grabs it away it also decrements the
+ reference count on the request since it no longer belongs to the
+ isr (and the dpc that would complete it).
+
+ NOTE: This routine assumes that if the current buffer that the
+ ISR is copying characters into is the interrupt buffer then
+ the dpc has already been queued.
+
+ NOTE: This routine is being called from WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that it is called with the cancel spin
+ lock held.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always false.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = Context;
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(extension->CurrentReadRequest);
+
+ if (extension->ReadBufferBase !=
+ extension->InterruptReadBuffer) {
+
+ //
+ // We need to set the information to the number of characters
+ // that the read wanted minus the number of characters that
+ // didn't get read into the interrupt buffer.
+ //
+
+ reqContext->Information = reqContext->Length -
+ ((extension->LastCharSlot - extension->CurrentCharSlot) + 1);
+
+ //
+ // Switch back to the interrupt buffer.
+ //
+
+ extension->ReadBufferBase = extension->InterruptReadBuffer;
+ extension->CurrentCharSlot = extension->InterruptReadBuffer;
+ extension->FirstReadableChar = extension->InterruptReadBuffer;
+ extension->LastCharSlot = extension->InterruptReadBuffer +
+ (extension->BufferSize - 1);
+ extension->CharsInInterruptBuffer = 0;
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ }
+
+ return FALSE;
+
+}
+
+VOID
+SerialReadTimeout(
+ IN WDFTIMER Timer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to complete a read because its total
+ timer has expired.
+
+Arguments:
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = NULL;
+
+ extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer));
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, ">SerialReadTimeout(%p)\n",
+ extension);
+
+ //
+ // We set this to indicate to the interval timer
+ // that the read has completed due to total timeout.
+ //
+ // Recall that the interval timer dpc can be lurking in some
+ // DPC queue.
+ //
+
+ extension->CountOnLastRead = SERIAL_COMPLETE_READ_TOTAL;
+
+ SerialTryToCompleteCurrent(
+ extension,
+ SerialGrabReadFromIsr,
+ STATUS_TIMEOUT,
+ &extension->CurrentReadRequest,
+ extension->ReadQueue,
+ extension->ReadRequestIntervalTimer,
+ extension->ReadRequestTotalTimer,
+ SerialStartRead,
+ SerialGetNextRequest,
+ SERIAL_REF_TOTAL_TIMER
+ );
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "<SerialReadTimeout\n");
+}
+
+
+BOOLEAN
+SerialUpdateReadByIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to update the count of characters read
+ by the isr since the last interval timer experation.
+
+ NOTE: This routine is being called from WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that it is called with the cancel spin
+ lock held.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always false.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ extension->CountOnLastRead = extension->ReadByIsr;
+ extension->ReadByIsr = 0;
+
+ return FALSE;
+
+}
+
+
+VOID
+SerialIntervalReadTimeout(
+ IN WDFTIMER Timer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used timeout the request if the time between
+ characters exceed the interval time. A global is kept in
+ the device extension that records the count of characters read
+ the last the last time this routine was invoked (This dpc
+ will resubmit the timer if the count has changed). If the
+ count has not changed then this routine will attempt to complete
+ the request. Note the special case of the last count being zero.
+ The timer isn't really in effect until the first character is
+ read.
+
+Arguments:
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION extension = NULL;
+
+ extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer));
+
+
+ //SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, ">SerialIntervalReadTimeout(%p)\n",
+ // extension);
+
+ if (extension->CountOnLastRead == SERIAL_COMPLETE_READ_TOTAL) {
+
+ //
+ // This value is only set by the total
+ // timer to indicate that it has fired.
+ // If so, then we should simply try to complete.
+ //
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, "in SERIAL_COMPLETE_READ_TOTAL\n");
+
+ SerialTryToCompleteCurrent(
+ extension,
+ SerialGrabReadFromIsr,
+ STATUS_TIMEOUT,
+ &extension->CurrentReadRequest,
+ extension->ReadQueue,
+ extension->ReadRequestIntervalTimer,
+ extension->ReadRequestTotalTimer,
+ SerialStartRead,
+ SerialGetNextRequest,
+ SERIAL_REF_INT_TIMER
+ );
+
+ } else if (extension->CountOnLastRead == SERIAL_COMPLETE_READ_COMPLETE) {
+
+ //
+ // This value is only set by the regular
+ // completion routine.
+ //
+ // If so, then we should simply try to complete.
+ //
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, "in SERIAL_COMPLETE_READ_COMPLETE\n");
+
+ SerialTryToCompleteCurrent(
+ extension,
+ SerialGrabReadFromIsr,
+ STATUS_SUCCESS,
+ &extension->CurrentReadRequest,
+ extension->ReadQueue,
+ extension->ReadRequestIntervalTimer,
+ extension->ReadRequestTotalTimer,
+ SerialStartRead,
+ SerialGetNextRequest,
+ SERIAL_REF_INT_TIMER
+ );
+
+ } else if (extension->CountOnLastRead == SERIAL_COMPLETE_READ_CANCEL) {
+
+ //
+ // This value is only set by the cancel
+ // read routine.
+ //
+ // If so, then we should simply try to complete.
+ //
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, "in SERIAL_COMPLETE_READ_CANCEL\n");
+
+ SerialTryToCompleteCurrent(
+ extension,
+ SerialGrabReadFromIsr,
+ STATUS_CANCELLED,
+ &extension->CurrentReadRequest,
+ extension->ReadQueue,
+ extension->ReadRequestIntervalTimer,
+ extension->ReadRequestTotalTimer,
+ SerialStartRead,
+ SerialGetNextRequest,
+ SERIAL_REF_INT_TIMER
+ );
+
+ } else if (extension->CountOnLastRead || extension->ReadByIsr) {
+
+ //
+ // Something has happened since we last came here. We
+ // check to see if the ISR has read in any more characters.
+ // If it did then we should update the isr's read count
+ // and resubmit the timer.
+ //
+
+ if (extension->ReadByIsr) {
+
+ WdfInterruptSynchronize(
+ extension->WdfInterrupt,
+ SerialUpdateReadByIsr,
+ extension
+ );
+
+ //
+ // Save off the "last" time something was read.
+ // As we come back to this routine we will compare
+ // the current time to the "last" time. If the
+ // difference is ever larger then the interval
+ // requested by the user, then time out the request.
+ //
+
+ KeQuerySystemTime(
+ &extension->LastReadTime
+ );
+
+ SerialSetTimer(
+ extension->ReadRequestIntervalTimer,
+ *extension->IntervalTimeToUse
+ );
+
+ } else {
+
+ //
+ // Take the difference between the current time
+ // and the last time we had characters and
+ // see if it is greater then the interval time.
+ // if it is, then time out the request. Otherwise
+ // go away again for a while.
+ //
+
+ //
+ // No characters read in the interval time. Kill
+ // this read.
+ //
+
+ LARGE_INTEGER currentTime;
+
+ KeQuerySystemTime(
+ ¤tTime
+ );
+
+ if ((currentTime.QuadPart - extension->LastReadTime.QuadPart) >=
+ extension->IntervalTime.QuadPart) {
+
+ SerialTryToCompleteCurrent(
+ extension,
+ SerialGrabReadFromIsr,
+ STATUS_TIMEOUT,
+ &extension->CurrentReadRequest,
+ extension->ReadQueue,
+ extension->ReadRequestIntervalTimer,
+ extension->ReadRequestTotalTimer,
+ SerialStartRead,
+ SerialGetNextRequest,
+ SERIAL_REF_INT_TIMER
+ );
+
+ } else {
+
+ SerialSetTimer(
+ extension->ReadRequestIntervalTimer,
+ *extension->IntervalTimeToUse
+ );
+
+ }
+
+
+ }
+
+ } else {
+
+ //
+ // Timer doesn't really start until the first character.
+ // So we should simply resubmit ourselves.
+ //
+
+ SerialSetTimer(
+ extension->ReadRequestIntervalTimer,
+ *extension->IntervalTimeToUse
+ );
+
+ }
+
+
+ //SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "<SerialIntervalReadTimeout\n");
+}
+
+
+ULONG
+SerialGetCharsFromIntBuffer(
+ PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to copy any characters out of the interrupt
+ buffer into the users buffer. It will be reading values that
+ are updated with the ISR but this is safe since this value is
+ only decremented by synchronization routines. This routine will
+ return the number of characters copied so some other routine
+ can call a synchronization routine to update what is seen at
+ interrupt level.
+
+Arguments:
+
+ Extension - A pointer to the device extension.
+
+Return Value:
+
+ The number of characters that were copied into the user
+ buffer.
+
+--*/
+
+{
+
+ //
+ // This value will be the number of characters that this
+ // routine returns. It will be the minimum of the number
+ // of characters currently in the buffer or the number of
+ // characters required for the read.
+ //
+ ULONG numberOfCharsToGet;
+
+ //
+ // This holds the number of characters between the first
+ // readable character and - the last character we will read or
+ // the real physical end of the buffer (not the last readable
+ // character).
+ //
+ ULONG firstTryNumberToGet;
+
+ PREQUEST_CONTEXT reqContext = SerialGetRequestContext(Extension->CurrentReadRequest);
+
+ //
+ // The minimum of the number of characters we need and
+ // the number of characters available
+ //
+
+ numberOfCharsToGet = Extension->CharsInInterruptBuffer;
+
+ if (numberOfCharsToGet > Extension->NumberNeededForRead) {
+
+ numberOfCharsToGet = Extension->NumberNeededForRead;
+
+ }
+
+ if (numberOfCharsToGet) {
+
+ //
+ // This will hold the number of characters between the
+ // first available character and the end of the buffer.
+ // Note that the buffer could wrap around but for the
+ // purposes of the first copy we don't care about that.
+ //
+
+ firstTryNumberToGet = (ULONG)(Extension->LastCharSlot -
+ Extension->FirstReadableChar) + 1;
+
+ if (firstTryNumberToGet > numberOfCharsToGet) {
+
+ //
+ // The characters don't wrap. Actually they may wrap but
+ // we don't care for the purposes of this read since the
+ // characters we need are available before the wrap.
+ //
+
+ RtlMoveMemory(
+ ((PUCHAR)(reqContext->SystemBuffer))
+ + (reqContext->Length - Extension->NumberNeededForRead),
+ Extension->FirstReadableChar,
+ numberOfCharsToGet
+ );
+
+ Extension->NumberNeededForRead -= numberOfCharsToGet;
+
+ //
+ // We now will move the pointer to the first character after
+ // what we just copied into the users buffer.
+ //
+ // We need to check if the stream of readable characters
+ // is wrapping around to the beginning of the buffer.
+ //
+ // Note that we may have just taken the last characters
+ // at the end of the buffer.
+ //
+
+ if ((Extension->FirstReadableChar + (numberOfCharsToGet - 1)) ==
+ Extension->LastCharSlot) {
+
+ Extension->FirstReadableChar = Extension->InterruptReadBuffer;
+
+ } else {
+
+ Extension->FirstReadableChar += numberOfCharsToGet;
+
+ }
+
+ } else {
+
+ //
+ // The characters do wrap. Get up until the end of the buffer.
+ //
+
+ RtlMoveMemory(
+ ((PUCHAR)(reqContext->SystemBuffer))
+ + (reqContext->Length - Extension->NumberNeededForRead),
+ Extension->FirstReadableChar,
+ firstTryNumberToGet
+ );
+
+ Extension->NumberNeededForRead -= firstTryNumberToGet;
+
+ //
+ // Now get the rest of the characters from the beginning of the
+ // buffer.
+ //
+
+ RtlMoveMemory(
+ ((PUCHAR)(reqContext->SystemBuffer))
+ + (reqContext->Length - Extension->NumberNeededForRead),
+ Extension->InterruptReadBuffer,
+ numberOfCharsToGet - firstTryNumberToGet
+ );
+
+ Extension->FirstReadableChar = Extension->InterruptReadBuffer +
+ (numberOfCharsToGet -
+ firstTryNumberToGet);
+
+ Extension->NumberNeededForRead -= (numberOfCharsToGet -
+ firstTryNumberToGet);
+
+ }
+
+ }
+
+ reqContext->Information += numberOfCharsToGet;
+ return numberOfCharsToGet;
+
+}
+
+
+BOOLEAN
+SerialUpdateInterruptBuffer(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to update the number of characters that
+ remain in the interrupt buffer. We need to use this routine
+ since the count could be updated during the update by execution
+ of the ISR.
+
+ NOTE: This is called by WdfInterruptSynchronize.
+
+Arguments:
+
+ Context - Points to a structure that contains a pointer to the
+ device extension and count of the number of characters
+ that we previously copied into the users buffer. The
+ structure actually has a third field that we don't
+ use in this routine.
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+
+ PSERIAL_UPDATE_CHAR update = Context;
+ PSERIAL_DEVICE_EXTENSION extension = update->Extension;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ ASSERT(extension->CharsInInterruptBuffer >= update->CharsCopied);
+ extension->CharsInInterruptBuffer -= update->CharsCopied;
+
+ //
+ // Deal with flow control if necessary.
+ //
+
+ SerialHandleReducedIntBuffer(extension);
+
+
+ return FALSE;
+
+}
+
+
+BOOLEAN
+SerialUpdateAndSwitchToUser(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine gets the (hopefully) few characters that
+ remain in the interrupt buffer after the first time we tried
+ to get them out. If we still don't have enough characters
+ to satisfy the read it will then we set things up so that the
+ ISR uses the user buffer copy into.
+
+ This routine is also used to update a count that is maintained
+ by the ISR to keep track of the number of characters in its buffer.
+
+ NOTE: This is called by WdfInterruptSynchronize.
+
+Arguments:
+
+ Context - Points to a structure that contains a pointer to the
+ device extension, a count of the number of characters
+ that we previously copied into the users buffer, and
+ a boolean that we will set that defines whether we
+ switched the ISR to copy into the users buffer.
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+
+ PSERIAL_UPDATE_CHAR updateChar = Context;
+ PSERIAL_DEVICE_EXTENSION extension = updateChar->Extension;
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(extension->CurrentReadRequest);
+
+ SerialUpdateInterruptBuffer(extension->WdfInterrupt, Context);
+
+ //
+ // There are more characters to get to satisfy this read.
+ // Copy any characters that have arrived since we got
+ // the last batch.
+ //
+
+ updateChar->CharsCopied = SerialGetCharsFromIntBuffer(extension);
+
+ SerialUpdateInterruptBuffer(extension->WdfInterrupt, Context);
+
+ //
+ // No more new characters will be "received" until we exit
+ // this routine. We again check to make sure that we
+ // haven't satisfied this read, and if we haven't we set things
+ // up so that the ISR copies into the user buffer.
+ //
+
+ if (extension->NumberNeededForRead) {
+
+ //
+ // We shouldn't be switching unless there are no
+ // characters left.
+ //
+
+ ASSERT(!extension->CharsInInterruptBuffer);
+
+ //
+ // We use the following to values to do inteval timing.
+ //
+ // CountOnLastRead is mostly used to simply prevent
+ // the interval timer from timing out before any characters
+ // are read. (Interval timing should only be effective
+ // after the first character is read.)
+ //
+ // After the first time the interval timer fires and
+ // characters have be read we will simply update with
+ // the value of ReadByIsr and then set ReadByIsr to zero.
+ // (We do that in a synchronization routine.
+ //
+ // If the interval timer dpc routine ever encounters
+ // ReadByIsr == 0 when CountOnLastRead is non-zero it
+ // will timeout the read.
+ //
+ // (Note that we have a special case of CountOnLastRead
+ // < 0. This is done by the read completion routines other
+ // than the total timeout dpc to indicate that the total
+ // timeout has expired.)
+ //
+
+ extension->CountOnLastRead = (LONG)reqContext->Information;
+
+ extension->ReadByIsr = 0;
+
+ //
+ // By compareing the read buffer base address to the
+ // the base address of the interrupt buffer the ISR
+ // can determine whether we are using the interrupt
+ // buffer or the user buffer.
+ //
+
+ extension->ReadBufferBase = reqContext->SystemBuffer;
+
+ //
+ // The current char slot is after the last copied in
+ // character. We know there is always room since we
+ // we wouldn't have gotten here if there wasn't.
+ //
+
+ extension->CurrentCharSlot = extension->ReadBufferBase +
+ reqContext->Information;
+
+ //
+ // The last position that a character can go is on the
+ // last byte of user buffer. While the actual allocated
+ // buffer space may be bigger, we know that there is at
+ // least as much as the read length.
+ //
+
+ extension->LastCharSlot = extension->ReadBufferBase +
+ (reqContext->Length - 1);
+#if 0 // We set the cancel before calling this routine in StartRead
+ //
+ // Mark the request as being in a cancelable state.
+ //
+ IoSetCancelRoutine(
+ extension->CurrentReadIrp,
+ SerialCancelCurrentRead
+ );
+
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_CANCEL
+ );
+#endif
+ //
+ // Increment the reference count twice.
+ //
+ // Once for the Isr owning the request and once
+ // because the cancel routine has a reference
+ // to it.
+ //
+
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ updateChar->Completed = FALSE;
+
+ } else {
+
+ updateChar->Completed = TRUE;
+
+ }
+
+ return FALSE;
+
+}
+//
+// We use this structure only to communicate to the synchronization
+// routine when we are switching to the resized buffer.
+//
+typedef struct _SERIAL_RESIZE_PARAMS {
+ PSERIAL_DEVICE_EXTENSION Extension;
+ PUCHAR OldBuffer;
+ PUCHAR NewBuffer;
+ ULONG NewBufferSize;
+ ULONG NumberMoved;
+ } SERIAL_RESIZE_PARAMS,*PSERIAL_RESIZE_PARAMS;
+
+
+NTSTATUS
+SerialResizeBuffer(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will process the resize buffer request.
+ If size requested for the RX buffer is smaller than
+ the current buffer then we will simply return
+ STATUS_SUCCESS. (We don't want to make buffers smaller.
+ If we did that then we all of a sudden have "overrun"
+ problems to deal with as well as flow control to deal
+ with - very painful.) We ignore the TX buffer size
+ request since we don't use a TX buffer.
+
+Arguments:
+
+ Extension - Pointer to the device extension for the port.
+
+Return Value:
+
+ STATUS_SUCCESS if everything worked out ok.
+ STATUS_INSUFFICIENT_RESOURCES if we couldn't allocate the
+ memory for the buffer.
+
+--*/
+
+{
+
+ PREQUEST_CONTEXT reqContext = SerialGetRequestContext(Extension->CurrentReadRequest);
+ PSERIAL_QUEUE_SIZE rs = reqContext->SystemBuffer;
+
+ PVOID newBuffer = reqContext->Type3InputBuffer;
+
+
+ reqContext->Type3InputBuffer = NULL;
+ reqContext->Information = 0L;
+ reqContext->Status = STATUS_SUCCESS;
+
+ if (rs->InSize <= Extension->BufferSize) {
+
+ //
+ // Nothing to do. We don't make buffers smaller. Just
+ // agree with the user. We must deallocate the memory
+ // that was already allocated in the ioctl dispatch routine.
+ //
+
+ ExFreePool(newBuffer);
+
+ } else {
+
+ SERIAL_RESIZE_PARAMS rp;
+
+ //
+ // Hmmm, looks like we actually have to go
+ // through with this. We need to move all the
+ // data that is in the current buffer into this
+ // new buffer. We'll do this in two steps.
+ //
+ // First we go up to dispatch level and try to
+ // move as much as we can without stopping the
+ // ISR from running. We go up to dispatch level
+ // by acquiring the control lock. We do it at
+ // dispatch using the control lock so that:
+ //
+ // 1) We can't be context switched in the middle
+ // of the move. Our pointers into the buffer
+ // could be *VERY* stale by the time we got back.
+ //
+ // 2) We use the control lock since we don't want
+ // some pesky purge request to come along while
+ // we are trying to move.
+ //
+ // After the move, but while we still hold the control
+ // lock, we synch with the ISR and get those last
+ // (hopefully) few characters that have come in since
+ // we started the copy. We switch all of our pointers,
+ // counters, and such to point to this new buffer. NOTE:
+ // we need to be careful. If the buffer we were using
+ // was not the default one created when we initialized
+ // the device (i.e. it was created via a previous WDFREQUEST of
+ // this type), we should deallocate it.
+ //
+
+ rp.Extension = Extension;
+ rp.OldBuffer = Extension->InterruptReadBuffer;
+ rp.NewBuffer = newBuffer;
+ rp.NewBufferSize = rs->InSize;
+
+ rp.NumberMoved = SerialMoveToNewIntBuffer(
+ Extension,
+ newBuffer
+ );
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialUpdateAndSwitchToNew,
+ &rp
+ );
+
+ //
+ // Free up the memory that the old buffer consumed.
+ //
+
+ ExFreePool(rp.OldBuffer);
+
+ }
+
+ return STATUS_SUCCESS;
+
+}
+
+
+ULONG
+SerialMoveToNewIntBuffer(
+ PSERIAL_DEVICE_EXTENSION Extension,
+ PUCHAR NewBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to copy any characters out of the interrupt
+ buffer into the "new" buffer. It will be reading values that
+ are updated with the ISR but this is safe since this value is
+ only decremented by synchronization routines. This routine will
+ return the number of characters copied so some other routine
+ can call a synchronization routine to update what is seen at
+ interrupt level.
+
+Arguments:
+
+ Extension - A pointer to the device extension.
+ NewBuffer - Where the characters are to be move to.
+
+Return Value:
+
+ The number of characters that were copied into the user
+ buffer.
+
+--*/
+
+{
+
+ ULONG numberOfCharsMoved = Extension->CharsInInterruptBuffer;
+
+
+ if (numberOfCharsMoved) {
+
+ //
+ // This holds the number of characters between the first
+ // readable character and the last character we will read or
+ // the real physical end of the buffer (not the last readable
+ // character).
+ //
+ ULONG firstTryNumberToGet = (ULONG)(Extension->LastCharSlot -
+ Extension->FirstReadableChar) + 1;
+
+ if (firstTryNumberToGet >= numberOfCharsMoved) {
+
+ //
+ // The characters don't wrap.
+ //
+
+ RtlMoveMemory(
+ NewBuffer,
+ Extension->FirstReadableChar,
+ numberOfCharsMoved
+ );
+
+ if ((Extension->FirstReadableChar+(numberOfCharsMoved-1)) ==
+ Extension->LastCharSlot) {
+
+ Extension->FirstReadableChar = Extension->InterruptReadBuffer;
+
+ } else {
+
+ Extension->FirstReadableChar += numberOfCharsMoved;
+
+ }
+
+ } else {
+
+ //
+ // The characters do wrap. Get up until the end of the buffer.
+ //
+
+ RtlMoveMemory(
+ NewBuffer,
+ Extension->FirstReadableChar,
+ firstTryNumberToGet
+ );
+
+ //
+ // Now get the rest of the characters from the beginning of the
+ // buffer.
+ //
+
+ RtlMoveMemory(
+ NewBuffer+firstTryNumberToGet,
+ Extension->InterruptReadBuffer,
+ numberOfCharsMoved - firstTryNumberToGet
+ );
+
+ Extension->FirstReadableChar = Extension->InterruptReadBuffer +
+ numberOfCharsMoved - firstTryNumberToGet;
+
+ }
+
+ }
+
+ return numberOfCharsMoved;
+
+}
+
+
+BOOLEAN
+SerialUpdateAndSwitchToNew(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine gets the (hopefully) few characters that
+ remain in the interrupt buffer after the first time we tried
+ to get them out.
+
+ NOTE: This is called by WdfInterruptSynchronize.
+
+Arguments:
+
+ Context - Points to a structure that contains a pointer to the
+ device extension, a pointer to the buffer we are moving
+ to, and a count of the number of characters
+ that we previously copied into the new buffer, and the
+ actual size of the new buffer.
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+
+ PSERIAL_RESIZE_PARAMS params = Context;
+ PSERIAL_DEVICE_EXTENSION extension = params->Extension;
+ ULONG tempCharsInInterruptBuffer = extension->CharsInInterruptBuffer;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ ASSERT(extension->CharsInInterruptBuffer >= params->NumberMoved);
+
+ //
+ // We temporarily reduce the chars in interrupt buffer to
+ // "fool" the move routine. We will restore it after the
+ // move.
+ //
+
+ extension->CharsInInterruptBuffer -= params->NumberMoved;
+
+ if (extension->CharsInInterruptBuffer) {
+
+ SerialMoveToNewIntBuffer(
+ extension,
+ params->NewBuffer + params->NumberMoved
+ );
+
+ }
+
+ extension->CharsInInterruptBuffer = tempCharsInInterruptBuffer;
+
+
+ extension->LastCharSlot = params->NewBuffer + (params->NewBufferSize - 1);
+ extension->FirstReadableChar = params->NewBuffer;
+ extension->ReadBufferBase = params->NewBuffer;
+ extension->InterruptReadBuffer = params->NewBuffer;
+ extension->BufferSize = params->NewBufferSize;
+
+ //
+ // We *KNOW* that the new interrupt buffer is larger than the
+ // old buffer. We don't need to worry about it being full.
+ //
+
+ extension->CurrentCharSlot = extension->InterruptReadBuffer +
+ extension->CharsInInterruptBuffer;
+
+ //
+ // We set up the default xon/xoff limits.
+ //
+
+ extension->HandFlow.XoffLimit = extension->BufferSize >> 3;
+ extension->HandFlow.XonLimit = extension->BufferSize >> 1;
+
+ extension->WmiCommData.XoffXmitThreshold = extension->HandFlow.XoffLimit;
+ extension->WmiCommData.XonXmitThreshold = extension->HandFlow.XonLimit;
+
+ extension->BufferSizePt8 = ((3*(extension->BufferSize>>2))+
+ (extension->BufferSize>>4));
+
+ //
+ // Since we (essentially) reduced the percentage of the interrupt
+ // buffer being full, we need to handle any flow control.
+ //
+
+ SerialHandleReducedIntBuffer(extension);
+
+ return FALSE;
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/registry.c b/tests/projects/windows/driver/kmdf/serial/registry.c new file mode 100644 index 000000000..5ab25955a --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/registry.c @@ -0,0 +1,443 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ registry.c
+
+Abstract:
+
+ This module contains the code that is used to get values from the
+ registry and to manipulate entries in the registry.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "registry.tmh"
+#endif
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(INIT,SerialGetConfigDefaults)
+#pragma alloc_text(PAGESRP0,SerialGetRegistryKeyValue)
+#pragma alloc_text(PAGESRP0,SerialPutRegistryKeyValue)
+#pragma alloc_text(PAGESRP0,SerialGetFdoRegistryKeyValue)
+#endif // ALLOC_PRAGMA
+
+
+#define PARAMATER_NAME_LEN 80
+
+
+NTSTATUS
+SerialGetConfigDefaults(
+ IN PSERIAL_FIRMWARE_DATA DriverDefaultsPtr,
+ IN WDFDRIVER Driver
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads the default configuration data from the
+ registry for the serial driver.
+
+ It also builds fields in the registry for several configuration
+ options if they don't exist.
+
+Arguments:
+
+ DriverDefaultsPtr - Pointer to a structure that will contain
+ the default configuration values.
+
+ RegistryPath - points to the entry for this driver in the
+ current control set of the registry.
+
+Return Value:
+
+ STATUS_SUCCESS if we got the defaults, otherwise we failed.
+ The only way to fail this call is if the STATUS_INSUFFICIENT_RESOURCES.
+
+--*/
+
+{
+
+ NTSTATUS status = STATUS_SUCCESS; // return value
+ WDFKEY hKey;
+ DECLARE_UNICODE_STRING_SIZE(valueName,PARAMATER_NAME_LEN);
+
+ status = WdfDriverOpenParametersRegistryKey(Driver,
+ STANDARD_RIGHTS_ALL,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &hKey);
+ if (!NT_SUCCESS (status)) {
+ return status;
+ }
+
+ status = RtlUnicodeStringPrintf(&valueName,L"BreakOnEntry");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->ShouldBreakOnEntry);
+
+ if (!NT_SUCCESS (status)) {
+ DriverDefaultsPtr->ShouldBreakOnEntry = 0;
+ }
+
+ status = RtlUnicodeStringPrintf(&valueName,L"DebugLevel");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->DebugLevel);
+
+ if (!NT_SUCCESS (status)) {
+ DriverDefaultsPtr->DebugLevel = 0;
+ }
+
+
+ status = RtlUnicodeStringPrintf(&valueName,L"ForceFifoEnable");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->ForceFifoEnableDefault);
+
+ if (!NT_SUCCESS (status)) {
+
+ //
+ // If it isn't then write out values so that it could
+ // be adjusted later.
+ //
+ DriverDefaultsPtr->ForceFifoEnableDefault = SERIAL_FORCE_FIFO_DEFAULT;
+
+ status = WdfRegistryAssignULong(hKey,
+ &valueName,
+ DriverDefaultsPtr->ForceFifoEnableDefault
+ );
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ }
+
+ status = RtlUnicodeStringPrintf(&valueName,L"RxFIFO");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->RxFIFODefault);
+
+ if (!NT_SUCCESS (status)) {
+
+ DriverDefaultsPtr->RxFIFODefault = SERIAL_RX_FIFO_DEFAULT;
+
+ status = WdfRegistryAssignULong(hKey,
+ &valueName,
+ DriverDefaultsPtr->RxFIFODefault
+ );
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ }
+
+ status = RtlUnicodeStringPrintf(&valueName,L"TxFIFO");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->TxFIFODefault);
+
+ if (!NT_SUCCESS (status)) {
+
+ DriverDefaultsPtr->TxFIFODefault = SERIAL_TX_FIFO_DEFAULT;
+
+ status = WdfRegistryAssignULong(hKey,
+ &valueName,
+ DriverDefaultsPtr->TxFIFODefault
+ );
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ }
+
+ status = RtlUnicodeStringPrintf(&valueName,L"PermitShare");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->PermitShareDefault);
+
+ if (!NT_SUCCESS (status)) {
+
+ DriverDefaultsPtr->PermitShareDefault = SERIAL_PERMIT_SHARE_DEFAULT;
+
+ status = WdfRegistryAssignULong(hKey,
+ &valueName,
+ DriverDefaultsPtr->PermitShareDefault
+ );
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ }
+
+ status = RtlUnicodeStringPrintf(&valueName,L"LogFifo");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->LogFifoDefault);
+
+ if (!NT_SUCCESS (status)) {
+
+ DriverDefaultsPtr->LogFifoDefault = SERIAL_LOG_FIFO_DEFAULT;
+
+ status = WdfRegistryAssignULong(hKey,
+ &valueName,
+ DriverDefaultsPtr->LogFifoDefault
+ );
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ DriverDefaultsPtr->LogFifoDefault = 1;
+ }
+
+
+ status = RtlUnicodeStringPrintf(&valueName,L"UartRemovalDetect");
+ if (!NT_SUCCESS (status)) {
+ goto End;
+ }
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ &DriverDefaultsPtr->UartRemovalDetect);
+
+ if (!NT_SUCCESS (status)) {
+ DriverDefaultsPtr->UartRemovalDetect = 0;
+ }
+
+
+End:
+ WdfRegistryClose(hKey);
+ return (status);
+}
+
+BOOLEAN
+SerialGetRegistryKeyValue(
+ IN WDFDEVICE WdfDevice,
+ _In_ PCWSTR Name,
+ OUT PULONG Value
+ )
+/*++
+
+Routine Description:
+
+ Can be used to read any REG_DWORD registry value stored
+ under Device Parameter.
+
+Arguments:
+
+ FdoData - pointer to the device extension
+ Name - Name of the registry value
+ Value -
+
+
+Return Value:
+
+ TRUE if successful
+ FALSE if not present/error in reading registry
+
+--*/
+{
+ WDFKEY hKey = NULL;
+ NTSTATUS status;
+ BOOLEAN retValue = FALSE;
+ UNICODE_STRING valueName;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, ">SerialGetRegistryKeyValue(XXX)\n");
+
+ *Value = 0;
+
+ status = WdfDeviceOpenRegistryKey(WdfDevice,
+ PLUGPLAY_REGKEY_DEVICE,
+ STANDARD_RIGHTS_ALL,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &hKey);
+
+ if (NT_SUCCESS (status)) {
+
+ RtlInitUnicodeString(&valueName,Name);
+
+ status = WdfRegistryQueryULong (hKey,
+ &valueName,
+ Value);
+
+ if (NT_SUCCESS (status)) {
+ retValue = TRUE;
+ }
+
+ WdfRegistryClose(hKey);
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "<--SerialGetRegistryKeyValue %ws %d \n",
+ Name, *Value);
+
+ return retValue;
+}
+
+#define PARAMATER_NAME_LEN 80
+
+BOOLEAN
+SerialPutRegistryKeyValue(
+ IN WDFDEVICE WdfDevice,
+ _In_ PCWSTR Name,
+ IN ULONG Value
+ )
+/*++
+
+Routine Description:
+
+ Can be used to write any REG_DWORD registry value stored
+ under Device Parameter.
+
+Arguments:
+
+
+Return Value:
+
+ TRUE - if write is successful
+ FALSE - otherwise
+
+--*/
+{
+ WDFKEY hKey = NULL;
+ NTSTATUS status;
+ BOOLEAN retValue = FALSE;
+ UNICODE_STRING valueName;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "Entered PciDrvWriteRegistryValue\n");
+
+ //
+ // write the value out to the registry
+ //
+ status = WdfDeviceOpenRegistryKey(WdfDevice,
+ PLUGPLAY_REGKEY_DEVICE,
+ STANDARD_RIGHTS_ALL,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &hKey);
+
+ if (NT_SUCCESS (status)) {
+
+ RtlInitUnicodeString(&valueName,Name);
+
+ status = WdfRegistryAssignULong (hKey,
+ &valueName,
+ Value
+ );
+
+ if (NT_SUCCESS (status)) {
+ retValue = TRUE;
+ }
+
+ WdfRegistryClose(hKey);
+ }
+
+ return retValue;
+
+}
+
+BOOLEAN
+SerialGetFdoRegistryKeyValue(
+ IN PWDFDEVICE_INIT DeviceInit,
+ _In_ PCWSTR Name,
+ OUT PULONG Value
+ )
+/*++
+
+Routine Description:
+
+ Can be used to read any REG_DWORD registry value stored
+ under Device Parameter.
+
+Arguments:
+
+ FdoData - pointer to the device extension
+ Name - Name of the registry value
+ Value -
+
+
+Return Value:
+
+ TRUE if successful
+ FALSE if not present/error in reading registry
+
+--*/
+{
+ WDFKEY hKey = NULL;
+ NTSTATUS status;
+ BOOLEAN retValue = FALSE;
+ UNICODE_STRING valueName;
+
+ PAGED_CODE();
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP,
+ "-->SerialGetFdoRegistryKeyValue\n");
+
+ *Value = 0;
+
+ status = WdfFdoInitOpenRegistryKey(DeviceInit,
+ PLUGPLAY_REGKEY_DEVICE,
+ STANDARD_RIGHTS_ALL,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &hKey);
+
+ if (NT_SUCCESS (status)) {
+
+ RtlInitUnicodeString(&valueName,Name);
+
+ status = WdfRegistryQueryULong (hKey, &valueName, Value);
+
+ if (NT_SUCCESS (status)) {
+ retValue = TRUE;
+ }
+
+ WdfRegistryClose(hKey);
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP,
+ "<--SerialGetFdoRegistryKeyValue %ws %d \n",
+ Name, *Value);
+
+ return retValue;
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/serial.h b/tests/projects/windows/driver/kmdf/serial/serial.h new file mode 100644 index 000000000..fcbf9748f --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/serial.h @@ -0,0 +1,1757 @@ +/*++
+
+Copyright (c) 1990, 1991, 1992, 1993 - 1997 Microsoft Corporation
+
+Module Name :
+
+ serial.h
+
+Abstract:
+
+ Type definitions and data for the serial port driver
+
+--*/
+
+#define POOL_TAG 'XMOC'
+
+
+//
+// Some default driver values. We will check the registry for
+// them first.
+//
+#define SERIAL_UNINITIALIZED_DEFAULT 1234567
+#define SERIAL_FORCE_FIFO_DEFAULT 1
+#define SERIAL_RX_FIFO_DEFAULT 8
+#define SERIAL_TX_FIFO_DEFAULT 14
+#define SERIAL_PERMIT_SHARE_DEFAULT 0
+#define SERIAL_LOG_FIFO_DEFAULT 0
+
+
+//
+// This define gives the default Object directory
+// that we should use to insert the symbolic links
+// between the NT device name and namespace used by
+// that object directory.
+#define DEFAULT_DIRECTORY L"DosDevices"
+
+//
+// For the above directory, the serial port will
+// use the following name as the suffix of the serial
+// ports for that directory. It will also append
+// a number onto the end of the name. That number
+// will start at 1.
+#define DEFAULT_SERIAL_NAME L"COM"
+//
+//
+// This define gives the default NT name for
+// for serial ports detected by the firmware.
+// This name will be appended to Device prefix
+// with a number following it. The number is
+// incremented each time encounter a serial
+// port detected by the firmware. Note that
+// on a system with multiple busses, this means
+// that the first port on a bus is not necessarily
+// \Device\Serial0.
+//
+#define DEFAULT_NT_SUFFIX L"Serial"
+#define _DRIVER_NAME_ "Serial.sys"
+
+#define DEVICE_OBJECT_NAME_LENGTH 128
+#define SYMBOLIC_NAME_LENGTH 128
+#define SERIAL_DEVICE_MAP L"SERIALCOMM"
+
+//
+// GUID_DEVINTERFACE_COMPORT is not defined in the Win2K
+// headers, so we will need this definition to avoid compilation
+// errors.
+//
+#define GUID_DEVINTERFACE_COMPORT GUID_CLASS_COMPORT
+
+//
+// This value - which could be redefined at compile
+// time, define the stride between registers
+//
+#if !defined(SERIAL_REGISTER_STRIDE)
+#define SERIAL_REGISTER_STRIDE 1
+#endif
+
+//
+// Offsets from the base register address of the
+// various registers for the 8250 family of UARTS.
+//
+#define RECEIVE_BUFFER_REGISTER ((ULONG)((0x00)*SERIAL_REGISTER_STRIDE))
+#define TRANSMIT_HOLDING_REGISTER ((ULONG)((0x00)*SERIAL_REGISTER_STRIDE))
+#define INTERRUPT_ENABLE_REGISTER ((ULONG)((0x01)*SERIAL_REGISTER_STRIDE))
+#define INTERRUPT_IDENT_REGISTER ((ULONG)((0x02)*SERIAL_REGISTER_STRIDE))
+#define FIFO_CONTROL_REGISTER ((ULONG)((0x02)*SERIAL_REGISTER_STRIDE))
+#define LINE_CONTROL_REGISTER ((ULONG)((0x03)*SERIAL_REGISTER_STRIDE))
+#define MODEM_CONTROL_REGISTER ((ULONG)((0x04)*SERIAL_REGISTER_STRIDE))
+#define LINE_STATUS_REGISTER ((ULONG)((0x05)*SERIAL_REGISTER_STRIDE))
+#define MODEM_STATUS_REGISTER ((ULONG)((0x06)*SERIAL_REGISTER_STRIDE))
+#define DIVISOR_LATCH_LSB ((ULONG)((0x00)*SERIAL_REGISTER_STRIDE))
+#define DIVISOR_LATCH_MSB ((ULONG)((0x01)*SERIAL_REGISTER_STRIDE))
+#define SERIAL_REGISTER_SPAN ((ULONG)(7*SERIAL_REGISTER_STRIDE))
+
+//
+// If we have an interrupt status register this is its assumed
+// length.
+//
+#define SERIAL_STATUS_LENGTH ((ULONG)(1*SERIAL_REGISTER_STRIDE))
+
+//
+// Bitmask definitions for accessing the 8250 device registers.
+//
+
+//
+// These bits define the number of data bits trasmitted in
+// the Serial Data Unit (SDU - Start,data, parity, and stop bits)
+//
+#define SERIAL_DATA_LENGTH_5 0x00
+#define SERIAL_DATA_LENGTH_6 0x01
+#define SERIAL_DATA_LENGTH_7 0x02
+#define SERIAL_DATA_LENGTH_8 0x03
+
+
+//
+// These masks define the interrupts that can be enabled or disabled.
+//
+//
+// This interrupt is used to notify that there is new incomming
+// data available. The SERIAL_RDA interrupt is enabled by this bit.
+//
+#define SERIAL_IER_RDA 0x01
+
+//
+// This interrupt is used to notify that there is space available
+// in the transmitter for another character. The SERIAL_THR
+// interrupt is enabled by this bit.
+//
+#define SERIAL_IER_THR 0x02
+
+//
+// This interrupt is used to notify that some sort of error occured
+// with the incomming data. The SERIAL_RLS interrupt is enabled by
+// this bit.
+#define SERIAL_IER_RLS 0x04
+
+//
+// This interrupt is used to notify that some sort of change has
+// taken place in the modem control line. The SERIAL_MS interrupt is
+// enabled by this bit.
+//
+#define SERIAL_IER_MS 0x08
+
+
+//
+// These masks define the values of the interrupt identification
+// register. The low bit must be clear in the interrupt identification
+// register for any of these interrupts to be valid. The interrupts
+// are defined in priority order, with the highest value being most
+// important. See above for a description of what each interrupt
+// implies.
+//
+#define SERIAL_IIR_RLS 0x06
+#define SERIAL_IIR_RDA 0x04
+#define SERIAL_IIR_CTI 0x0c
+#define SERIAL_IIR_THR 0x02
+#define SERIAL_IIR_MS 0x00
+
+//
+// This bit mask get the value of the high two bits of the
+// interrupt id register. If this is a 16550 class chip
+// these bits will be a one if the fifo's are enbled, otherwise
+// they will always be zero.
+//
+#define SERIAL_IIR_FIFOS_ENABLED 0xc0
+
+//
+// If the low bit is logic one in the interrupt identification register
+// this implies that *NO* interrupts are pending on the device.
+//
+#define SERIAL_IIR_NO_INTERRUPT_PENDING 0x01
+
+
+//
+// Use these bits to detect removal of serial card for Stratus implementation
+//
+#define SERIAL_IIR_MUST_BE_ZERO 0x30
+
+
+//
+// These masks define access to the fifo control register.
+//
+
+//
+// Enabling this bit in the fifo control register will turn
+// on the fifos. If the fifos are enabled then the high two
+// bits of the interrupt id register will be set to one. Note
+// that this only occurs on a 16550 class chip. If the high
+// two bits in the interrupt id register are not one then
+// we know we have a lower model chip.
+//
+//
+#define SERIAL_FCR_ENABLE ((UCHAR)0x01)
+#define SERIAL_FCR_RCVR_RESET ((UCHAR)0x02)
+#define SERIAL_FCR_TXMT_RESET ((UCHAR)0x04)
+
+//
+// This set of values define the high water marks (when the
+// interrupts trip) for the receive fifo.
+//
+#define SERIAL_1_BYTE_HIGH_WATER ((UCHAR)0x00)
+#define SERIAL_4_BYTE_HIGH_WATER ((UCHAR)0x40)
+#define SERIAL_8_BYTE_HIGH_WATER ((UCHAR)0x80)
+#define SERIAL_14_BYTE_HIGH_WATER ((UCHAR)0xc0)
+
+//
+// These masks define access to the line control register.
+//
+
+//
+// This defines the bit used to control the definition of the "first"
+// two registers for the 8250. These registers are the input/output
+// register and the interrupt enable register. When the DLAB bit is
+// enabled these registers become the least significant and most
+// significant bytes of the divisor value.
+//
+#define SERIAL_LCR_DLAB 0x80
+
+//
+// This defines the bit used to control whether the device is sending
+// a break. When this bit is set the device is sending a space (logic 0).
+//
+// Most protocols will assume that this is a hangup.
+//
+#define SERIAL_LCR_BREAK 0x40
+
+//
+// These defines are used to set the line control register.
+//
+#define SERIAL_5_DATA ((UCHAR)0x00)
+#define SERIAL_6_DATA ((UCHAR)0x01)
+#define SERIAL_7_DATA ((UCHAR)0x02)
+#define SERIAL_8_DATA ((UCHAR)0x03)
+#define SERIAL_DATA_MASK ((UCHAR)0x03)
+
+#define SERIAL_1_STOP ((UCHAR)0x00)
+#define SERIAL_1_5_STOP ((UCHAR)0x04) // Only valid for 5 data bits
+#define SERIAL_2_STOP ((UCHAR)0x04) // Not valid for 5 data bits
+#define SERIAL_STOP_MASK ((UCHAR)0x04)
+
+#define SERIAL_NONE_PARITY ((UCHAR)0x00)
+#define SERIAL_ODD_PARITY ((UCHAR)0x08)
+#define SERIAL_EVEN_PARITY ((UCHAR)0x18)
+#define SERIAL_MARK_PARITY ((UCHAR)0x28)
+#define SERIAL_SPACE_PARITY ((UCHAR)0x38)
+#define SERIAL_PARITY_MASK ((UCHAR)0x38)
+
+//
+// These masks define access the modem control register.
+//
+
+//
+// This bit controls the data terminal ready (DTR) line. When
+// this bit is set the line goes to logic 0 (which is then inverted
+// by normal hardware). This is normally used to indicate that
+// the device is available to be used. Some odd hardware
+// protocols (like the kernel debugger) use this for handshaking
+// purposes.
+//
+#define SERIAL_MCR_DTR 0x01
+
+//
+// This bit controls the ready to send (RTS) line. When this bit
+// is set the line goes to logic 0 (which is then inverted by the normal
+// hardware). This is used for hardware handshaking. It indicates that
+// the hardware is ready to send data and it is waiting for the
+// receiving end to set clear to send (CTS).
+//
+#define SERIAL_MCR_RTS 0x02
+
+//
+// This bit is used for general purpose output.
+//
+#define SERIAL_MCR_OUT1 0x04
+
+//
+// This bit is used for general purpose output.
+//
+#define SERIAL_MCR_OUT2 0x08
+
+//
+// This bit controls the loopback testing mode of the device. Basically
+// the outputs are connected to the inputs (and vice versa).
+//
+#define SERIAL_MCR_LOOP 0x10
+
+//
+// This bit enables auto flow control on a TI TL16C550C/TL16C550CI
+//
+
+#define SERIAL_MCR_TL16C550CAFE 0x20
+
+
+//
+// These masks define access to the line status register. The line
+// status register contains information about the status of data
+// transfer. The first five bits deal with receive data and the
+// last two bits deal with transmission. An interrupt is generated
+// whenever bits 1 through 4 in this register are set.
+//
+
+//
+// This bit is the data ready indicator. It is set to indicate that
+// a complete character has been received. This bit is cleared whenever
+// the receive buffer register has been read.
+//
+#define SERIAL_LSR_DR 0x01
+
+//
+// This is the overrun indicator. It is set to indicate that the receive
+// buffer register was not read befor a new character was transferred
+// into the buffer. This bit is cleared when this register is read.
+//
+#define SERIAL_LSR_OE 0x02
+
+//
+// This is the parity error indicator. It is set whenever the hardware
+// detects that the incoming serial data unit does not have the correct
+// parity as defined by the parity select in the line control register.
+// This bit is cleared by reading this register.
+//
+#define SERIAL_LSR_PE 0x04
+
+//
+// This is the framing error indicator. It is set whenever the hardware
+// detects that the incoming serial data unit does not have a valid
+// stop bit. This bit is cleared by reading this register.
+//
+#define SERIAL_LSR_FE 0x08
+
+//
+// This is the break interrupt indicator. It is set whenever the data
+// line is held to logic 0 for more than the amount of time it takes
+// to send one serial data unit. This bit is cleared whenever the
+// this register is read.
+//
+#define SERIAL_LSR_BI 0x10
+
+//
+// This is the transmit holding register empty indicator. It is set
+// to indicate that the hardware is ready to accept another character
+// for transmission. This bit is cleared whenever a character is
+// written to the transmit holding register.
+//
+#define SERIAL_LSR_THRE 0x20
+
+//
+// This bit is the transmitter empty indicator. It is set whenever the
+// transmit holding buffer is empty and the transmit shift register
+// (a non-software accessable register that is used to actually put
+// the data out on the wire) is empty. Basically this means that all
+// data has been sent. It is cleared whenever the transmit holding or
+// the shift registers contain data.
+//
+#define SERIAL_LSR_TEMT 0x40
+
+//
+// This bit indicates that there is at least one error in the fifo.
+// The bit will not be turned off until there are no more errors
+// in the fifo.
+//
+#define SERIAL_LSR_FIFOERR 0x80
+
+
+//
+// These masks are used to access the modem status register.
+// Whenever one of the first four bits in the modem status
+// register changes state a modem status interrupt is generated.
+//
+
+//
+// This bit is the delta clear to send. It is used to indicate
+// that the clear to send bit (in this register) has *changed*
+// since this register was last read by the CPU.
+//
+#define SERIAL_MSR_DCTS 0x01
+
+//
+// This bit is the delta data set ready. It is used to indicate
+// that the data set ready bit (in this register) has *changed*
+// since this register was last read by the CPU.
+//
+#define SERIAL_MSR_DDSR 0x02
+
+//
+// This is the trailing edge ring indicator. It is used to indicate
+// that the ring indicator input has changed from a low to high state.
+//
+#define SERIAL_MSR_TERI 0x04
+
+//
+// This bit is the delta data carrier detect. It is used to indicate
+// that the data carrier bit (in this register) has *changed*
+// since this register was last read by the CPU.
+//
+#define SERIAL_MSR_DDCD 0x08
+
+//
+// This bit contains the (complemented) state of the clear to send
+// (CTS) line.
+//
+#define SERIAL_MSR_CTS 0x10
+
+//
+// This bit contains the (complemented) state of the data set ready
+// (DSR) line.
+//
+#define SERIAL_MSR_DSR 0x20
+
+//
+// This bit contains the (complemented) state of the ring indicator
+// (RI) line.
+//
+#define SERIAL_MSR_RI 0x40
+
+//
+// This bit contains the (complemented) state of the data carrier detect
+// (DCD) line.
+//
+#define SERIAL_MSR_DCD 0x80
+
+//
+// This should be more than enough space to hold then
+// numeric suffix of the device name.
+//
+#define DEVICE_NAME_DELTA 20
+
+
+//
+// Up to 16 Ports Per card. However for sixteen
+// port cards the interrupt status register must me
+// the indexing kind rather then the bitmask kind.
+//
+//
+#define SERIAL_MAX_PORTS_INDEXED (16)
+#define SERIAL_MAX_PORTS_NONINDEXED (8)
+
+typedef struct _CONFIG_DATA {
+ PHYSICAL_ADDRESS Controller;
+ PHYSICAL_ADDRESS TrController;
+ ULONG SpanOfController;
+ ULONG ClockRate;
+ ULONG AddressSpace;
+ ULONG DisablePort;
+ ULONG ForceFifoEnable;
+ ULONG RxFIFO;
+ ULONG TxFIFO;
+ ULONG PermitShare;
+ ULONG PermitSystemWideShare;
+ ULONG LogFifo;
+ KINTERRUPT_MODE InterruptMode;
+ ULONG TrVector;
+ ULONG TrIrql;
+ KAFFINITY Affinity;
+ ULONG TL16C550CAFC;
+ } CONFIG_DATA,*PCONFIG_DATA;
+
+
+//
+// This structure contains configuration data, much of which
+// is read from the registry.
+//
+typedef struct _SERIAL_FIRMWARE_DATA {
+ PDRIVER_OBJECT DriverObject;
+ ULONG ControllersFound;
+ ULONG ForceFifoEnableDefault;
+ ULONG DebugLevel;
+ ULONG ShouldBreakOnEntry;
+ ULONG RxFIFODefault;
+ ULONG TxFIFODefault;
+ ULONG PermitShareDefault;
+ ULONG PermitSystemWideShare;
+ ULONG LogFifoDefault;
+ ULONG UartRemovalDetect;
+ UNICODE_STRING Directory;
+ UNICODE_STRING NtNameSuffix;
+ UNICODE_STRING DirectorySymbolicName;
+ LIST_ENTRY ConfigList;
+} SERIAL_FIRMWARE_DATA,*PSERIAL_FIRMWARE_DATA;
+
+//
+// Default xon/xoff characters.
+//
+#define SERIAL_DEF_XON 0x11
+#define SERIAL_DEF_XOFF 0x13
+
+//
+// Reasons that recption may be held up.
+//
+#define SERIAL_RX_DTR ((ULONG)0x01)
+#define SERIAL_RX_XOFF ((ULONG)0x02)
+#define SERIAL_RX_RTS ((ULONG)0x04)
+#define SERIAL_RX_DSR ((ULONG)0x08)
+
+//
+// Reasons that transmission may be held up.
+//
+#define SERIAL_TX_CTS ((ULONG)0x01)
+#define SERIAL_TX_DSR ((ULONG)0x02)
+#define SERIAL_TX_DCD ((ULONG)0x04)
+#define SERIAL_TX_XOFF ((ULONG)0x08)
+#define SERIAL_TX_BREAK ((ULONG)0x10)
+
+//
+// These values are used by the routines that can be used
+// to complete a read (other than interval timeout) to indicate
+// to the interval timeout that it should complete.
+//
+#define SERIAL_COMPLETE_READ_CANCEL ((LONG)-1)
+#define SERIAL_COMPLETE_READ_TOTAL ((LONG)-2)
+#define SERIAL_COMPLETE_READ_COMPLETE ((LONG)-3)
+
+//
+// These are default values that shouldn't appear in the registry
+//
+#define SERIAL_BAD_VALUE ((ULONG)-1)
+
+
+typedef struct _SERIAL_DEVICE_STATE {
+ //
+ // TRUE if we need to set the state to open
+ // on a powerup
+ //
+
+ BOOLEAN Reopen;
+
+ //
+ // Hardware registers
+ //
+
+ UCHAR IER;
+ // FCR is known by other values
+ UCHAR LCR;
+ UCHAR MCR;
+ // LSR is never written
+ // MSR is never written
+ // SCR is either scratch or interrupt status
+
+
+} SERIAL_DEVICE_STATE, *PSERIAL_DEVICE_STATE;
+
+
+typedef
+UCHAR
+(*PREAD_PORT_UCHAR)(
+ IN UCHAR *Register
+ );
+
+typedef
+VOID
+(*PWRITE_PORT_UCHAR)(
+ IN UCHAR *Register,
+ IN UCHAR Value
+ );
+
+typedef struct _SERIAL_DEVICE_EXTENSION {
+ //
+ // WDF device handle
+ //
+ WDFDEVICE WdfDevice;
+ //
+ // Points to the device object that contains
+ // this device extension.
+ //
+ PDEVICE_OBJECT DeviceObject;
+ //
+ // We keep a pointer around to our device name for dumps
+ // and for creating "external" symbolic links to this
+ // device.
+ //
+ UNICODE_STRING DeviceName;
+ //
+ // Pointer to the driver object
+ //
+
+ PDRIVER_OBJECT DriverObject;
+
+ //
+ // Records whether we actually created the symbolic link name
+ // at driver load time. If we didn't create it, we won't try
+ // to destroy it when we unload.
+ //
+ BOOLEAN CreatedSymbolicLink;
+
+ //
+ // Records whether we actually created an entry in SERIALCOMM
+ // at driver load time. If we didn't create it, we won't try
+ // to destroy it when the device is removed.
+ //
+ BOOLEAN CreatedSerialCommEntry;
+
+ //
+ // Did we update system count for serial ports
+ //
+ BOOLEAN IsSystemConfigInfoUpdated;
+
+ //
+ // Should we expose external interfaces?
+ //
+ ULONG SkipNaming;
+
+ //
+ // Support the TI TL16C550C and TL16C550CI auto flow control
+ //
+
+ ULONG TL16C550CAFC;
+
+ //
+ // Detect removed hardware in intterrupt routine flag
+ //
+ ULONG UartRemovalDetect;
+
+ //
+ // We keep track of whether the somebody has the device currently
+ // opened with a simple boolean. We need to know this so that
+ // spurious interrupts from the device (especially during initialization)
+ // will be ignored. This value is only accessed in the ISR and
+ // is only set via synchronization routines. We may be able
+ // to get rid of this boolean when the code is more fleshed out.
+ //
+ BOOLEAN DeviceIsOpened;
+
+ //
+ // Current state during powerdown
+ //
+
+ SERIAL_DEVICE_STATE DeviceState;
+
+ //
+ // TRUE if we own power policy
+ //
+
+ BOOLEAN OwnsPowerPolicy;
+
+ //
+ // TRUE if we should retain power on close and not aggressively
+ // reduce power consumption
+ //
+
+ BOOLEAN RetainPowerOnClose;
+
+ //
+ // Should we enable wakeup
+ //
+
+ BOOLEAN IsWakeEnabled;
+
+ //
+ // This list head is used to contain the time ordered list
+ // of read requests. Access to this list is protected by
+ // the global cancel spinlock.
+ //
+ WDFQUEUE ReadQueue;
+
+ //
+ // This list head is used to contain the time ordered list
+ // of write requests. Access to this list is protected by
+ // the global cancel spinlock.
+ //
+ WDFQUEUE WriteQueue;
+
+ //
+ // This list head is used to contain the time ordered list
+ // of set and wait mask requests. Access to this list is protected by
+ // the global cancel spinlock.
+ //
+ WDFQUEUE MaskQueue;
+
+ //
+ // Holds the serialized list of purge requests.
+ //
+ WDFQUEUE PurgeQueue;
+
+ //
+ // This points to the request that is currently being processed
+ // for the read queue. This field is initialized by the open to
+ // NULL.
+ //
+ // This value is only set at dispatch level. It may be
+ // read at interrupt level.
+ //
+ WDFREQUEST CurrentReadRequest;
+
+ //
+ // This points to the request that is currently being processed
+ // for the write queue.
+ //
+ // This value is only set at dispatch level. It may be
+ // read at interrupt level.
+ //
+ WDFREQUEST CurrentWriteRequest;
+
+ //
+ // Points to the request that is currently being processed to
+ // affect the wait mask operations.
+ //
+ WDFREQUEST CurrentMaskRequest;
+
+ //
+ // Points to the request that is currently being processed to
+ // purge the read/write queues and buffers.
+ //
+ WDFREQUEST CurrentPurgeRequest;
+
+ //
+ // Points to the current request that is waiting on a comm event.
+ //
+ WDFREQUEST CurrentWaitRequest;
+
+ //
+ // Points to the request that is being used to send an immediate
+ // character.
+ //
+ WDFREQUEST CurrentImmediateRequest;
+
+ //
+ // Points to the request that is being used to count the number
+ // of characters received after an xoff (as currently defined
+ // by the IOCTL_SERIAL_XOFF_COUNTER ioctl) is sent.
+ //
+ WDFREQUEST CurrentXoffRequest;
+
+ //
+ // The base address for the set of device registers
+ // of the serial port.
+ //
+ PUCHAR Controller;
+ //
+ // This value holds the span (in units of bytes) of the register
+ // set controlling this port. This is constant over the life
+ // of the port.
+ //
+ ULONG SpanOfController;
+
+ //
+ // Address space
+ //
+
+ ULONG AddressSpace;
+
+ PREAD_PORT_UCHAR SerialReadUChar;
+ PWRITE_PORT_UCHAR SerialWriteUChar;
+
+ //
+ // Hold the clock rate input to the serial part.
+ //
+ ULONG ClockRate;
+
+ //
+ // The number of characters to push out if a fifo is present.
+ //
+ ULONG TxFifoAmount;
+
+ //
+ // Set to indicate that it is ok to share interrupts within the device.
+ //
+ ULONG PermitShare;
+
+
+ //
+ // Points to the interrupt object for used by this device.
+ //
+ WDFINTERRUPT WdfInterrupt;
+
+ //
+ // Translated vector
+ //
+ ULONG Vector;
+ //
+ // Translated Irql
+ //
+ KIRQL Irql;
+
+ KINTERRUPT_MODE InterruptMode;
+
+ KAFFINITY Affinity;
+
+ //
+ // This value is set by the read code to hold the time value
+ // used for read interval timing. We keep it in the extension
+ // so that the interval timer dpc routine determine if the
+ // interval time has passed for the IO.
+ //
+ LARGE_INTEGER IntervalTime;
+
+ //
+ // These two values hold the "constant" time that we should use
+ // to delay for the read interval time.
+ //
+ LARGE_INTEGER ShortIntervalAmount;
+ LARGE_INTEGER LongIntervalAmount;
+
+ //
+ // This holds the value that we use to determine if we should use
+ // the long interval delay or the short interval delay.
+ //
+ LARGE_INTEGER CutOverAmount;
+
+ //
+ // This holds the system time when we last time we had
+ // checked that we had actually read characters. Used
+ // for interval timing.
+ //
+ LARGE_INTEGER LastReadTime;
+
+
+ //
+ // This points the the delta time that we should use to
+ // delay for interval timing.
+ //
+ PLARGE_INTEGER IntervalTimeToUse;
+
+
+ //
+ // Set at intialization to indicate that on the current
+ // architecture we need to unmap the base register address
+ // when we unload the driver.
+ //
+ BOOLEAN UnMapRegisters;
+
+ //
+ // Holds the number of bytes remaining in the current write
+ // request.
+ //
+ // This location is only accessed while at interrupt level.
+ //
+ ULONG WriteLength;
+
+ //
+ // Holds a pointer to the current character to be sent in
+ // the current write.
+ //
+ // This location is only accessed while at interrupt level.
+ //
+ PUCHAR WriteCurrentChar;
+
+ //
+ // This is a buffer for the read processing.
+ //
+ // The buffer works as a ring. When the character is read from
+ // the device it will be place at the end of the ring.
+ //
+ // Characters are only placed in this buffer at interrupt level
+ // although character may be read at any level. The pointers
+ // that manage this buffer may not be updated except at interrupt
+ // level.
+ //
+ PUCHAR InterruptReadBuffer;
+
+ //
+ // This is a pointer to the first character of the buffer into
+ // which the interrupt service routine is copying characters.
+ //
+ PUCHAR ReadBufferBase;
+
+ //
+ // This is a count of the number of characters in the interrupt
+ // buffer. This value is set and read at interrupt level. Note
+ // that this value is only *incremented* at interrupt level so
+ // it is safe to read it at any level. When characters are
+ // copied out of the read buffer, this count is decremented by
+ // a routine that synchronizes with the ISR.
+ //
+ ULONG CharsInInterruptBuffer;
+
+ //
+ // Points to the first available position for a newly received
+ // character. This variable is only accessed at interrupt level and
+ // buffer initialization code.
+ //
+ PUCHAR CurrentCharSlot;
+
+ //
+ // This variable is used to contain the last available position
+ // in the read buffer. It is updated at open and at interrupt
+ // level when switching between the users buffer and the interrupt
+ // buffer.
+ //
+ PUCHAR LastCharSlot;
+
+ //
+ // This marks the first character that is available to satisfy
+ // a read request. Note that while this always points to valid
+ // memory, it may not point to a character that can be sent to
+ // the user. This can occur when the buffer is empty.
+ //
+ PUCHAR FirstReadableChar;
+
+ //
+ // Pointer to the lock variable returned for this extension when
+ // locking down the driver
+ //
+ PVOID LockPtr;
+
+
+ //
+ // This variable holds the size of whatever buffer we are currently
+ // using.
+ //
+ ULONG BufferSize;
+
+ //
+ // This variable holds .8 of BufferSize. We don't want to recalculate
+ // this real often - It's needed when so that an application can be
+ // "notified" that the buffer is getting full.
+ //
+ ULONG BufferSizePt8;
+
+ //
+ // This value holds the number of characters desired for a
+ // particular read. It is initially set by read length in the
+ // WDFREQUEST. It is decremented each time more characters are placed
+ // into the "users" buffer buy the code that reads characters
+ // out of the typeahead buffer into the users buffer. If the
+ // typeahead buffer is exhausted by the read, and the reads buffer
+ // is given to the isr to fill, this value is becomes meaningless.
+ //
+ ULONG NumberNeededForRead;
+
+ //
+ // This mask will hold the bitmask sent down via the set mask
+ // ioctl. It is used by the interrupt service routine to determine
+ // if the occurence of "events" (in the serial drivers understanding
+ // of the concept of an event) should be noted.
+ //
+ ULONG IsrWaitMask;
+
+ //
+ // This mask will always be a subset of the IsrWaitMask. While
+ // at device level, if an event occurs that is "marked" as interesting
+ // in the IsrWaitMask, the driver will turn on that bit in this
+ // history mask. The driver will then look to see if there is a
+ // request waiting for an event to occur. If there is one, it
+ // will copy the value of the history mask into the wait request, zero
+ // the history mask, and complete the wait request. If there is no
+ // waiting request, the driver will be satisfied with just recording
+ // that the event occured. If a wait request should be queued,
+ // the driver will look to see if the history mask is non-zero. If
+ // it is non-zero, the driver will copy the history mask into the
+ // request, zero the history mask, and then complete the request.
+ //
+ ULONG HistoryMask;
+
+ //
+ // This is a pointer to the where the history mask should be
+ // placed when completing a wait. It is only accessed at
+ // device level.
+ //
+ // We have a pointer here to assist us to synchronize completing a wait.
+ // If this is non-zero, then we have wait outstanding, and the isr still
+ // knows about it. We make this pointer null so that the isr won't
+ // attempt to complete the wait.
+ //
+ // We still keep a pointer around to the wait request, since the actual
+ // pointer to the wait request will be used for the "common" request completion
+ // path.
+ //
+ ULONG *IrpMaskLocation;
+
+ //
+ // This mask holds all of the reason that transmission
+ // is not proceeding. Normal transmission can not occur
+ // if this is non-zero.
+ //
+ // This is only written from interrupt level.
+ // This could be (but is not) read at any level.
+ //
+ ULONG TXHolding;
+
+ //
+ // This mask holds all of the reason that reception
+ // is not proceeding. Normal reception can not occur
+ // if this is non-zero.
+ //
+ // This is only written from interrupt level.
+ // This could be (but is not) read at any level.
+ //
+ ULONG RXHolding;
+
+ //
+ // This holds the reasons that the driver thinks it is in
+ // an error state.
+ //
+ // This is only written from interrupt level.
+ // This could be (but is not) read at any level.
+ //
+ ULONG ErrorWord;
+
+ //
+ // This keeps a total of the number of characters that
+ // are in all of the "write" irps that the driver knows
+ // about. It is only accessed with the cancel spinlock
+ // held.
+ //
+ ULONG TotalCharsQueued;
+
+ //
+ // This holds a count of the number of characters read
+ // the last time the interval timer dpc fired. It
+ // is a long (rather than a ulong) since the other read
+ // completion routines use negative values to indicate
+ // to the interval timer that it should complete the read
+ // if the interval timer DPC was lurking in some DPC queue when
+ // some other way to complete occurs.
+ //
+ LONG CountOnLastRead;
+
+ //
+ // This is a count of the number of characters read by the
+ // isr routine. It is *ONLY* written at isr level. We can
+ // read it at dispatch level.
+ //
+ ULONG ReadByIsr;
+
+ //
+ // This holds the current baud rate for the device.
+ //
+ ULONG CurrentBaud;
+
+ //
+ // This is the number of characters read since the XoffCounter
+ // was started. This variable is only accessed at device level.
+ // If it is greater than zero, it implies that there is an
+ // XoffCounter ioctl in the queue.
+ //
+ LONG CountSinceXoff;
+
+ //
+ // This ulong is incremented each time something trys to start
+ // the execution path that tries to lower the RTS line when
+ // doing transmit toggling. If it "bumps" into another path
+ // (indicated by a false return value from queueing a dpc
+ // and a TRUE return value tring to start a timer) it will
+ // decrement the count. These increments and decrements
+ // are all done at device level. Note that in the case
+ // of a bump while trying to start the timer, we have to
+ // go up to device level to do the decrement.
+ //
+ ULONG CountOfTryingToLowerRTS;
+
+ //
+ // This ULONG is used to keep track of the "named" (in ntddser.h)
+ // baud rates that this particular device supports.
+ //
+ ULONG SupportedBauds;
+
+ //
+ // Holds the timeout controls for the device. This value
+ // is set by the Ioctl processing.
+ //
+ // It should only be accessed under protection of the control
+ // lock since more than one request can be in the control dispatch
+ // routine at one time.
+ //
+ SERIAL_TIMEOUTS Timeouts;
+
+ //
+ // This holds the various characters that are used
+ // for replacement on errors and also for flow control.
+ //
+ // They are only set at interrupt level.
+ //
+ SERIAL_CHARS SpecialChars;
+
+ //
+ // This structure holds the handshake and control flow
+ // settings for the serial driver.
+ //
+ // It is only set at interrupt level. It can be
+ // be read at any level with the control lock held.
+ //
+ SERIAL_HANDFLOW HandFlow;
+
+
+ //
+ // Holds performance statistics that applications can query.
+ // Reset on each open. Only set at device level.
+ //
+ SERIALPERF_STATS PerfStats;
+
+ //
+ // This holds what we beleive to be the current value of
+ // the line control register.
+ //
+ // It should only be accessed under protection of the control
+ // lock since more than one request can be in the control dispatch
+ // routine at one time.
+ //
+ UCHAR LineControl;
+
+
+ //
+ // This is only accessed at interrupt level. It keeps track
+ // of whether the holding register is empty.
+ //
+ BOOLEAN HoldingEmpty;
+
+ //
+ // This variable is only accessed at interrupt level. It
+ // indicates that we want to transmit a character immediately.
+ // That is - in front of any characters that could be transmitting
+ // from a normal write.
+ //
+ BOOLEAN TransmitImmediate;
+
+ //
+ // This variable is only accessed at interrupt level. Whenever
+ // a wait is initiated this variable is set to false.
+ // Whenever any kind of character is written it is set to true.
+ // Whenever the write queue is found to be empty the code that
+ // is processing that completing request will synchonize with the interrupt.
+ // If this synchronization code finds that the variable is true and that
+ // there is a wait on the transmit queue being empty then it is
+ // certain that the queue was emptied and that it has happened since
+ // the wait was initiated.
+ //
+ BOOLEAN EmptiedTransmit;
+
+ //
+ // We keep the following values around so that we can connect
+ // to the interrupt and report resources after the configuration
+ // record is gone.
+ //
+
+ //
+ // We hold the character that should be transmitted immediately.
+ //
+ // Note that we can't use this to determine whether there is
+ // a character to send because the character to send could be
+ // zero.
+ //
+ UCHAR ImmediateChar;
+
+ //
+ // This holds the mask that will be used to mask off unwanted
+ // data bits of the received data (valid data bits can be 5,6,7,8)
+ // The mask will normally be 0xff. This is set while the control
+ // lock is held since it wouldn't have adverse effects on the
+ // isr if it is changed in the middle of reading characters.
+ // (What it would do to the app is another question - but then
+ // the app asked the driver to do it.)
+ //
+ UCHAR ValidDataMask;
+
+ //
+ // The application can turn on a mode,via the
+ // IOCTL_SERIAL_LSRMST_INSERT ioctl, that will cause the
+ // serial driver to insert the line status or the modem
+ // status into the RX stream. The parameter with the ioctl
+ // is a pointer to a UCHAR. If the value of the UCHAR is
+ // zero, then no insertion will ever take place. If the
+ // value of the UCHAR is non-zero (and not equal to the
+ // xon/xoff characters), then the serial driver will insert.
+ //
+ UCHAR EscapeChar;
+
+ //
+ // These two booleans are used to indicate to the isr transmit
+ // code that it should send the xon or xoff character. They are
+ // only accessed at open and at interrupt level.
+ //
+ BOOLEAN SendXonChar;
+ BOOLEAN SendXoffChar;
+
+ //
+ // This boolean will be true if a 16550 is present *and* enabled.
+ //
+ BOOLEAN FifoPresent;
+
+ //
+ // This is the water mark that the rxfifo should be
+ // set to when the fifo is turned on. This is not the actual
+ // value, but the encoded value that goes into the register.
+ //
+ UCHAR RxFifoTrigger;
+
+ //
+ // This points to a DPC used to complete write requests.
+ //
+ WDFDPC CompleteWriteDpc;
+
+ //
+ // This points to a DPC used to complete read requests.
+ //
+ WDFDPC CompleteReadDpc;
+
+
+ //
+ // This dpc is fired off if a comm error occurs. It will
+ // execute a dpc routine that will cancel all pending reads
+ // and writes.
+ //
+ WDFDPC CommErrorDpc;
+
+ //
+ // This dpc is fired off if an event occurs and there was
+ // a request waiting on that event. A dpc routine will execute
+ // that completes the request.
+ //
+ WDFDPC CommWaitDpc;
+
+ //
+ // This dpc is fired off when the transmit immediate char
+ // character is given to the hardware. It will simply complete
+ // the request.
+ //
+ WDFDPC CompleteImmediateDpc;
+
+ //
+ // This dpc is fired off if the xoff counter actually runs down
+ // to zero.
+ //
+ WDFDPC XoffCountCompleteDpc;
+
+ //
+ // This dpc is fired off only from device level to start off
+ // a timer that will queue a dpc to check if the RTS line
+ // should be lowered when we are doing transmit toggling.
+ //
+ WDFDPC StartTimerLowerRTSDpc;
+
+ //
+ // This timer used to handle total read request timing.
+ //
+ WDFTIMER ReadRequestTotalTimer;
+
+ //
+ // This timer used to handle interval read request timing.
+ //
+ WDFTIMER ReadRequestIntervalTimer;
+
+ //
+ // This timer used to handle total write request timing.
+ //
+ WDFTIMER WriteRequestTotalTimer;
+
+ //
+ // This is timer structure used to handle total time request timing.
+ //
+ WDFTIMER ImmediateTotalTimer;
+
+ //
+ // This timer is used to timeout the xoff counter io.
+ //
+ WDFTIMER XoffCountTimer;
+
+ //
+ // This timer is used to invoke a dpc one character time
+ // after the timer is set. That dpc will be used to check
+ // whether we should lower the RTS line if we are doing
+ // transmit toggling.
+ //
+ WDFTIMER LowerRTSTimer;
+
+ //
+ // WMI Information
+ //
+
+ //
+ // WMI Comm Data
+ //
+
+ SERIAL_WMI_COMM_DATA WmiCommData;
+
+ //
+ // WMI HW Data
+ //
+
+ SERIAL_WMI_HW_DATA WmiHwData;
+
+ //
+ // WMI Performance Data
+ //
+
+ SERIAL_WMI_PERF_DATA WmiPerfData;
+
+} SERIAL_DEVICE_EXTENSION,*PSERIAL_DEVICE_EXTENSION;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SERIAL_DEVICE_EXTENSION,
+ SerialGetDeviceExtension)
+
+//
+// This is the scratch area for every request.
+// We will copy some of the frequently used information of the request
+// into our context area so that way we don't have to call WdfRequestGetParams
+// function everytime.
+//
+typedef struct _REQUEST_CONTEXT {
+ ULONG_PTR Information;
+ NTSTATUS Status;
+ ULONG Length;
+ PVOID RefCount;
+ PVOID SystemBuffer;
+ UCHAR MajorFunction;
+ PFN_WDF_REQUEST_CANCEL CancelRoutine;
+ BOOLEAN Cancelled;
+ PVOID Type3InputBuffer;
+ PSERIAL_DEVICE_EXTENSION Extension;
+ ULONG IoctlCode;
+ BOOLEAN MarkCancelableOnResume;
+} REQUEST_CONTEXT, *PREQUEST_CONTEXT;
+
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(REQUEST_CONTEXT,
+ SerialGetRequestContext)
+
+
+//
+// This is the Interrupt context for the Serial device. This structure is used
+// for keeping track of whether the Interrupt is connected or not.
+//
+typedef struct _SERIAL_INTERRUPT_CONTEXT {
+
+ //
+ // This boolean value indicates whether Interrupt is connected.
+ //
+ BOOLEAN IsInterruptConnected;
+
+ //
+ // This lock is used to synchronize the file close logic and
+ // the Surprise Removal logic. When a surprise remove happens,
+ // the device interrupts are disabled. When this occurs, the
+ // file close logic should not attempt to use the interrupt
+ // object.
+ //
+ WDFWAITLOCK InterruptStateLock;
+
+} SERIAL_INTERRUPT_CONTEXT, *PSERIAL_INTERRUPT_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SERIAL_INTERRUPT_CONTEXT,
+ SerialGetInterruptContext)
+
+
+#define SERIAL_FLAGS_CLEAR 0x0L
+#define SERIAL_FLAGS_STARTED 0x1L
+#define SERIAL_FLAGS_STOPPED 0x2L
+#define SERIAL_FLAGS_BROKENHW 0x4L
+#define SERIAL_FLAGS_LEGACY_ENUMED 0x8L
+
+
+__inline
+UCHAR
+SerialReadPortUChar (
+ IN UCHAR * x
+ )
+{
+ return READ_PORT_UCHAR (x);
+}
+__inline
+VOID
+SerialWritePortUChar (
+ IN UCHAR * x,
+ IN UCHAR y
+ )
+{
+ WRITE_PORT_UCHAR (x,y);
+}
+
+__inline
+UCHAR
+SerialReadRegisterUChar (
+ IN UCHAR * x
+ )
+{
+ return READ_REGISTER_UCHAR (x);
+}
+
+__inline
+VOID
+SerialWriteRegisterUChar (
+ IN UCHAR * x,
+ IN UCHAR y
+ )
+{
+ WRITE_REGISTER_UCHAR (x,y);
+}
+
+
+
+//
+// Sets the divisor latch register. The divisor latch register
+// is used to control the baud rate of the 8250.
+//
+// As with all of these routines it is assumed that it is called
+// at a safe point to access the hardware registers. In addition
+// it also assumes that the data is correct.
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// DesiredDivisor - The value to which the divisor latch register should
+// be set.
+//
+#define WRITE_DIVISOR_LATCH(Extension, BaseAddress,DesiredDivisor) \
+do \
+{ \
+ PUCHAR Address = BaseAddress; \
+ SHORT Divisor = DesiredDivisor; \
+ UCHAR LineControl; \
+ LineControl = Extension->SerialReadUChar(Address+LINE_CONTROL_REGISTER); \
+ Extension->SerialWriteUChar( \
+ Address+LINE_CONTROL_REGISTER, \
+ (UCHAR)(LineControl | SERIAL_LCR_DLAB) \
+ ); \
+ Extension->SerialWriteUChar( \
+ Address+DIVISOR_LATCH_LSB, \
+ (UCHAR)(Divisor & 0xff) \
+ ); \
+ Extension->SerialWriteUChar( \
+ Address+DIVISOR_LATCH_MSB, \
+ (UCHAR)((Divisor & 0xff00) >> 8) \
+ ); \
+ Extension->SerialWriteUChar( \
+ Address+LINE_CONTROL_REGISTER, \
+ LineControl \
+ ); \
+} WHILE (0)
+
+//
+// Reads the divisor latch register. The divisor latch register
+// is used to control the baud rate of the 8250.
+//
+// As with all of these routines it is assumed that it is called
+// at a safe point to access the hardware registers. In addition
+// it also assumes that the data is correct.
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// DesiredDivisor - A pointer to the 2 byte word which will contain
+// the value of the divisor.
+//
+#define READ_DIVISOR_LATCH(Extension, BaseAddress,PDesiredDivisor) \
+do \
+{ \
+ PUCHAR Address = BaseAddress; \
+ PSHORT PDivisor = PDesiredDivisor; \
+ UCHAR LineControl; \
+ UCHAR Lsb; \
+ UCHAR Msb; \
+ LineControl = Extension->SerialReadUChar(Address+LINE_CONTROL_REGISTER); \
+ Extension->SerialWriteUChar( \
+ Address+LINE_CONTROL_REGISTER, \
+ (UCHAR)(LineControl | SERIAL_LCR_DLAB) \
+ ); \
+ Lsb = Extension->SerialReadUChar(Address+DIVISOR_LATCH_LSB); \
+ Msb = Extension->SerialReadUChar(Address+DIVISOR_LATCH_MSB); \
+ *PDivisor = Lsb; \
+ *PDivisor = *PDivisor | (((USHORT)Msb) << 8); \
+ Extension->SerialWriteUChar( \
+ Address+LINE_CONTROL_REGISTER, \
+ LineControl \
+ ); \
+} WHILE (0)
+
+//
+// This macro reads the interrupt enable register.
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+#define READ_INTERRUPT_ENABLE(Extension, BaseAddress) \
+ (Extension->SerialReadUChar((BaseAddress)+INTERRUPT_ENABLE_REGISTER))
+
+//
+// This macro writes the interrupt enable register.
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// Values - The values to write to the interrupt enable register.
+//
+#define WRITE_INTERRUPT_ENABLE(Extension, BaseAddress,Values) \
+do \
+{ \
+ Extension->SerialWriteUChar( \
+ BaseAddress+INTERRUPT_ENABLE_REGISTER, \
+ Values \
+ ); \
+} WHILE (0)
+
+//
+// This macro disables all interrupts on the hardware.
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define DISABLE_ALL_INTERRUPTS(Extension, BaseAddress) \
+do \
+{ \
+ WRITE_INTERRUPT_ENABLE(Extension, BaseAddress,0); \
+} WHILE (0)
+
+//
+// This macro enables all interrupts on the hardware.
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define ENABLE_ALL_INTERRUPTS(Extension, BaseAddress) \
+do \
+{ \
+ \
+ WRITE_INTERRUPT_ENABLE( \
+ (Extension), (BaseAddress), \
+ (UCHAR)(SERIAL_IER_RDA | SERIAL_IER_THR | \
+ SERIAL_IER_RLS | SERIAL_IER_MS) \
+ ); \
+ \
+} WHILE (0)
+
+//
+// This macro reads the interrupt identification register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// Note that this routine potententially quites a transmitter
+// empty interrupt. This is because one way that the transmitter
+// empty interrupt is cleared is to simply read the interrupt id
+// register.
+//
+//
+#define READ_INTERRUPT_ID_REG(Extension, BaseAddress) \
+ (Extension->SerialReadUChar((BaseAddress)+INTERRUPT_IDENT_REGISTER))
+
+//
+// This macro reads the modem control register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define READ_MODEM_CONTROL(Extension, BaseAddress) \
+ (Extension->SerialReadUChar((BaseAddress)+MODEM_CONTROL_REGISTER))
+
+//
+// This macro reads the modem status register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define READ_MODEM_STATUS(Extension, BaseAddress) \
+ (Extension->SerialReadUChar((BaseAddress)+MODEM_STATUS_REGISTER))
+
+//
+// This macro reads a value out of the receive buffer
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define READ_RECEIVE_BUFFER(Extension, BaseAddress) \
+ (Extension->SerialReadUChar((BaseAddress)+RECEIVE_BUFFER_REGISTER))
+
+//
+// This macro reads the line status register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define READ_LINE_STATUS(Extension, BaseAddress) \
+ (Extension->SerialReadUChar((BaseAddress)+LINE_STATUS_REGISTER))
+
+//
+// This macro writes the line control register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define WRITE_LINE_CONTROL(Extension, BaseAddress,NewLineControl) \
+do \
+{ \
+ Extension->SerialWriteUChar( \
+ (BaseAddress)+LINE_CONTROL_REGISTER, \
+ (NewLineControl) \
+ ); \
+} WHILE (0)
+
+//
+// This macro reads the line control register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+//
+#define READ_LINE_CONTROL(Extension, BaseAddress) \
+ (Extension->SerialReadUChar((BaseAddress)+LINE_CONTROL_REGISTER))
+
+
+//
+// This macro writes to the transmit register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// TransmitChar - The character to send down the wire.
+//
+//
+#define WRITE_TRANSMIT_HOLDING(Extension, BaseAddress,TransmitChar) \
+do \
+{ \
+ Extension->SerialWriteUChar( \
+ (BaseAddress)+TRANSMIT_HOLDING_REGISTER, \
+ (TransmitChar) \
+ ); \
+} WHILE (0)
+
+//
+// This macro writes to the transmit FIFO register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// TransmitChars - Pointer to the characters to send down the wire.
+//
+// TxN - number of charactes to send.
+//
+//
+#define WRITE_TRANSMIT_FIFO_HOLDING(Extension, BaseAddress,TransmitChars,TxN) \
+do \
+{ \
+ WRITE_PORT_BUFFER_UCHAR( \
+ (BaseAddress)+TRANSMIT_HOLDING_REGISTER, \
+ (TransmitChars), \
+ (TxN) \
+ ); \
+} WHILE (0)
+
+//
+// This macro writes to the control register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// ControlValue - The value to set the fifo control register too.
+//
+//
+#define WRITE_FIFO_CONTROL(Extension, BaseAddress,ControlValue) \
+do \
+{ \
+ Extension->SerialWriteUChar( \
+ (BaseAddress)+FIFO_CONTROL_REGISTER, \
+ (ControlValue) \
+ ); \
+} WHILE (0)
+
+//
+// This macro writes to the modem control register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located.
+//
+// ModemControl - The control bits to send to the modem control.
+//
+//
+#define WRITE_MODEM_CONTROL(Extension, BaseAddress,ModemControl) \
+do \
+{ \
+ Extension->SerialWriteUChar( \
+ (BaseAddress)+MODEM_CONTROL_REGISTER, \
+ (ModemControl) \
+ ); \
+} WHILE (0)
+
+#define WRITE_INTERRUPT_STATUS(Extension, BaseAddress,Status) \
+do \
+{ \
+ Extension->SerialWriteUChar(BaseAddress, Status); \
+} WHILE (0)
+
+
+//
+// This macro reads the interrupt status register
+//
+// Arguments:
+//
+// BaseAddress - A pointer to the address from which the hardware
+// device registers are located. BaseAddress is gotten
+// from PSERIAL_MULTIPORT_DISPATCH->InterruptStatus which
+// already has the complete address
+//
+// AddressSpace - Flag indicating where port is located, MMIO or IO
+// space
+//
+//
+#define READ_INTERRUPT_STATUS(Extension, BaseAddress) \
+ Extension->SerialReadUChar(BaseAddress))
+
+//
+// We use this to query into the registry as to whether we
+// should break at driver entry.
+//
+
+extern SERIAL_FIRMWARE_DATA driverDefaults;
+
+
+//
+// This is exported from the kernel. It is used to point
+// to the address that the kernel debugger is using.
+//
+
+extern PUCHAR *KdComPortInUse;
+
+
+typedef enum _SERIAL_MEM_COMPARES {
+ AddressesAreEqual,
+ AddressesOverlap,
+ AddressesAreDisjoint
+ } SERIAL_MEM_COMPARES,*PSERIAL_MEM_COMPARES;
+
+#define SERIAL_BAUD_INVALID 0xFFFFFFFF
+
+typedef struct _SUPPORTED_BAUD_RATES {
+ UINT32 BaudRate;
+ ULONG Mask;
+}SUPPORTED_BAUD_RATES;
+
diff --git a/tests/projects/windows/driver/kmdf/serial/serial.inx b/tests/projects/windows/driver/kmdf/serial/serial.inx Binary files differnew file mode 100644 index 000000000..3322653b0 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/serial.inx diff --git a/tests/projects/windows/driver/kmdf/serial/serial.rc b/tests/projects/windows/driver/kmdf/serial/serial.rc new file mode 100644 index 000000000..9fbb59de3 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/serial.rc @@ -0,0 +1,14 @@ +#include <windows.h>
+
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DRV
+#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
+#define VER_FILEDESCRIPTION_STR "Serial Device Driver"
+#define VER_INTERNALNAME_STR "serial.sys"
+#define VER_ORIGINALFILENAME_STR "serial.sys"
+
+#include "common.ver"
+
+#include "serlog.rc"
+
diff --git a/tests/projects/windows/driver/kmdf/serial/serialp.h b/tests/projects/windows/driver/kmdf/serial/serialp.h new file mode 100644 index 000000000..d363f3503 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/serialp.h @@ -0,0 +1,596 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name :
+
+ serialp.h
+
+Abstract:
+
+ Prototypes and macros that are used throughout the driver.
+
+--*/
+
+//-----------------------------------------------------------------------------
+// 4127 -- Conditional Expression is Constant warning
+//-----------------------------------------------------------------------------
+#define WHILE(constant) \
+__pragma(warning(suppress: 4127)) while(constant)
+
+typedef
+VOID
+(*PSERIAL_START_ROUTINE) (
+ IN PSERIAL_DEVICE_EXTENSION
+ );
+
+typedef
+VOID
+(*PSERIAL_GET_NEXT_ROUTINE) (
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ OUT WDFREQUEST *NewRequest,
+ IN BOOLEAN CompleteCurrent,
+ PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+DRIVER_INITIALIZE DriverEntry;
+
+EVT_WDF_DRIVER_DEVICE_ADD SerialEvtDeviceAdd;
+EVT_WDF_OBJECT_CONTEXT_CLEANUP SerialEvtDriverContextCleanup;
+EVT_WDF_DEVICE_CONTEXT_CLEANUP SerialEvtDeviceContextCleanup;
+
+EVT_WDF_DEVICE_D0_ENTRY SerialEvtDeviceD0Entry;
+EVT_WDF_DEVICE_D0_EXIT SerialEvtDeviceD0Exit;
+EVT_WDF_DEVICE_D0_ENTRY_POST_INTERRUPTS_ENABLED SerialEvtDeviceD0EntryPostInterruptsEnabled;
+EVT_WDF_DEVICE_D0_EXIT_PRE_INTERRUPTS_DISABLED SerialEvtDeviceD0ExitPreInterruptsDisabled;
+EVT_WDF_DEVICE_PREPARE_HARDWARE SerialEvtPrepareHardware;
+EVT_WDF_DEVICE_RELEASE_HARDWARE SerialEvtReleaseHardware;
+
+EVT_WDF_DEVICE_FILE_CREATE SerialEvtDeviceFileCreate;
+EVT_WDF_FILE_CLOSE SerialEvtFileClose;
+
+EVT_WDF_IO_QUEUE_IO_READ SerialEvtIoRead;
+EVT_WDF_IO_QUEUE_IO_WRITE SerialEvtIoWrite;
+EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL SerialEvtIoDeviceControl;
+EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL SerialEvtIoInternalDeviceControl;
+EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE SerialEvtCanceledOnQueue;
+EVT_WDF_IO_QUEUE_IO_STOP SerialEvtIoStop;
+EVT_WDF_IO_QUEUE_IO_RESUME SerialEvtIoResume;
+
+EVT_WDF_INTERRUPT_ENABLE SerialEvtInterruptEnable;
+EVT_WDF_INTERRUPT_DISABLE SerialEvtInterruptDisable;
+
+EVT_WDF_DPC SerialCompleteRead;
+EVT_WDF_DPC SerialCompleteWrite;
+EVT_WDF_DPC SerialCommError;
+EVT_WDF_DPC SerialCompleteImmediate;
+EVT_WDF_DPC SerialCompleteXoff;
+EVT_WDF_DPC SerialCompleteWait;
+EVT_WDF_DPC SerialStartTimerLowerRTS;
+
+EVT_WDF_TIMER SerialReadTimeout;
+EVT_WDF_TIMER SerialIntervalReadTimeout;
+EVT_WDF_TIMER SerialWriteTimeout;
+EVT_WDF_TIMER SerialTimeoutImmediate;
+EVT_WDF_TIMER SerialTimeoutXoff;
+EVT_WDF_TIMER SerialInvokePerhapsLowerRTS;
+
+VOID
+SerialStartRead(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+VOID
+SerialStartWrite(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+VOID
+SerialStartMask(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+VOID
+SerialStartImmediate(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+VOID
+SerialStartPurge(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+VOID
+SerialGetNextWrite(
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ IN WDFREQUEST *NewRequest,
+ IN BOOLEAN CompleteCurrent,
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialWdmDeviceFileCreate;
+EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialWdmFileClose;
+EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialFlush;
+
+EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialQueryInformationFile;
+EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialSetInformationFile;
+
+NTSTATUS
+SerialDeviceFileCreateWorker (
+ IN WDFDEVICE Device
+ );
+
+
+VOID
+SerialFileCloseWorker(
+ IN WDFDEVICE Device
+ );
+
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialProcessEmptyTransmit;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetDTR;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialClrDTR;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetRTS;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialClrRTS;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetBaud;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetLineControl;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetHandFlow;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialTurnOnBreak;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialTurnOffBreak;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPretendXoff;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPretendXon;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialReset;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPerhapsLowerRTS;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialMarkOpen;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialMarkClose;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetStats;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialClearStats;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetChars;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetMCRContents;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetMCRContents;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetFCRContents;
+
+BOOLEAN
+SerialSetupNewHandFlow(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN PSERIAL_HANDFLOW NewHandFlow
+ );
+
+
+VOID
+SerialHandleReducedIntBuffer(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+VOID
+SerialProdXonXoff(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN BOOLEAN SendXon
+ );
+
+EVT_WDF_REQUEST_CANCEL SerialCancelWait;
+
+
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPurgeInterruptBuff;
+
+VOID
+SerialPurgeRequests(
+ IN WDFQUEUE QueueToClean,
+ IN WDFREQUEST *CurrentOpRequest
+ );
+
+VOID
+SerialFlushRequests(
+ IN WDFQUEUE QueueToClean,
+ IN WDFREQUEST *CurrentOpRequest
+ );
+
+VOID
+SerialGetNextRequest(
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ OUT WDFREQUEST *NextIrp,
+ IN BOOLEAN CompleteCurrent,
+ IN PSERIAL_DEVICE_EXTENSION extension
+ );
+
+
+VOID
+SerialTryToCompleteCurrent(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN PFN_WDF_INTERRUPT_SYNCHRONIZE SynchRoutine OPTIONAL,
+ IN NTSTATUS StatusToUse,
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ IN WDFTIMER IntervalTimer,
+ IN WDFTIMER TotalTimer,
+ IN PSERIAL_START_ROUTINE Starter,
+ IN PSERIAL_GET_NEXT_ROUTINE GetNextIrp,
+ IN LONG RefType
+ );
+
+VOID
+SerialStartOrQueue(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN WDFREQUEST Request,
+ IN WDFQUEUE QueueToExamine,
+ IN WDFREQUEST *CurrentOpRequest,
+ IN PSERIAL_START_ROUTINE Starter
+ );
+
+NTSTATUS
+SerialCompleteIfError(
+ PSERIAL_DEVICE_EXTENSION extension,
+ WDFREQUEST Request
+ );
+
+ULONG
+SerialHandleModemUpdate(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN BOOLEAN DoingTX
+ );
+
+
+EVT_WDF_INTERRUPT_ISR SerialISR;
+
+NTSTATUS
+SerialGetDivisorFromBaud(
+ IN ULONG ClockRate,
+ IN LONG DesiredBaud,
+ OUT PSHORT AppropriateDivisor
+ );
+
+VOID
+SerialCleanupDevice(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+UCHAR
+SerialProcessLSR(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+LARGE_INTEGER
+SerialGetCharTime(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+
+VOID
+SerialPutChar(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN UCHAR CharToPut
+ );
+
+NTSTATUS
+SerialGetConfigDefaults(
+ IN PSERIAL_FIRMWARE_DATA DriverDefaultsPtr,
+ IN WDFDRIVER Driver
+ );
+
+VOID
+SerialGetProperties(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN PSERIAL_COMMPROP Properties
+ );
+
+VOID
+SerialLogError(
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_opt_ PDEVICE_OBJECT DeviceObject,
+ _In_ PHYSICAL_ADDRESS P1,
+ _In_ PHYSICAL_ADDRESS P2,
+ _In_ ULONG SequenceNumber,
+ _In_ UCHAR MajorFunctionCode,
+ _In_ UCHAR RetryCount,
+ _In_ ULONG UniqueErrorValue,
+ _In_ NTSTATUS FinalStatus,
+ _In_ NTSTATUS SpecificIOStatus,
+ _In_ ULONG LengthOfInsert1,
+ _In_reads_bytes_opt_(LengthOfInsert1) PWCHAR Insert1,
+ _In_ ULONG LengthOfInsert2,
+ _In_reads_bytes_opt_(LengthOfInsert2) PWCHAR Insert2
+ );
+
+NTSTATUS
+SerialMapHWResources(
+ IN WDFDEVICE Device,
+ IN WDFCMRESLIST PResList,
+ IN WDFCMRESLIST PTrResList,
+ OUT PCONFIG_DATA PConfig
+ );
+
+VOID
+SerialUnmapHWResources(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+BOOLEAN
+SerialGetRegistryKeyValue (
+ IN WDFDEVICE WdfDevice,
+ _In_ PCWSTR Name,
+ OUT PULONG Value
+ );
+
+
+BOOLEAN
+SerialPutRegistryKeyValue (
+ IN WDFDEVICE WdfDevice,
+ _In_ PCWSTR Name,
+ IN ULONG Value
+ );
+
+NTSTATUS
+SerialInitController(
+ IN PSERIAL_DEVICE_EXTENSION pDevExt,
+ IN PCONFIG_DATA PConfigData
+ );
+
+BOOLEAN
+SerialCIsrSw(
+ IN WDFINTERRUPT Interrupt,
+ IN ULONG MessageID
+ );
+
+NTSTATUS
+SerialDoExternalNaming(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+PVOID
+SerialGetMappedAddress(
+ PHYSICAL_ADDRESS IoAddress,
+ ULONG NumberOfBytes,
+ ULONG AddressSpace,
+ PBOOLEAN MappedAddress
+ );
+
+BOOLEAN
+SerialDoesPortExist(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ PUNICODE_STRING InsertString,
+ IN ULONG ForceFifo,
+ IN ULONG LogFifo
+ );
+
+SERIAL_MEM_COMPARES
+SerialMemCompare(
+ IN PHYSICAL_ADDRESS A,
+ IN ULONG SpanOfA,
+ IN PHYSICAL_ADDRESS B,
+ IN ULONG SpanOfB
+ );
+
+VOID
+SerialUndoExternalNaming(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ );
+
+VOID
+SerialReleaseResources(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+VOID
+SerialPurgePendingRequests(
+ PSERIAL_DEVICE_EXTENSION pDevExt
+ );
+
+VOID
+SerialDisableUART(
+ IN PVOID Context
+ );
+
+VOID
+SerialDrainUART(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt,
+ IN PLARGE_INTEGER PDrainTime
+ );
+
+VOID
+SerialSaveDeviceState(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+NTSTATUS
+SerialSetPowerPolicy(
+ IN PSERIAL_DEVICE_EXTENSION DeviceExtension
+ );
+
+UINT32
+SerialReportMaxBaudRate(
+ ULONG Bauds
+ );
+
+BOOLEAN
+SerialInsertQueueDpc(
+ IN WDFDPC Dpc
+ );
+
+BOOLEAN
+SerialSetTimer(
+ IN WDFTIMER Timer,
+ IN LARGE_INTEGER DueTime
+ );
+
+BOOLEAN
+SerialCancelTimer(
+ IN WDFTIMER Timer,
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+VOID
+SerialUnlockPages(
+ IN WDFDPC PDpc,
+ IN PVOID PDeferredContext,
+ IN PVOID PSysContext1,
+ IN PVOID PSysContext2)
+ ;
+
+VOID
+SerialMarkHardwareBroken(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+VOID
+SerialDisableInterfacesResources(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt,
+ IN BOOLEAN DisableUART
+ );
+
+VOID
+SerialSetDeviceFlags(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt,
+ OUT PULONG PFlags,
+ IN ULONG Value,
+ IN BOOLEAN Set
+ );
+
+
+VOID
+SetDeviceIsOpened(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt,
+ IN BOOLEAN DeviceIsOpened,
+ IN BOOLEAN Reopen
+ );
+
+BOOLEAN
+IsQueueEmpty(
+ IN WDFQUEUE Queue
+ );
+
+NTSTATUS
+SerialCreateTimersAndDpcs(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+VOID
+SerialDrainTimersAndDpcs(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ );
+
+VOID
+SerialSetCancelRoutine(
+ IN WDFREQUEST Request,
+ IN PFN_WDF_REQUEST_CANCEL CancelRoutine
+ );
+
+NTSTATUS
+SerialClearCancelRoutine(
+ IN WDFREQUEST Request,
+ IN BOOLEAN ClearReference
+ );
+
+NTSTATUS
+SerialWmiRegistration(
+ WDFDEVICE Device
+ );
+
+NTSTATUS
+SerialReadSymName(
+ IN WDFDEVICE Device,
+ _Out_writes_bytes_(*SizeOfRegName) PWSTR RegName,
+ _Inout_ PUSHORT SizeOfRegName
+ );
+
+VOID
+SerialCompleteRequest(
+ IN WDFREQUEST Request,
+ IN NTSTATUS Status,
+ IN ULONG_PTR Info
+ );
+
+BOOLEAN
+SerialGetFdoRegistryKeyValue(
+ IN PWDFDEVICE_INIT DeviceInit,
+ _In_ PCWSTR Name,
+ OUT PULONG Value
+ );
+
+VOID
+SerialSetInterruptPolicy(
+ _In_ WDFINTERRUPT WdfInterrupt
+ );
+
+typedef struct _SERIAL_UPDATE_CHAR {
+ PSERIAL_DEVICE_EXTENSION Extension;
+ ULONG CharsCopied;
+ BOOLEAN Completed;
+ } SERIAL_UPDATE_CHAR,*PSERIAL_UPDATE_CHAR;
+
+//
+// The following simple structure is used to send a pointer
+// the device extension and an ioctl specific pointer
+// to data.
+//
+typedef struct _SERIAL_IOCTL_SYNC {
+ PSERIAL_DEVICE_EXTENSION Extension;
+ PVOID Data;
+ } SERIAL_IOCTL_SYNC,*PSERIAL_IOCTL_SYNC;
+
+
+//
+// The following three macros are used to initialize, set
+// and clear references in IRPs that are used by
+// this driver. The reference is stored in the fourth
+// argument of the request, which is never used by any operation
+// accepted by this driver.
+//
+
+#define SERIAL_REF_ISR (0x00000001)
+#define SERIAL_REF_CANCEL (0x00000002)
+#define SERIAL_REF_TOTAL_TIMER (0x00000004)
+#define SERIAL_REF_INT_TIMER (0x00000008)
+#define SERIAL_REF_XOFF_REF (0x00000010)
+
+
+#define SERIAL_INIT_REFERENCE(ReqContext) { \
+ (ReqContext)->RefCount = NULL; \
+ }
+
+#define SERIAL_SET_REFERENCE(ReqContext, RefType) \
+ do { \
+ LONG _refType = (RefType); \
+ PULONG_PTR _arg4 = (PVOID)&(ReqContext)->RefCount; \
+ ASSERT(!(*_arg4 & _refType)); \
+ *_arg4 |= _refType; \
+ } WHILE (0)
+
+#define SERIAL_CLEAR_REFERENCE(ReqContext, RefType) \
+ do { \
+ LONG _refType = (RefType); \
+ PULONG_PTR _arg4 = (PVOID)&(ReqContext)->RefCount; \
+ ASSERT(*_arg4 & _refType); \
+ *_arg4 &= ~_refType; \
+ } WHILE (0)
+
+#define SERIAL_REFERENCE_COUNT(ReqContext) \
+ ((ULONG_PTR)(((ReqContext)->RefCount)))
+
+#define SERIAL_TEST_REFERENCE(ReqContext, RefType) ((ULONG_PTR)ReqContext ->RefCount & RefType)
+
+//
+// Prototypes and defines to handle processor groups.
+//
+typedef
+USHORT
+(*PFN_KE_GET_ACTIVE_GROUP_COUNT)(
+ VOID
+ );
+
+typedef
+KAFFINITY
+(*PFN_KE_QUERY_GROUP_AFFINITY) (
+ _In_ USHORT GroupNumber
+ );
+
+//
+// Force the serial interrupt to run on the last interrupt group.
+//
+//#define SERIAL_SELECT_INTERRUPT_GROUP 1
+#define SERIAL_LAST_INTERRUPT_GROUP 0xFFFF
+#define SERIAL_PREFERRED_INTERRUPT_GROUP SERIAL_LAST_INTERRUPT_GROUP
+
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/serlog.mc b/tests/projects/windows/driver/kmdf/serial/serlog.mc new file mode 100644 index 000000000..ee6935b67 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/serlog.mc @@ -0,0 +1,290 @@ +;/*++ BUILD Version: 0001 // Increment this if a change has global effects +; +;Copyright (c) 1992, 1993 Microsoft Corporation +; +;Module Name: +; +; ntiologc.h +; +;Abstract: +; +; Constant definitions for the I/O error code log values. +; +;--*/ +; +;#ifndef _SERLOG_ +;#define _SERLOG_ +; +;// +;// Status values are 32 bit values layed out as follows: +;// +;// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +;// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +;// +---+-+-------------------------+-------------------------------+ +;// |Sev|C| Facility | Code | +;// +---+-+-------------------------+-------------------------------+ +;// +;// where +;// +;// Sev - is the severity code +;// +;// 00 - Success +;// 01 - Informational +;// 10 - Warning +;// 11 - Error +;// +;// C - is the Customer code flag +;// +;// Facility - is the facility code +;// +;// Code - is the facility's status code +;// +; +MessageIdTypedef=NTSTATUS + +SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS + Informational=0x1:STATUS_SEVERITY_INFORMATIONAL + Warning=0x2:STATUS_SEVERITY_WARNING + Error=0x3:STATUS_SEVERITY_ERROR + ) + +FacilityNames=(System=0x0 + RpcRuntime=0x2:FACILITY_RPC_RUNTIME + RpcStubs=0x3:FACILITY_RPC_STUBS + Io=0x4:FACILITY_IO_ERROR_CODE + Serial=0x6:FACILITY_SERIAL_ERROR_CODE + ) + + +MessageId=0x0001 Facility=Serial Severity=Informational SymbolicName=SERIAL_KERNEL_DEBUGGER_ACTIVE +Language=English +The kernel debugger is already using %2. +. + +MessageId=0x0002 Facility=Serial Severity=Informational SymbolicName=SERIAL_FIFO_PRESENT +Language=English +While validating that %2 was really a serial port, a fifo was detected. The fifo will be used. +. + +MessageId=0x0003 Facility=Serial Severity=Informational SymbolicName=SERIAL_USER_OVERRIDE +Language=English +User configuration data for parameter %2 overriding firmware configuration data. +. + +MessageId=0x0004 Facility=Serial Severity=Warning SymbolicName=SERIAL_NO_SYMLINK_CREATED +Language=English +Unable to create the symbolic link for %2. +. + +MessageId=0x0005 Facility=Serial Severity=Warning SymbolicName=SERIAL_NO_DEVICE_MAP_CREATED +Language=English +Unable to create the device map entry for %2. +. + +MessageId=0x0006 Facility=Serial Severity=Warning SymbolicName=SERIAL_NO_DEVICE_MAP_DELETED +Language=English +Unable to delete the device map entry for %2. +. + +MessageId=0x0007 Facility=Serial Severity=Error SymbolicName=SERIAL_UNREPORTED_IRQL_CONFLICT +Language=English +Another driver on the system, which did not report its resources, has already claimed the interrupt used by %2. +. + +MessageId=0x0008 Facility=Serial Severity=Error SymbolicName=SERIAL_INSUFFICIENT_RESOURCES +Language=English +Not enough resources were available for the driver. +. + +MessageId=0x0009 Facility=Serial Severity=Error SymbolicName=SERIAL_UNSUPPORTED_CLOCK_RATE +Language=English +The baud clock rate configuration is not supported on device %2. +. + +MessageId=0x000A Facility=Serial Severity=Error SymbolicName=SERIAL_REGISTERS_NOT_MAPPED +Language=English +The hardware locations for %2 could not be translated to something the memory management system could understand. +. + +MessageId=0x000B Facility=Serial Severity=Error SymbolicName=SERIAL_RESOURCE_CONFLICT +Language=English +The hardware resources for %2 are already in use by another device. +. + +MessageId=0x000C Facility=Serial Severity=Error SymbolicName=SERIAL_NO_BUFFER_ALLOCATED +Language=English +No memory could be allocated in which to place new data for %2. +. + +MessageId=0x000D Facility=Serial Severity=Error SymbolicName=SERIAL_IER_INVALID +Language=English +While validating that %2 was really a serial port, the interrupt enable register contained enabled bits in a must be zero bitfield. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x000E Facility=Serial Severity=Error SymbolicName=SERIAL_MCR_INVALID +Language=English +While validating that %2 was really a serial port, the modem control register contained enabled bits in a must be zero bitfield. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x000F Facility=Serial Severity=Error SymbolicName=SERIAL_IIR_INVALID +Language=English +While validating that %2 was really a serial port, the interrupt id register contained enabled bits in a must be zero bitfield. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x0010 Facility=Serial Severity=Error SymbolicName=SERIAL_DL_INVALID +Language=English +While validating that %2 was really a serial port, the baud rate register could not be set consistantly. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x0011 Facility=Serial Severity=Error SymbolicName=SERIAL_NOT_ENOUGH_CONFIG_INFO +Language=English +Some firmware configuration information was incomplete. +. + +MessageId=0x0012 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_PARAMETERS_INFO +Language=English +No Parameters subkey was found for user defined data. This is odd, and it also means no user configuration can be found. +. + +MessageId=0x0013 Facility=Serial Severity=Error SymbolicName=SERIAL_UNABLE_TO_ACCESS_CONFIG +Language=English +Specific user configuration data is unretrievable. +. + +MessageId=0x0014 Facility=Serial Severity=Error SymbolicName=SERIAL_INVALID_PORT_INDEX +Language=English +On parameter %2 which indicates a multiport card, must have a port index specified greater than 0. +. + +MessageId=0x0015 Facility=Serial Severity=Error SymbolicName=SERIAL_PORT_INDEX_TOO_HIGH +Language=English +On parameter %2 which indicates a multiport card, the port index for the multiport card is too large. +. + +MessageId=0x0016 Facility=Serial Severity=Error SymbolicName=SERIAL_UNKNOWN_BUS +Language=English +The bus type for %2 is not recognizable. +. + +MessageId=0x0017 Facility=Serial Severity=Error SymbolicName=SERIAL_BUS_NOT_PRESENT +Language=English +The bus type for %2 is not available on this computer. +. + +MessageId=0x0018 Facility=Serial Severity=Error SymbolicName=SERIAL_BUS_INTERRUPT_CONFLICT +Language=English +The bus specified for %2 does not support the specified method of interrupt. +. + +MessageId=0x0019 Facility=Serial Severity=Error SymbolicName=SERIAL_INVALID_USER_CONFIG +Language=English +User configuration for parameter %2 must have %3. +. + +MessageId=0x001A Facility=Serial Severity=Error SymbolicName=SERIAL_DEVICE_TOO_HIGH +Language=English +The user specified port for %2 is way too high in physical memory. +. + +MessageId=0x001B Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_TOO_HIGH +Language=English +The status port for %2 is way too high in physical memory. +. + +MessageId=0x001C Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_CONTROL_CONFLICT +Language=English +The status port for %2 overlaps the control registers for the device. +. + +MessageId=0x001D Facility=Serial Severity=Error SymbolicName=SERIAL_CONTROL_OVERLAP +Language=English +The control registers for %2 overlaps with the %3 control registers. +. + +MessageId=0x001E Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_OVERLAP +Language=English +The status register for %2 overlaps the %3 control registers. +. + +MessageId=0x001F Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_STATUS_OVERLAP +Language=English +The status register for %2 overlaps with the %3 status register. +. + +MessageId=0x0020 Facility=Serial Severity=Error SymbolicName=SERIAL_CONTROL_STATUS_OVERLAP +Language=English +The control registers for %2 overlaps the %3 status register. +. + +MessageId=0x0021 Facility=Serial Severity=Error SymbolicName=SERIAL_MULTI_INTERRUPT_CONFLICT +Language=English +Two ports, %2 and %3, on a single multiport card can't have two different interrupts. +. + +MessageId=0x0022 Facility=Serial Severity=Informational SymbolicName=SERIAL_DISABLED_PORT +Language=English +Disabling %2 as requested by the configuration data. +. + +MessageId=0x0023 Facility=Serial Severity=Error SymbolicName=SERIAL_GARBLED_PARAMETER +Language=English +Parameter %2 data is unretrievable from the registry. +. + +MessageId=0x0024 Facility=Serial Severity=Error SymbolicName=SERIAL_DLAB_INVALID +Language=English +While validating that %2 was really a serial port, the contents of the divisor latch register was identical to the interrupt enable and the receive registers. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x0025 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_TRANSLATE_PORT +Language=English +Could not translate the user reported I/O port for %2. +. + +MessageId=0x0026 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_GET_INTERRUPT +Language=English +Could not get the user reported interrupt for %2 from the HAL. +. + +MessageId=0x0027 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_TRANSLATE_ISR +Language=English +Could not translate the user reported Interrupt Status Register for %2. +. + +MessageId=0x0028 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_DEVICE_REPORT +Language=English +Could not report the discovered legacy device %2 to the IO subsystem. +. + +MessageId=0x0029 Facility=Serial Severity=Error SymbolicName=SERIAL_REGISTRY_WRITE_FAILED +Language=English +Error writing to the registry. +. + +MessageId=0x002A Facility=Serial Severity=Warning SymbolicName=SERIAL_MOUSE_CONFLICT_IRQ +Language=English +There is a serial mouse using the same interrupt as %2. Therefore, %2 will not be started. +. + +MessageId=0x002B Facility=Serial Severity=Warning SymbolicName=SERIAL_MOUSE_ON_PORT +Language=English +There was a serial mouse found on %2. Therefore, %2 will be assigned to the mouse. +. + +MessageId=0x002C Facility=Serial Severity=Error SymbolicName=SERIAL_NO_DEVICE_REPORT_RES +Language=English +Could not report device %2 to IO subsystem due to a resource conflict. +. + +MessageId=0x002D Facility=Serial Severity=Error SymbolicName=SERIAL_HARDWARE_FAILURE +Language=English +The serial driver detected a hardware failure on device %2 and will disable this device. +. + +;#endif /* _NTIOLOGC_ */ + diff --git a/tests/projects/windows/driver/kmdf/serial/trace.h b/tests/projects/windows/driver/kmdf/serial/trace.h new file mode 100644 index 000000000..5bd9d50ca --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/trace.h @@ -0,0 +1,118 @@ +/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ TRACE.h
+
+Abstract:
+
+ Header file for the debug tracing related function defintions and macros.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include <evntrace.h> // For TRACE_LEVEL definitions
+
+#if !defined(EVENT_TRACING)
+
+//
+// TODO: These defines are missing in evntrace.h
+// in some DDK build environments (XP).
+//
+#if !defined(TRACE_LEVEL_NONE)
+ #define TRACE_LEVEL_NONE 0
+ #define TRACE_LEVEL_CRITICAL 1
+ #define TRACE_LEVEL_FATAL 1
+ #define TRACE_LEVEL_ERROR 2
+ #define TRACE_LEVEL_WARNING 3
+ #define TRACE_LEVEL_INFORMATION 4
+ #define TRACE_LEVEL_VERBOSE 5
+ #define TRACE_LEVEL_RESERVED6 6
+ #define TRACE_LEVEL_RESERVED7 7
+ #define TRACE_LEVEL_RESERVED8 8
+ #define TRACE_LEVEL_RESERVED9 9
+#endif
+
+
+//
+// Define Debug Flags
+//
+#define DBG_INIT 0x00000001
+#define DBG_PNP 0x00000002
+#define DBG_POWER 0x00000004
+#define DBG_WMI 0x00000008
+#define DBG_CREATE_CLOSE 0x00000010
+#define DBG_IOCTLS 0x00000020
+#define DBG_WRITE 0x00000040
+#define DBG_READ 0x00000080
+#define DBG_DPC 0x00000100
+#define DBG_INTERRUPT 0x00000200
+#define DBG_LOCKS 0x00000400
+#define DBG_QUEUEING 0x00000800
+#define DBG_HW_ACCESS 0x00001000
+
+VOID
+TraceEvents (
+ IN ULONG DebugPrintLevel,
+ IN ULONG DebugPrintFlag,
+ IN PCCHAR DebugMessage,
+ ...
+ );
+
+#define WPP_INIT_TRACING(DriverObject, RegistryPath)
+#define WPP_CLEANUP(DriverObject)
+
+#else
+//
+// If software tracing is defined in the sources file..
+// WPP_DEFINE_CONTROL_GUID specifies the GUID used for this driver.
+// *** REPLACE THE GUID WITH YOUR OWN UNIQUE ID ***
+// WPP_DEFINE_BIT allows setting debug bit masks to selectively print.
+// The names defined in the WPP_DEFINE_BIT call define the actual names
+// that are used to control the level of tracing for the control guid
+// specified.
+//
+// Name of the logger is Serial and the guid is
+// {F3A79AB6-9827-4419-9465-45CF949EF659}
+// (0xf3a79ab6, 0x9827, 0x4419, 0x94, 0x65, 0x45, 0xcf, 0x94, 0x9e, 0xf6, 0x59);
+//
+
+#define WPP_CHECK_FOR_NULL_STRING //to prevent exceptions due to NULL strings
+
+#define WPP_CONTROL_GUIDS \
+ WPP_DEFINE_CONTROL_GUID(SerialTraceGuid,(bc6c9364,fc67,42c5,acf7,abed3b12ecc6), \
+ WPP_DEFINE_BIT(DBG_INIT) /* bit 0 = 0x00000001 */ \
+ WPP_DEFINE_BIT(DBG_PNP) /* bit 1 = 0x00000002 */ \
+ WPP_DEFINE_BIT(DBG_POWER) /* bit 2 = 0x00000004 */ \
+ WPP_DEFINE_BIT(DBG_WMI) /* bit 3 = 0x00000008 */ \
+ WPP_DEFINE_BIT(DBG_CREATE_CLOSE) /* bit 4 = 0x00000010 */ \
+ WPP_DEFINE_BIT(DBG_IOCTLS) /* bit 5 = 0x00000020 */ \
+ WPP_DEFINE_BIT(DBG_WRITE) /* bit 6 = 0x00000040 */ \
+ WPP_DEFINE_BIT(DBG_READ) /* bit 7 = 0x00000080 */ \
+ WPP_DEFINE_BIT(DBG_DPC) /* bit 8 = 0x00000100 */ \
+ WPP_DEFINE_BIT(DBG_INTERRUPT) /* bit 9 = 0x00000200 */ \
+ WPP_DEFINE_BIT(DBG_LOCKS) /* bit 10 = 0x00000400 */ \
+ WPP_DEFINE_BIT(DBG_QUEUEING) /* bit 11 = 0x00000800 */ \
+ WPP_DEFINE_BIT(DBG_HW_ACCESS) /* bit 12 = 0x00001000 */ \
+ /* You can have up to 32 defines. If you want more than that,\
+ you have to provide another trace control GUID */\
+ )
+
+
+#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) WPP_LEVEL_LOGGER(flags)
+#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)
+
+
+#endif
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/utils.c b/tests/projects/windows/driver/kmdf/serial/utils.c new file mode 100644 index 000000000..a84136efe --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/utils.c @@ -0,0 +1,1946 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ utils.c
+
+Abstract:
+
+ This module contains code that perform queueing and completion
+ manipulation on requests. Also module generic functions such
+ as error logging.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "utils.tmh"
+#endif
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGESRP0,SerialMemCompare)
+#pragma alloc_text(PAGESRP0,SerialLogError)
+#pragma alloc_text(PAGESRP0,SerialMarkHardwareBroken)
+#endif // ALLOC_PRAGMA
+
+
+VOID
+SerialRundownIrpRefs(
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFTIMER IntervalTimer,
+ IN WDFTIMER TotalTimer,
+ IN PSERIAL_DEVICE_EXTENSION PDevExt,
+ IN LONG RefType
+ );
+
+static const PHYSICAL_ADDRESS SerialPhysicalZero = {0};
+
+VOID
+SerialPurgeRequests(
+ IN WDFQUEUE QueueToClean,
+ IN WDFREQUEST *CurrentOpRequest
+ )
+
+/*++
+
+Routine Description:
+
+ This function is used to cancel all queued and the current irps
+ for reads or for writes. Called at DPC level.
+
+Arguments:
+
+ QueueToClean - A pointer to the queue which we're going to clean out.
+
+ CurrentOpRequest - Pointer to a pointer to the current request.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS status;
+ PREQUEST_CONTEXT reqContext;
+
+ WdfIoQueuePurge(QueueToClean, WDF_NO_EVENT_CALLBACK, WDF_NO_CONTEXT);
+
+ //
+ // The queue is clean. Now go after the current if
+ // it's there.
+ //
+
+ if (*CurrentOpRequest) {
+
+ PFN_WDF_REQUEST_CANCEL CancelRoutine;
+
+ reqContext = SerialGetRequestContext(*CurrentOpRequest);
+ CancelRoutine = reqContext->CancelRoutine;
+ //
+ // Clear the common cancel routine but don't clear the reference because the
+ // request specific cancel routine called below will clear the reference.
+ //
+ status = SerialClearCancelRoutine(*CurrentOpRequest, FALSE);
+ if (NT_SUCCESS(status)) {
+ //
+ // Let us just call the CancelRoutine to start the next request.
+ //
+ if(CancelRoutine) {
+ CancelRoutine(*CurrentOpRequest);
+ }
+ }
+ }
+}
+
+VOID
+SerialFlushRequests(
+ IN WDFQUEUE QueueToClean,
+ IN WDFREQUEST *CurrentOpRequest
+ )
+
+/*++
+
+Routine Description:
+
+ This function is used to cancel all queued and the current irps
+ for reads or for writes. Called at DPC level.
+
+Arguments:
+
+ QueueToClean - A pointer to the queue which we're going to clean out.
+
+ CurrentOpRequest - Pointer to a pointer to the current request.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ SerialPurgeRequests(QueueToClean, CurrentOpRequest);
+
+ //
+ // Since purge puts the queue state to fail requests, we have to explicitly
+ // change the queue state to accept requests.
+ //
+ WdfIoQueueStart(QueueToClean);
+
+}
+
+
+VOID
+SerialGetNextRequest(
+ IN WDFREQUEST * CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ OUT WDFREQUEST * NextRequest,
+ IN BOOLEAN CompleteCurrent,
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This function is used to make the head of the particular
+ queue the current request. It also completes the what
+ was the old current request if desired.
+
+Arguments:
+
+ CurrentOpRequest - Pointer to a pointer to the currently active
+ request for the particular work list. Note that
+ this item is not actually part of the list.
+
+ QueueToProcess - The list to pull the new item off of.
+
+ NextIrp - The next Request to process. Note that CurrentOpRequest
+ will be set to this value under protection of the
+ cancel spin lock. However, if *NextIrp is NULL when
+ this routine returns, it is not necessaryly true the
+ what is pointed to by CurrentOpRequest will also be NULL.
+ The reason for this is that if the queue is empty
+ when we hold the cancel spin lock, a new request may come
+ in immediately after we release the lock.
+
+ CompleteCurrent - If TRUE then this routine will complete the
+ request pointed to by the pointer argument
+ CurrentOpRequest.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ WDFREQUEST oldRequest = NULL;
+ PREQUEST_CONTEXT reqContext;
+ NTSTATUS status;
+
+ UNREFERENCED_PARAMETER(Extension);
+
+ oldRequest = *CurrentOpRequest;
+ *CurrentOpRequest = NULL;
+
+ //
+ // Check to see if there is a new request to start up.
+ //
+
+ status = WdfIoQueueRetrieveNextRequest(
+ QueueToProcess,
+ CurrentOpRequest
+ );
+
+ if(!NT_SUCCESS(status)) {
+ ASSERTMSG("WdfIoQueueRetrieveNextRequest failed",
+ status == STATUS_NO_MORE_ENTRIES);
+ }
+
+ *NextRequest = *CurrentOpRequest;
+
+ if (CompleteCurrent) {
+
+ if (oldRequest) {
+
+ reqContext = SerialGetRequestContext(oldRequest);
+
+ SerialCompleteRequest(oldRequest,
+ reqContext->Status,
+ reqContext->Information);
+ }
+ }
+}
+
+VOID
+SerialTryToCompleteCurrent(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN PFN_WDF_INTERRUPT_SYNCHRONIZE SynchRoutine OPTIONAL,
+ IN NTSTATUS StatusToUse,
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess OPTIONAL,
+ IN WDFTIMER IntervalTimer OPTIONAL,
+ IN WDFTIMER TotalTimer OPTIONAL,
+ IN PSERIAL_START_ROUTINE Starter OPTIONAL,
+ IN PSERIAL_GET_NEXT_ROUTINE GetNextRequest OPTIONAL,
+ IN LONG RefType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to remove all of the reasons there are
+ references on the current read/write. If everything can be completed
+ it will complete this read/write and try to start another.
+
+ NOTE: This routine assumes that it is called with the cancel
+ spinlock held.
+
+Arguments:
+
+ Extension - Simply a pointer to the device extension.
+
+ SynchRoutine - A routine that will synchronize with the isr
+ and attempt to remove the knowledge of the
+ current request from the isr. NOTE: This pointer
+ can be null.
+
+ IrqlForRelease - This routine is called with the cancel spinlock held.
+ This is the irql that was current when the cancel
+ spinlock was acquired.
+
+ StatusToUse - The request's status field will be set to this value, if
+ this routine can complete the request.
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PREQUEST_CONTEXT reqContext;
+
+ ASSERTMSG("SerialTryToCompleteCurrent: CurrentOpRequest is NULL", *CurrentOpRequest);
+
+ reqContext = SerialGetRequestContext(*CurrentOpRequest);
+
+ if(RefType == SERIAL_REF_ISR || RefType == SERIAL_REF_XOFF_REF) {
+ //
+ // We can decrement the reference to "remove" the fact
+ // that the caller no longer will be accessing this request.
+ //
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ RefType
+ );
+ }
+
+ if (SynchRoutine) {
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SynchRoutine,
+ Extension
+ );
+
+ }
+
+ //
+ // Try to run down all other references to this request.
+ //
+
+ SerialRundownIrpRefs(
+ CurrentOpRequest,
+ IntervalTimer,
+ TotalTimer,
+ Extension,
+ RefType
+ );
+
+ if(StatusToUse == STATUS_CANCELLED) {
+ //
+ // This function is called from a cancelroutine. So mark
+ // the request as cancelled. We need to do this because
+ // we may not complete the request below if somebody
+ // else has a reference to it.
+ // This state variable was added to avoid calling
+ // WdfRequestMarkCancelable second time on a request that
+ // has cancelled but wasn't completed in the cancel routine.
+ //
+ reqContext->Cancelled = TRUE;
+ }
+
+ //
+ // See if the ref count is zero after trying to complete everybody else.
+ //
+
+ if (!SERIAL_REFERENCE_COUNT(reqContext)) {
+
+ WDFREQUEST newRequest;
+
+
+ //
+ // The ref count was zero so we should complete this
+ // request.
+ //
+ // The following call will also cause the current request to be
+ // completed.
+ //
+
+ reqContext->Status = StatusToUse;
+
+ if (StatusToUse == STATUS_CANCELLED) {
+
+ reqContext->Information = 0;
+
+ }
+
+ if (GetNextRequest) {
+
+ GetNextRequest(
+ CurrentOpRequest,
+ QueueToProcess,
+ &newRequest,
+ TRUE,
+ Extension
+ );
+
+ if (newRequest) {
+
+ Starter(Extension);
+
+ }
+
+ } else {
+
+ WDFREQUEST oldRequest = *CurrentOpRequest;
+
+ //
+ // There was no get next routine. We will simply complete
+ // the request. We should make sure that we null out the
+ // pointer to the pointer to this request.
+ //
+
+ *CurrentOpRequest = NULL;
+
+ SerialCompleteRequest(oldRequest,
+ reqContext->Status,
+ reqContext->Information);
+ }
+
+ } else {
+
+
+ }
+
+}
+
+
+VOID
+SerialEvtIoStop(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN ULONG ActionFlags
+ )
+/*++
+
+Routine Description:
+
+ This callback is invoked for every request pending in the driver (not queue) -
+ in-flight request. The Action parameter tells us why the callback is invoked -
+ because the device is being stopped, removed or suspended. In this
+ driver, we have told the framework not to stop or remove when there
+ are pending requests, so only reason for this callback is when the system is
+ suspending.
+
+Arguments:
+
+ Queue - Queue the request currently belongs to
+ Request - Request that is currently out of queue and being processed by the driver
+ Action - Reason for this callback
+
+Return Value:
+
+ None. Acknowledge the request so that framework can contiue suspending the
+ device.
+
+--*/
+{
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Queue);
+
+ reqContext = SerialGetRequestContext(Request);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE,
+ "--> SerialEvtIoStop %x %p\n", ActionFlags, Request);
+
+ //
+ // System suspends all the timers before asking the driver to goto
+ // sleep. So let us not worry about cancelling the timers. Also the
+ // framework will disconnect the interrupt before calling our
+ // D0Exit handler so we can be sure that nobody will touch the hardware.
+ // So just acknowledge callback to say that we are okay to stop due to
+ // system suspend. Please note that since we have taken a power reference
+ // we will never idle out when there is an open handle. Also we have told
+ // the framework to not stop for resource rebalancing or remove when there are
+ // open handles, so let us not worry about that either.
+ //
+ if (ActionFlags & WdfRequestStopRequestCancelable) {
+ PFN_WDF_REQUEST_CANCEL cancelRoutine;
+
+ //
+ // Request is in a cancelable state. So unmark cancelable before you
+ // acknowledge. We will mark the request cancelable when we resume.
+ //
+ cancelRoutine = reqContext->CancelRoutine;
+
+ SerialClearCancelRoutine(Request, TRUE);
+
+ //
+ // SerialClearCancelRoutine clears the cancel-routine. So set it back
+ // in the context. We will need that when we resume.
+ //
+ reqContext->CancelRoutine = cancelRoutine;
+
+ reqContext->MarkCancelableOnResume = TRUE;
+
+ ActionFlags &= ~WdfRequestStopRequestCancelable;
+ }
+
+ ASSERT(ActionFlags == WdfRequestStopActionSuspend);
+
+ WdfRequestStopAcknowledge(Request, FALSE); // Don't requeue the request
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE,
+ "<-- SerialEvtIoStop \n");
+}
+
+VOID
+SerialEvtIoResume(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request
+ )
+/*++
+
+Routine Description:
+
+ This callback is invoked for every request pending in the driver - in-flight
+ request - to notify that the hardware is ready for contiuing the processing
+ of the request.
+
+Arguments:
+
+ Queue - Queue the request currently belongs to
+ Request - Request that is currently out of queue and being processed by the driver
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Queue);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE,
+ "--> SerialEvtIoResume %p \n", Request);
+
+ reqContext = SerialGetRequestContext(Request);
+
+ //
+ // If we unmarked cancelable on suspend, let us mark it cancelable again.
+ //
+ if (reqContext->MarkCancelableOnResume) {
+ SerialSetCancelRoutine(Request, reqContext->CancelRoutine);
+ reqContext->MarkCancelableOnResume = FALSE;
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE,
+ "<-- SerialEvtIoResume \n");
+}
+
+VOID
+SerialRundownIrpRefs(
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFTIMER IntervalTimer OPTIONAL,
+ IN WDFTIMER TotalTimer OPTIONAL,
+ IN PSERIAL_DEVICE_EXTENSION PDevExt,
+ IN LONG RefType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine runs through the various items that *could*
+ have a reference to the current read/write. It try's to remove
+ the reason. If it does succeed in removing the reason it
+ will decrement the reference count on the request.
+
+ NOTE: This routine assumes that it is called with the cancel
+ spin lock held.
+
+Arguments:
+
+ CurrentOpRequest - Pointer to a pointer to current request for the
+ particular operation.
+
+ IntervalTimer - Pointer to the interval timer for the operation.
+ NOTE: This could be null.
+
+ TotalTimer - Pointer to the total timer for the operation.
+ NOTE: This could be null.
+
+ PDevExt - Pointer to device extension
+
+Return Value:
+
+ None.
+
+--*/
+
+
+{
+ PREQUEST_CONTEXT reqContext;
+ WDFREQUEST request = *CurrentOpRequest;
+
+ reqContext = SerialGetRequestContext(request);
+
+ if(RefType == SERIAL_REF_CANCEL) {
+ //
+ // Caller is a cancel routine. So just clear the reference.
+ //
+ SERIAL_CLEAR_REFERENCE( reqContext, SERIAL_REF_CANCEL );
+ reqContext->CancelRoutine = NULL;
+
+ } else {
+ //
+ // Try to clear the cancelable state.
+ //
+ SerialClearCancelRoutine(request, TRUE);
+ }
+ if (IntervalTimer) {
+
+ //
+ // Try to cancel the operations interval timer. If the operation
+ // returns true then the timer did have a reference to the
+ // request. Since we've canceled this timer that reference is
+ // no longer valid and we can decrement the reference count.
+ //
+ // If the cancel returns false then this means either of two things:
+ //
+ // a) The timer has already fired.
+ //
+ // b) There never was an interval timer.
+ //
+ // In the case of "b" there is no need to decrement the reference
+ // count since the "timer" never had a reference to it.
+ //
+ // In the case of "a", then the timer itself will be coming
+ // along and decrement it's reference. Note that the caller
+ // of this routine might actually be the this timer, so
+ // decrement the reference.
+ //
+
+ if (SerialCancelTimer(IntervalTimer, PDevExt)) {
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_INT_TIMER
+ );
+
+ } else if(RefType == SERIAL_REF_INT_TIMER) { // caller is the timer
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_INT_TIMER
+ );
+ }
+
+ }
+
+ if (TotalTimer) {
+
+ //
+ // Try to cancel the operations total timer. If the operation
+ // returns true then the timer did have a reference to the
+ // request. Since we've canceled this timer that reference is
+ // no longer valid and we can decrement the reference count.
+ //
+ // If the cancel returns false then this means either of two things:
+ //
+ // a) The timer has already fired.
+ //
+ // b) There never was an total timer.
+ //
+ // In the case of "b" there is no need to decrement the reference
+ // count since the "timer" never had a reference to it.
+ //
+ // In the case of "a", then the timer itself will be coming
+ // along and decrement it's reference. Note that the caller
+ // of this routine might actually be the this timer, so
+ // decrement the reference.
+ //
+
+ if (SerialCancelTimer(TotalTimer, PDevExt)) {
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_TOTAL_TIMER
+ );
+
+ } else if(RefType == SERIAL_REF_TOTAL_TIMER) { // caller is the timer
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_TOTAL_TIMER
+ );
+ }
+ }
+}
+
+
+VOID
+SerialStartOrQueue(
+ IN PSERIAL_DEVICE_EXTENSION Extension,
+ IN WDFREQUEST Request,
+ IN WDFQUEUE QueueToExamine,
+ IN WDFREQUEST *CurrentOpRequest,
+ IN PSERIAL_START_ROUTINE Starter
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to either start or queue any requst
+ that can be queued in the driver.
+
+Arguments:
+
+ Extension - Points to the serial device extension.
+
+ Request - The request to either queue or start. In either
+ case the request will be marked pending.
+
+ QueueToExamine - The queue the request will be place on if there
+ is already an operation in progress.
+
+ CurrentOpRequest - Pointer to a pointer to the request the is current
+ for the queue. The pointer pointed to will be
+ set with to Request if what CurrentOpRequest points to
+ is NULL.
+
+ Starter - The routine to call if the queue is empty.
+
+Return Value:
+
+
+--*/
+
+{
+
+ NTSTATUS status;
+ PREQUEST_CONTEXT reqContext;
+ WDF_REQUEST_PARAMETERS params;
+
+ reqContext = SerialGetRequestContext(Request);
+
+ WDF_REQUEST_PARAMETERS_INIT(¶ms);
+
+ WdfRequestGetParameters(
+ Request,
+ ¶ms);
+
+ //
+ // If this is a write request then take the amount of characters
+ // to write and add it to the count of characters to write.
+ //
+
+ if (params.Type == WdfRequestTypeWrite) {
+
+ Extension->TotalCharsQueued += reqContext->Length;
+
+ } else if ((params.Type == WdfRequestTypeDeviceControl) &&
+ ((params.Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_IMMEDIATE_CHAR) ||
+ (params.Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_XOFF_COUNTER))) {
+
+ reqContext->IoctlCode = params.Parameters.DeviceIoControl.IoControlCode; // We need this in the destroy callback
+
+ Extension->TotalCharsQueued++;
+
+ }
+
+ if (IsQueueEmpty(QueueToExamine) && !(*CurrentOpRequest)) {
+
+ //
+ // There were no current operation. Mark this one as
+ // current and start it up.
+ //
+
+ *CurrentOpRequest = Request;
+
+ Starter(Extension);
+
+ return;
+
+ } else {
+
+ //
+ // We don't know how long the request will be in the
+ // queue. If it gets cancelled while waiting in the queue, we will
+ // be notified by EvtCanceledOnQueue callback so that we can readjust
+ // the lenght or free the buffer.
+ //
+ reqContext->Extension = Extension; // We need this in the destroy callback
+
+ status = WdfRequestForwardToIoQueue(Request, QueueToExamine);
+ if(!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_READ, "WdfRequestForwardToIoQueue failed%X\n", status);
+ ASSERTMSG("WdfRequestForwardToIoQueue failed ", FALSE);
+ SerialCompleteRequest(Request, status, 0);
+ }
+
+ return;
+ }
+}
+
+VOID
+SerialEvtCanceledOnQueue(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request
+ )
+
+/*++
+
+Routine Description:
+
+ Called when the request is cancelled while it's waiting
+ on the queue. This callback is used instead of EvtCleanupCallback
+ on the request because this one will be called with the
+ presentation lock held.
+
+
+Arguments:
+
+ Queue - Queue in which the request currently waiting
+ Request - Request being cancelled
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION extension = NULL;
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Queue);
+
+ reqContext = SerialGetRequestContext(Request);
+
+ extension = reqContext->Extension;
+
+ //
+ // If this is a write request then take the amount of characters
+ // to write and subtract it from the count of characters to write.
+ //
+
+ if (reqContext->MajorFunction == IRP_MJ_WRITE) {
+
+ extension->TotalCharsQueued -= reqContext->Length;
+
+ } else if (reqContext->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
+
+ //
+ // If it's an immediate then we need to decrement the
+ // count of chars queued. If it's a resize then we
+ // need to deallocate the pool that we're passing on
+ // to the "resizing" routine.
+ //
+
+ if (( reqContext->IoctlCode == IOCTL_SERIAL_IMMEDIATE_CHAR) ||
+ (reqContext->IoctlCode == IOCTL_SERIAL_XOFF_COUNTER)) {
+
+ extension->TotalCharsQueued--;
+
+ } else if (reqContext->IoctlCode == IOCTL_SERIAL_SET_QUEUE_SIZE) {
+
+ //
+ // We shoved the pointer to the memory into the
+ // the type 3 buffer pointer which we KNOW we
+ // never use.
+ //
+
+ ASSERT(reqContext->Type3InputBuffer);
+
+ ExFreePool(reqContext->Type3InputBuffer);
+
+ reqContext->Type3InputBuffer = NULL;
+
+ }
+
+ }
+
+ SerialCompleteRequest(Request, WdfRequestGetStatus(Request), 0);
+}
+
+
+NTSTATUS
+SerialCompleteIfError(
+ PSERIAL_DEVICE_EXTENSION extension,
+ WDFREQUEST Request
+ )
+
+/*++
+
+Routine Description:
+
+ If the current request is not an IOCTL_SERIAL_GET_COMMSTATUS request and
+ there is an error and the application requested abort on errors,
+ then cancel the request.
+
+Arguments:
+
+ extension - Pointer to the device context
+
+ Request - Pointer to the WDFREQUEST to test.
+
+Return Value:
+
+ STATUS_SUCCESS or STATUS_CANCELLED.
+
+--*/
+
+{
+
+ WDF_REQUEST_PARAMETERS params;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ if ((extension->HandFlow.ControlHandShake &
+ SERIAL_ERROR_ABORT) && extension->ErrorWord) {
+
+ WDF_REQUEST_PARAMETERS_INIT(¶ms);
+
+ WdfRequestGetParameters(
+ Request,
+ ¶ms
+ );
+
+
+ //
+ // There is a current error in the driver. No requests should
+ // come through except for the GET_COMMSTATUS.
+ //
+
+ if ((params.Type != WdfRequestTypeDeviceControl) ||
+ (params.Parameters.DeviceIoControl.IoControlCode != IOCTL_SERIAL_GET_COMMSTATUS)) {
+ status = STATUS_CANCELLED;
+ SerialCompleteRequest(Request, status, 0);
+ }
+
+ }
+
+ return status;
+
+}
+
+NTSTATUS
+SerialCreateTimersAndDpcs(
+ IN PSERIAL_DEVICE_EXTENSION pDevExt
+ )
+/*++
+
+Routine Description:
+
+ This function creates all the timers and DPC objects. All the objects
+ are associated with the WDFDEVICE and the callbacks are serialized
+ with the device callbacks. Also these objects will be deleted automatically
+ when the device is deleted, so there is no need for the driver to explicitly
+ delete the objects.
+
+Arguments:
+
+ PDevExt - Pointer to the device extension for the device
+
+Return Value:
+
+ return NTSTATUS
+
+--*/
+{
+ WDF_DPC_CONFIG dpcConfig;
+ WDF_TIMER_CONFIG timerConfig;
+ NTSTATUS status;
+ WDF_OBJECT_ATTRIBUTES dpcAttributes;
+ WDF_OBJECT_ATTRIBUTES timerAttributes;
+
+ //
+ // Initialize all the timers used to timeout operations.
+ //
+ //
+ // This timer dpc is fired off if the timer for the total timeout
+ // for the read expires. It will cause the current read to complete.
+ //
+
+ WDF_TIMER_CONFIG_INIT(&timerConfig, SerialReadTimeout);
+
+ timerConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ timerAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfTimerCreate(&timerConfig,
+ &timerAttributes,
+ &pDevExt->ReadRequestTotalTimer);
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(ReadRequestTotalTimer) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off if the timer for the interval timeout
+ // expires. If no more characters have been read then the
+ // dpc routine will cause the read to complete. However, if
+ // more characters have been read then the dpc routine will
+ // resubmit the timer.
+ //
+ WDF_TIMER_CONFIG_INIT(&timerConfig, SerialIntervalReadTimeout);
+
+ timerConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ timerAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfTimerCreate(&timerConfig,
+ &timerAttributes,
+ &pDevExt->ReadRequestIntervalTimer);
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(ReadRequestIntervalTimer) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off if the timer for the total timeout
+ // for the write expires. It will queue a dpc routine that
+ // will cause the current write to complete.
+ //
+ //
+
+ WDF_TIMER_CONFIG_INIT(&timerConfig, SerialWriteTimeout);
+
+ timerConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ timerAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfTimerCreate(&timerConfig,
+ &timerAttributes,
+ &pDevExt->WriteRequestTotalTimer);
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(WriteRequestTotalTimer) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off if the transmit immediate char
+ // character times out. The dpc routine will "grab" the
+ // request from the isr and time it out.
+ //
+ WDF_TIMER_CONFIG_INIT(&timerConfig, SerialTimeoutImmediate);
+
+ timerConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ timerAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfTimerCreate(&timerConfig,
+ &timerAttributes,
+ &pDevExt->ImmediateTotalTimer);
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(ImmediateTotalTimer) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off if the timer used to "timeout" counting
+ // the number of characters received after the Xoff ioctl is started
+ // expired.
+ //
+
+ WDF_TIMER_CONFIG_INIT(&timerConfig, SerialTimeoutXoff);
+
+ timerConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ timerAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfTimerCreate(&timerConfig,
+ &timerAttributes,
+ &pDevExt->XoffCountTimer);
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(XoffCountTimer) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off when a timer expires (after one
+ // character time), so that code can be invoked that will
+ // check to see if we should lower the RTS line when
+ // doing transmit toggling.
+ //
+ WDF_TIMER_CONFIG_INIT(&timerConfig, SerialInvokePerhapsLowerRTS);
+
+ timerConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ timerAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfTimerCreate(&timerConfig,
+ &timerAttributes,
+ &pDevExt->LowerRTSTimer);
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(LowerRTSTimer) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // Create a DPC to complete read requests.
+ //
+
+ WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteWrite);
+
+ dpcConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes);
+ dpcAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfDpcCreate(&dpcConfig,
+ &dpcAttributes,
+ &pDevExt->CompleteWriteDpc);
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CompleteWriteDpc) failed [%#08lx]\n", status);
+ return status;
+ }
+
+
+ //
+ // Create a DPC to complete read requests.
+ //
+
+ WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteRead);
+
+ dpcConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes);
+ dpcAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfDpcCreate(&dpcConfig,
+ &dpcAttributes,
+ &pDevExt->CompleteReadDpc);
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CompleteReadDpc) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off if a comm error occurs. It will
+ // cancel all pending reads and writes.
+ //
+ WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCommError);
+
+ dpcConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes);
+ dpcAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfDpcCreate(&dpcConfig,
+ &dpcAttributes,
+ &pDevExt->CommErrorDpc);
+
+
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CommErrorDpc) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off when the transmit immediate char
+ // character is given to the hardware. It will simply complete
+ // the request.
+ //
+
+ WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteImmediate);
+
+ dpcConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes);
+ dpcAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfDpcCreate(&dpcConfig,
+ &dpcAttributes,
+ &pDevExt->CompleteImmediateDpc);
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CompleteImmediateDpc) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off if an event occurs and there was
+ // a request waiting on that event. A dpc routine will execute
+ // that completes the request.
+ //
+ WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteWait);
+
+ dpcConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes);
+ dpcAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfDpcCreate(&dpcConfig,
+ &dpcAttributes,
+ &pDevExt->CommWaitDpc);
+ if (!NT_SUCCESS(status)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CommWaitDpc) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ //
+ // This dpc is fired off if the xoff counter actually runs down
+ // to zero.
+ //
+ WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteXoff);
+
+ dpcConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes);
+ dpcAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfDpcCreate(&dpcConfig,
+ &dpcAttributes,
+ &pDevExt->XoffCountCompleteDpc);
+
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(XoffCountCompleteDpc) failed [%#08lx]\n", status);
+ return status;
+ }
+
+
+ //
+ // This dpc is fired off only from device level to start off
+ // a timer that will queue a dpc to check if the RTS line
+ // should be lowered when we are doing transmit toggling.
+ //
+ WDF_DPC_CONFIG_INIT(&dpcConfig, SerialStartTimerLowerRTS);
+
+ dpcConfig.AutomaticSerialization = TRUE;
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes);
+ dpcAttributes.ParentObject = pDevExt->WdfDevice;
+
+ status = WdfDpcCreate(&dpcConfig,
+ &dpcAttributes,
+ &pDevExt->StartTimerLowerRTSDpc);
+ if (!NT_SUCCESS(status)) {
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(StartTimerLowerRTSDpc) failed [%#08lx]\n", status);
+ return status;
+ }
+
+ return status;
+}
+
+
+
+
+BOOLEAN
+SerialInsertQueueDpc(IN WDFDPC PDpc)
+/*++
+
+Routine Description:
+
+ This function must be called to queue DPC's for the serial driver.
+
+Arguments:
+
+ PDpc - Pointer to the Dpc object
+
+Return Value:
+
+ Kicks up return value from KeInsertQueueDpc()
+
+--*/
+{
+ //
+ // If the specified DPC object is not currently in the queue, WdfDpcEnqueue
+ // queues the DPC and returns TRUE.
+ //
+
+ return WdfDpcEnqueue(PDpc);
+}
+
+
+
+BOOLEAN
+SerialSetTimer(IN WDFTIMER Timer, IN LARGE_INTEGER DueTime)
+/*++
+
+Routine Description:
+
+ This function must be called to set timers for the serial driver.
+
+Arguments:
+
+ Timer - pointer to timer dispatcher object
+
+ DueTime - time at which the timer should expire
+
+
+Return Value:
+
+ Kicks up return value from KeSetTimerEx()
+
+--*/
+{
+ BOOLEAN result;
+ //
+ // If the timer object was already in the system timer queue, WdfTimerStart returns TRUE
+ //
+ result = WdfTimerStart(Timer, DueTime.QuadPart);
+
+ return result;
+
+}
+
+
+VOID
+SerialDrainTimersAndDpcs(
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ )
+/*++
+
+Routine Description:
+
+ This function cancels all the timers and Dpcs and waits for them
+ to run to completion if they are already fired.
+
+Arguments:
+
+ PDevExt - Pointer to the device extension for the device that needs to
+ set a timer
+
+Return Value:
+
+--*/
+{
+ WdfTimerStop(PDevExt->ReadRequestTotalTimer, TRUE);
+
+ WdfTimerStop(PDevExt->ReadRequestIntervalTimer, TRUE);
+
+ WdfTimerStop(PDevExt->WriteRequestTotalTimer, TRUE);
+
+ WdfTimerStop(PDevExt->ImmediateTotalTimer, TRUE);
+
+ WdfTimerStop(PDevExt->XoffCountTimer, TRUE);
+
+ WdfTimerStop(PDevExt->LowerRTSTimer, TRUE);
+
+ WdfDpcCancel(PDevExt->CompleteWriteDpc, TRUE);
+
+ WdfDpcCancel(PDevExt->CompleteReadDpc, TRUE);
+
+ WdfDpcCancel(PDevExt->CommErrorDpc, TRUE);
+
+ WdfDpcCancel(PDevExt->CompleteImmediateDpc, TRUE);
+
+ WdfDpcCancel(PDevExt->CommWaitDpc, TRUE);
+
+ WdfDpcCancel(PDevExt->XoffCountCompleteDpc, TRUE);
+
+ WdfDpcCancel(PDevExt->StartTimerLowerRTSDpc, TRUE);
+
+ return;
+}
+
+
+
+BOOLEAN
+SerialCancelTimer(
+ IN WDFTIMER Timer,
+ IN PSERIAL_DEVICE_EXTENSION PDevExt
+ )
+/*++
+
+Routine Description:
+
+ This function must be called to cancel timers for the serial driver.
+
+Arguments:
+
+ Timer - pointer to timer dispatcher object
+
+ PDevExt - Pointer to the device extension for the device that needs to
+ set a timer
+
+Return Value:
+
+ True if timer was cancelled
+
+--*/
+{
+ UNREFERENCED_PARAMETER(PDevExt);
+
+ return WdfTimerStop(Timer, FALSE);
+}
+
+SERIAL_MEM_COMPARES
+SerialMemCompare(
+ IN PHYSICAL_ADDRESS A,
+ IN ULONG SpanOfA,
+ IN PHYSICAL_ADDRESS B,
+ IN ULONG SpanOfB
+ )
+/*++
+
+Routine Description:
+
+ Compare two phsical address.
+
+Arguments:
+
+ A - One half of the comparison.
+
+ SpanOfA - In units of bytes, the span of A.
+
+ B - One half of the comparison.
+
+ SpanOfB - In units of bytes, the span of B.
+
+
+Return Value:
+
+ The result of the comparison.
+
+--*/
+{
+ LARGE_INTEGER a;
+ LARGE_INTEGER b;
+
+ LARGE_INTEGER lower;
+ ULONG lowerSpan;
+ LARGE_INTEGER higher;
+
+ PAGED_CODE();
+
+ a = A;
+ b = B;
+
+ if (a.QuadPart == b.QuadPart) {
+
+ return AddressesAreEqual;
+
+ }
+
+ if (a.QuadPart > b.QuadPart) {
+
+ higher = a;
+ lower = b;
+ lowerSpan = SpanOfB;
+
+ } else {
+
+ higher = b;
+ lower = a;
+ lowerSpan = SpanOfA;
+
+ }
+
+ if ((higher.QuadPart - lower.QuadPart) >= lowerSpan) {
+
+ return AddressesAreDisjoint;
+
+ }
+
+ return AddressesOverlap;
+
+}
+
+
+VOID
+SerialLogError(
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_opt_ PDEVICE_OBJECT DeviceObject,
+ _In_ PHYSICAL_ADDRESS P1,
+ _In_ PHYSICAL_ADDRESS P2,
+ _In_ ULONG SequenceNumber,
+ _In_ UCHAR MajorFunctionCode,
+ _In_ UCHAR RetryCount,
+ _In_ ULONG UniqueErrorValue,
+ _In_ NTSTATUS FinalStatus,
+ _In_ NTSTATUS SpecificIOStatus,
+ _In_ ULONG LengthOfInsert1,
+ _In_reads_bytes_opt_(LengthOfInsert1) PWCHAR Insert1,
+ _In_ ULONG LengthOfInsert2,
+ _In_reads_bytes_opt_(LengthOfInsert2) PWCHAR Insert2
+ )
+/*++
+
+Routine Description:
+
+ This routine allocates an error log entry, copies the supplied data
+ to it, and requests that it be written to the error log file.
+
+Arguments:
+
+ DriverObject - A pointer to the driver object for the device.
+
+ DeviceObject - A pointer to the device object associated with the
+ device that had the error, early in initialization, one may not
+ yet exist.
+
+ P1,P2 - If phyical addresses for the controller ports involved
+ with the error are available, put them through as dump data.
+
+ SequenceNumber - A ulong value that is unique to an WDFREQUEST over the
+ life of the request in this driver - 0 generally means an error not
+ associated with an request.
+
+ MajorFunctionCode - If there is an error associated with the request,
+ this is the major function code of that request.
+
+ RetryCount - The number of times a particular operation has been
+ retried.
+
+ UniqueErrorValue - A unique long word that identifies the particular
+ call to this function.
+
+ FinalStatus - The final status given to the request that was associated
+ with this error. If this log entry is being made during one of
+ the retries this value will be STATUS_SUCCESS.
+
+ SpecificIOStatus - The IO status for a particular error.
+
+ LengthOfInsert1 - The length in bytes (including the terminating NULL)
+ of the first insertion string.
+
+ Insert1 - The first insertion string.
+
+ LengthOfInsert2 - The length in bytes (including the terminating NULL)
+ of the second insertion string. NOTE, there must
+ be a first insertion string for their to be
+ a second insertion string.
+
+ Insert2 - The second insertion string.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIO_ERROR_LOG_PACKET errorLogEntry;
+
+ PVOID objectToUse;
+ SHORT dumpToAllocate = 0;
+ PUCHAR ptrToFirstInsert;
+ PUCHAR ptrToSecondInsert;
+
+ PAGED_CODE();
+
+ if (Insert1 == NULL) {
+ LengthOfInsert1 = 0;
+ }
+
+ if (Insert2 == NULL) {
+ LengthOfInsert2 = 0;
+ }
+
+
+ if (ARGUMENT_PRESENT(DeviceObject)) {
+
+ objectToUse = DeviceObject;
+
+ } else {
+
+ objectToUse = DriverObject;
+
+ }
+
+ if (SerialMemCompare(
+ P1,
+ (ULONG)1,
+ SerialPhysicalZero,
+ (ULONG)1
+ ) != AddressesAreEqual) {
+
+ dumpToAllocate = (SHORT)sizeof(PHYSICAL_ADDRESS);
+
+ }
+
+ if (SerialMemCompare(
+ P2,
+ (ULONG)1,
+ SerialPhysicalZero,
+ (ULONG)1
+ ) != AddressesAreEqual) {
+
+ dumpToAllocate += (SHORT)sizeof(PHYSICAL_ADDRESS);
+
+ }
+
+ errorLogEntry = IoAllocateErrorLogEntry(
+ objectToUse,
+ (UCHAR)(sizeof(IO_ERROR_LOG_PACKET) +
+ dumpToAllocate
+ + LengthOfInsert1 +
+ LengthOfInsert2)
+ );
+
+ if ( errorLogEntry != NULL ) {
+
+ errorLogEntry->ErrorCode = SpecificIOStatus;
+ errorLogEntry->SequenceNumber = SequenceNumber;
+ errorLogEntry->MajorFunctionCode = MajorFunctionCode;
+ errorLogEntry->RetryCount = RetryCount;
+ errorLogEntry->UniqueErrorValue = UniqueErrorValue;
+ errorLogEntry->FinalStatus = FinalStatus;
+ errorLogEntry->DumpDataSize = dumpToAllocate;
+
+ if (dumpToAllocate) {
+
+ RtlCopyMemory(
+ &errorLogEntry->DumpData[0],
+ &P1,
+ sizeof(PHYSICAL_ADDRESS)
+ );
+
+ if (dumpToAllocate > sizeof(PHYSICAL_ADDRESS)) {
+
+ RtlCopyMemory(
+ ((PUCHAR)&errorLogEntry->DumpData[0])
+ +sizeof(PHYSICAL_ADDRESS),
+ &P2,
+ sizeof(PHYSICAL_ADDRESS)
+ );
+
+ ptrToFirstInsert =
+ ((PUCHAR)&errorLogEntry->DumpData[0])+(2*sizeof(PHYSICAL_ADDRESS));
+
+ } else {
+
+ ptrToFirstInsert =
+ ((PUCHAR)&errorLogEntry->DumpData[0])+sizeof(PHYSICAL_ADDRESS);
+
+
+ }
+
+ } else {
+
+ ptrToFirstInsert = (PUCHAR)&errorLogEntry->DumpData[0];
+
+ }
+
+ ptrToSecondInsert = ptrToFirstInsert + LengthOfInsert1;
+
+ if (LengthOfInsert1) {
+
+ errorLogEntry->NumberOfStrings = 1;
+ errorLogEntry->StringOffset = (USHORT)(ptrToFirstInsert -
+ (PUCHAR)errorLogEntry);
+ RtlCopyMemory(
+ ptrToFirstInsert,
+ Insert1,
+ LengthOfInsert1
+ );
+
+ if (LengthOfInsert2) {
+
+ errorLogEntry->NumberOfStrings = 2;
+ RtlCopyMemory(
+ ptrToSecondInsert,
+ Insert2,
+ LengthOfInsert2
+ );
+
+ }
+
+ }
+
+ IoWriteErrorLogEntry(errorLogEntry);
+
+ }
+
+}
+
+VOID
+SerialMarkHardwareBroken(IN PSERIAL_DEVICE_EXTENSION PDevExt)
+/*++
+
+Routine Description:
+
+ Marks a UART as broken. This causes the driver stack to stop accepting
+ requests and eventually be removed.
+
+Arguments:
+ PDevExt - Device extension attached to PDevObj
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PAGED_CODE();
+
+ //
+ // Write a log entry
+ //
+
+ SerialLogError(PDevExt->DriverObject, NULL, SerialPhysicalZero,
+ SerialPhysicalZero, 0, 0, 0, 88, STATUS_SUCCESS,
+ SERIAL_HARDWARE_FAILURE, PDevExt->DeviceName.Length
+ + sizeof(WCHAR), PDevExt->DeviceName.Buffer, 0, NULL);
+
+ SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_INIT, "Device is broken. Request a restart...\n");
+ WdfDeviceSetFailed(PDevExt->WdfDevice, WdfDeviceFailedAttemptRestart);
+}
+
+NTSTATUS
+SerialGetDivisorFromBaud(
+ IN ULONG ClockRate,
+ IN LONG DesiredBaud,
+ OUT PSHORT AppropriateDivisor
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will determine a divisor based on an unvalidated
+ baud rate.
+
+Arguments:
+
+ ClockRate - The clock input to the controller.
+
+ DesiredBaud - The baud rate for whose divisor we seek.
+
+ AppropriateDivisor - Given that the DesiredBaud is valid, the
+ LONG pointed to by this parameter will be set to the appropriate
+ value. NOTE: The long is undefined if the DesiredBaud is not
+ supported.
+
+Return Value:
+
+ This function will return STATUS_SUCCESS if the baud is supported.
+ If the value is not supported it will return a status such that
+ NT_ERROR(Status) == FALSE.
+
+--*/
+
+{
+
+ NTSTATUS status = STATUS_SUCCESS;
+ SHORT calculatedDivisor;
+ ULONG denominator;
+ ULONG remainder;
+
+ //
+ // Allow up to a 1 percent error
+ //
+
+ ULONG maxRemain18 = 18432;
+ ULONG maxRemain30 = 30720;
+ ULONG maxRemain42 = 42336;
+ ULONG maxRemain80 = 80000;
+ ULONG maxRemain;
+
+
+
+ //
+ // Reject any non-positive bauds.
+ //
+
+ denominator = DesiredBaud*(ULONG)16;
+
+ if (DesiredBaud <= 0) {
+
+ *AppropriateDivisor = -1;
+
+ } else if ((LONG)denominator < DesiredBaud) {
+
+ //
+ // If the desired baud was so huge that it cause the denominator
+ // calculation to wrap, don't support it.
+ //
+
+ *AppropriateDivisor = -1;
+
+ } else {
+
+ if (ClockRate == 1843200) {
+ maxRemain = maxRemain18;
+ } else if (ClockRate == 3072000) {
+ maxRemain = maxRemain30;
+ } else if (ClockRate == 4233600) {
+ maxRemain = maxRemain42;
+ } else {
+ maxRemain = maxRemain80;
+ }
+
+ calculatedDivisor = (SHORT)(ClockRate / denominator);
+ remainder = ClockRate % denominator;
+
+ //
+ // Round up.
+ //
+
+ if (((remainder*2) > ClockRate) && (DesiredBaud != 110)) {
+
+ calculatedDivisor++;
+ }
+
+
+ //
+ // Only let the remainder calculations effect us if
+ // the baud rate is > 9600.
+ //
+
+ if (DesiredBaud >= 9600) {
+
+ //
+ // If the remainder is less than the maximum remainder (wrt
+ // the ClockRate) or the remainder + the maximum remainder is
+ // greater than or equal to the ClockRate then assume that the
+ // baud is ok.
+ //
+
+ if ((remainder >= maxRemain) && ((remainder+maxRemain) < ClockRate)) {
+ calculatedDivisor = -1;
+ }
+
+ }
+
+ //
+ // Don't support a baud that causes the denominator to
+ // be larger than the clock.
+ //
+
+ if (denominator > ClockRate) {
+
+ calculatedDivisor = -1;
+
+ }
+
+ //
+ // Ok, Now do some special casing so that things can actually continue
+ // working on all platforms.
+ //
+
+ if (ClockRate == 1843200) {
+
+ if (DesiredBaud == 56000) {
+ calculatedDivisor = 2;
+ }
+
+ } else if (ClockRate == 3072000) {
+
+ if (DesiredBaud == 14400) {
+ calculatedDivisor = 13;
+ }
+
+ } else if (ClockRate == 4233600) {
+
+ if (DesiredBaud == 9600) {
+ calculatedDivisor = 28;
+ } else if (DesiredBaud == 14400) {
+ calculatedDivisor = 18;
+ } else if (DesiredBaud == 19200) {
+ calculatedDivisor = 14;
+ } else if (DesiredBaud == 38400) {
+ calculatedDivisor = 7;
+ } else if (DesiredBaud == 56000) {
+ calculatedDivisor = 5;
+ }
+
+ } else if (ClockRate == 8000000) {
+
+ if (DesiredBaud == 14400) {
+ calculatedDivisor = 35;
+ } else if (DesiredBaud == 56000) {
+ calculatedDivisor = 9;
+ }
+
+ }
+
+ *AppropriateDivisor = calculatedDivisor;
+
+ }
+
+
+ if (*AppropriateDivisor == -1) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ }
+
+ return status;
+
+}
+
+
+BOOLEAN
+IsQueueEmpty(
+ IN WDFQUEUE Queue
+ )
+{
+ WDF_IO_QUEUE_STATE queueStatus;
+
+ queueStatus = WdfIoQueueGetState( Queue, NULL, NULL );
+
+ return (WDF_IO_QUEUE_IDLE(queueStatus)) ? TRUE : FALSE;
+}
+
+VOID
+SerialSetCancelRoutine(
+ IN WDFREQUEST Request,
+ IN PFN_WDF_REQUEST_CANCEL CancelRoutine)
+{
+ PREQUEST_CONTEXT reqContext = SerialGetRequestContext(Request);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "-->SerialSetCancelRoutine %p \n", Request);
+
+ WdfRequestMarkCancelable(Request, CancelRoutine);
+ SERIAL_SET_REFERENCE(reqContext, SERIAL_REF_CANCEL);
+ reqContext->CancelRoutine = CancelRoutine;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "<-- SerialSetCancelRoutine \n");
+
+ return;
+}
+
+NTSTATUS
+SerialClearCancelRoutine(
+ IN WDFREQUEST Request,
+ IN BOOLEAN ClearReference
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PREQUEST_CONTEXT reqContext = SerialGetRequestContext(Request);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "-->SerialClearCancelRoutine %p %x\n",
+ Request, ClearReference);
+
+ if(SERIAL_TEST_REFERENCE(reqContext, SERIAL_REF_CANCEL))
+ {
+ status = WdfRequestUnmarkCancelable(Request);
+ if (NT_SUCCESS(status)) {
+
+ reqContext->CancelRoutine = NULL;
+ if(ClearReference) {
+
+ SERIAL_CLEAR_REFERENCE( reqContext, SERIAL_REF_CANCEL );
+
+ }
+ } else {
+ ASSERT(status == STATUS_CANCELLED);
+ }
+ }
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "-->SerialClearCancelRoutine %p\n", Request);
+
+ return status;
+}
+
+
+VOID
+SerialCompleteRequest(
+ IN WDFREQUEST Request,
+ IN NTSTATUS Status,
+ IN ULONG_PTR Info
+ )
+{
+ PREQUEST_CONTEXT reqContext;
+
+ reqContext = SerialGetRequestContext(Request);
+
+ ASSERT(reqContext->RefCount == 0);
+
+ SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP,
+ "Complete Request: %p %X 0x%I64x\n",
+ (Request), (Status), (Info));
+
+ WdfRequestCompleteWithInformation((Request), (Status), (Info));
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/waitmask.c b/tests/projects/windows/driver/kmdf/serial/waitmask.c new file mode 100644 index 000000000..c3139679e --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/waitmask.c @@ -0,0 +1,574 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ waitmask.c
+
+Abstract:
+
+ This module contains the code that is very specific to get/set/wait
+ on event mask operations in the serial driver
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "waitmask.tmh"
+#endif
+
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabWaitFromIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGiveWaitToIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialFinishOldWait;
+
+
+VOID
+SerialStartMask(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to process the set mask and wait
+ mask ioctls. Calls to this routine are serialized by
+ placing irps in the list under the protection of the
+ cancel spin lock.
+
+Arguments:
+
+ Extension - A pointer to the serial device extension.
+
+Return Value:
+
+ Will return pending for everything put the first
+ request that we actually process. Even in that
+ case it will return pending unless it can complete
+ it right away.
+
+
+--*/
+
+{
+
+
+ WDFREQUEST NewRequest;
+ PREQUEST_CONTEXT reqContext;
+ WDF_REQUEST_PARAMETERS params;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "In SerialStartMask\n");
+
+ ASSERT(Extension->CurrentMaskRequest);
+
+
+ do {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "STARTMASK - CurrentMaskRequest: %p\n",
+ Extension->CurrentMaskRequest);
+
+ WDF_REQUEST_PARAMETERS_INIT(¶ms);
+
+ WdfRequestGetParameters(
+ Extension->CurrentMaskRequest,
+ ¶ms
+ );
+
+
+ reqContext = SerialGetRequestContext(Extension->CurrentMaskRequest);
+
+ ASSERT((params.Parameters.DeviceIoControl.IoControlCode ==
+ IOCTL_SERIAL_WAIT_ON_MASK) ||
+ (params.Parameters.DeviceIoControl.IoControlCode ==
+ IOCTL_SERIAL_SET_WAIT_MASK));
+
+ if (params.Parameters.DeviceIoControl.IoControlCode ==
+ IOCTL_SERIAL_SET_WAIT_MASK) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "SERIAL - %p is a SETMASK request\n",
+ Extension->CurrentMaskRequest);
+
+ //
+ // Complete the old wait if there is one.
+ //
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialFinishOldWait,
+ Extension
+ );
+
+ //
+ // Any current waits should be on its way to completion
+ // at this point. There certainly shouldn't be any
+ // request mask location.
+ //
+
+ ASSERT(!Extension->IrpMaskLocation);
+
+ reqContext->Status = STATUS_SUCCESS;
+
+ //
+ // The following call will also cause the current
+ // call to be completed.
+ //
+
+ SerialGetNextRequest(
+ &Extension->CurrentMaskRequest,
+ Extension->MaskQueue,
+ &NewRequest,
+ TRUE,
+ Extension
+ );
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "Perhaps another mask request was found in "
+ "the queue\n"
+ "------- %p/%p <- values should be the same\n",
+ Extension->CurrentMaskRequest, NewRequest);
+
+
+ } else {
+
+ //
+ // First make sure that we have a non-zero mask.
+ // If the app queues a wait on a zero mask it can't
+ // be statisfied so it makes no sense to start it.
+ //
+
+ if ((!Extension->IsrWaitMask) || (Extension->CurrentWaitRequest)) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "WaitIrp is invalid\n"
+ "------- IsrWaitMask: %x\n"
+ "------- CurrentWaitRequest: %p\n",
+ Extension->IsrWaitMask,
+ Extension->CurrentWaitRequest);
+
+ reqContext->Status = STATUS_INVALID_PARAMETER;
+
+ SerialGetNextRequest(&Extension->CurrentMaskRequest,
+ Extension->MaskQueue, &NewRequest, TRUE,
+ Extension);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "Perhaps another mask request was found "
+ "in the queue\n"
+ "------- %p/%p <- values should be the same\n",
+ Extension->CurrentMaskRequest,NewRequest);
+
+ } else {
+
+ //
+ // Make the current mask request the current wait request and
+ // get a new current mask request. Note that when we get
+ // the new current mask request we DO NOT complete the
+ // old current mask request (which is now the current wait
+ // request.
+ //
+ // Then under the protection of the cancel spin lock
+ // we check to see if the current wait request needs to
+ // be canceled
+ //
+
+ SERIAL_INIT_REFERENCE(reqContext);
+
+ SerialSetCancelRoutine(Extension->CurrentMaskRequest,
+ SerialCancelWait);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "%p will become the current "
+ "wait request\n",
+ Extension->CurrentMaskRequest);
+ //
+ // There should never be a mask location when
+ // there isn't a current wait request. At this point
+ // there shouldn't be a current wait request also.
+ //
+
+ ASSERT(!Extension->IrpMaskLocation);
+ ASSERT(!Extension->CurrentWaitRequest);
+
+ Extension->CurrentWaitRequest = Extension->CurrentMaskRequest;
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGiveWaitToIsr,
+ Extension
+ );
+
+ //
+ // Since it isn't really the mask request anymore,
+ // null out that pointer.
+ //
+ Extension->CurrentMaskRequest = NULL;
+
+ //
+ // This will release the cancel spinlock for us
+ //
+
+ SerialGetNextRequest(&Extension->CurrentMaskRequest,
+ Extension->MaskQueue, &NewRequest,
+ FALSE, Extension);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "Perhaps another mask request was "
+ "found in the queue\n"
+ "------- %p/%p <- values should be the "
+ "same\n", Extension->CurrentMaskRequest,
+ NewRequest);
+ }
+
+ }
+
+ } while (NewRequest);
+
+ return;
+
+}
+
+BOOLEAN
+SerialGrabWaitFromIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will check to see if the ISR still knows about
+ a wait request by checking to see if the IrpMaskLocation is non-null.
+ If it is then it will zero the Irpmasklocation (which in effect
+ grabs the request away from the isr). This routine is only called
+ buy the cancel code for the wait.
+
+ NOTE: This is called by WdfInterruptSynchronize.
+
+Arguments:
+
+ Context - A pointer to the device extension
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "In SerialGrabWaitFromIsr\n");
+
+ if (Extension->IrpMaskLocation) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "The isr still owns the request %p, mask "
+ "location is %p\n"
+ "------- and system buffer is %p\n",
+ Extension->CurrentWaitRequest,Extension->IrpMaskLocation,
+ reqContext->SystemBuffer);
+
+ //
+ // The isr still "owns" the request.
+ //
+
+ *Extension->IrpMaskLocation = 0;
+ Extension->IrpMaskLocation = NULL;
+
+ reqContext->Information = sizeof(ULONG);
+
+ //
+ // Since the isr no longer references the request we need to
+ // decrement the reference count.
+ //
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ }
+
+ return FALSE;
+}
+
+BOOLEAN
+SerialGiveWaitToIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine simply sets a variable in the device extension
+ so that the isr knows that we have a wait request.
+
+ NOTE: This is called by WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that it is called with the
+ cancel spinlock held.
+
+Arguments:
+
+ Context - Simply a pointer to the device extension.
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "In SerialGiveWaitToIsr\n");
+ //
+ // There certainly shouldn't be a current mask location at
+ // this point since we have a new current wait request.
+ //
+
+ ASSERT(!Extension->IrpMaskLocation);
+
+ //
+ // The isr may or may not actually reference this request. It
+ // won't if the wait can be satisfied immediately. However,
+ // since it will then go through the normal completion sequence,
+ // we need to have an incremented reference count anyway.
+ //
+
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ if (!Extension->HistoryMask) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "No events occured prior to the wait call"
+ "\n");
+
+ //
+ // Although this wait might not be for empty transmit
+ // queue, it doesn't hurt anything to set it to false.
+ //
+
+ Extension->EmptiedTransmit = FALSE;
+
+ //
+ // Record where the "completion mask" should be set.
+ //
+
+ Extension->IrpMaskLocation = reqContext->SystemBuffer;
+ SerialDbgPrintEx( TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "The isr owns the request %p, mask location is "
+ "%p\n"
+ "------- and system buffer is %p\n",
+ Extension->CurrentWaitRequest,Extension->IrpMaskLocation,
+ reqContext->SystemBuffer);
+
+ } else {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "%x occurred prior to the wait - starting "
+ "the\n"
+ "------- completion code for %p\n",
+ Extension->HistoryMask,Extension->CurrentWaitRequest);
+
+ *((ULONG *)reqContext->SystemBuffer) =
+ Extension->HistoryMask;
+ Extension->HistoryMask = 0;
+ reqContext->Information = sizeof(ULONG);
+ reqContext->Status = STATUS_SUCCESS;
+
+ SerialInsertQueueDpc(Extension->CommWaitDpc);
+
+ }
+
+ return FALSE;
+}
+
+BOOLEAN
+SerialFinishOldWait(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will check to see if the ISR still knows about
+ a wait request by checking to see if the Irpmasklocation is non-null.
+ If it is then it will zero the Irpmasklocation (which in effect
+ grabs the request away from the isr). This routine is only called
+ buy the cancel code for the wait.
+
+ NOTE: This is called by WdfInterruptSynchronize.
+
+Arguments:
+
+ Context - A pointer to the device extension
+
+Return Value:
+
+ Always FALSE.
+
+--*/
+
+{
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ PREQUEST_CONTEXT reqContext = NULL;
+ PREQUEST_CONTEXT reqContextMask;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContextMask = SerialGetRequestContext(Extension->CurrentMaskRequest);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "In SerialFinishOldWait\n");
+
+ if (Extension->IrpMaskLocation) {
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "The isr still owns the request %p, mask "
+ "location is %p\n"
+ "------- and system buffer is %p\n",
+ Extension->CurrentWaitRequest,Extension->IrpMaskLocation,
+ reqContext->SystemBuffer);
+ //
+ // The isr still "owns" the request.
+ //
+
+ *Extension->IrpMaskLocation = 0;
+ Extension->IrpMaskLocation = NULL;
+
+ reqContext->Information = sizeof(ULONG);
+
+ //
+ // We don't decrement the reference since the completion routine
+ // will do that.
+ //
+
+ SerialInsertQueueDpc(Extension->CommWaitDpc);
+
+ }
+
+ //
+ // Don't wipe out any historical data we are still interested in.
+ //
+
+ Extension->HistoryMask &= *((ULONG *)reqContextMask->SystemBuffer);
+
+ Extension->IsrWaitMask = *((ULONG *)reqContextMask->SystemBuffer);
+ SerialDbgPrintEx( TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "Set mask location of %p, in request %p, with "
+ "system buffer of %p\n",
+ Extension->IrpMaskLocation, Extension->CurrentMaskRequest,
+ reqContextMask->SystemBuffer);
+ return FALSE;
+}
+
+VOID
+SerialCancelWait(
+ IN WDFREQUEST Request
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to cancel a request that is waiting on
+ a comm event.
+
+Arguments:
+
+ Device - Wdf handle for the device
+
+ Request - Pointer to the WDFREQUEST for the current request
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension;
+ WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request));
+
+ UNREFERENCED_PARAMETER(Request);
+
+ Extension = SerialGetDeviceExtension(device);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "Canceling wait for request %p\n",
+ Extension->CurrentWaitRequest);
+
+ SerialTryToCompleteCurrent(Extension,
+ SerialGrabWaitFromIsr,
+ STATUS_CANCELLED,
+ &Extension->CurrentWaitRequest,
+ NULL, NULL, NULL,
+ NULL, NULL, SERIAL_REF_CANCEL);
+
+}
+
+
+VOID
+SerialCompleteWait(
+ IN WDFDPC Dpc
+ )
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ ">SerialCompleteWait(%p)\n",
+ Extension);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "Completing wait for request %p\n",
+ Extension->CurrentWaitRequest);
+
+ SerialTryToCompleteCurrent(Extension, NULL, STATUS_SUCCESS,
+ &Extension->CurrentWaitRequest, NULL, NULL, NULL,
+ NULL, NULL, SERIAL_REF_ISR);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS,
+ "<SerialCompleteWait\n");
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/wmi.c b/tests/projects/windows/driver/kmdf/serial/wmi.c new file mode 100644 index 000000000..0076f4337 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/wmi.c @@ -0,0 +1,295 @@ +/*++
+
+Copyright (c) 1997 Microsoft Corporation
+
+Module Name:
+
+ wmi.c
+
+Abstract:
+
+ This module contains the code that handles the wmi IRPs for the
+ serial driver.
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+#include <wmistr.h>
+
+#if defined(EVENT_TRACING)
+#include "wmi.tmh"
+#endif
+
+EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortName;
+EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortCommData;
+EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortHWData;
+EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortPerfData;
+EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortPropData;
+
+NTSTATUS
+SerialWmiRegisterInstance(
+ WDFDEVICE Device,
+ const GUID* Guid,
+ ULONG MinInstanceBufferSize,
+ PFN_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiInstanceQueryInstance
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGESRP0, SerialWmiRegistration)
+#pragma alloc_text(PAGESRP0, SerialWmiRegisterInstance)
+#pragma alloc_text(PAGESRP0, EvtWmiQueryPortName)
+#pragma alloc_text(PAGESRP0, EvtWmiQueryPortCommData)
+#pragma alloc_text(PAGESRP0, EvtWmiQueryPortHWData)
+#pragma alloc_text(PAGESRP0, EvtWmiQueryPortPerfData)
+#pragma alloc_text(PAGESRP0, EvtWmiQueryPortPropData)
+#endif
+
+NTSTATUS
+SerialWmiRegisterInstance(
+ WDFDEVICE Device,
+ const GUID* Guid,
+ ULONG MinInstanceBufferSize,
+ PFN_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiInstanceQueryInstance
+ )
+{
+ WDF_WMI_PROVIDER_CONFIG providerConfig;
+ WDF_WMI_INSTANCE_CONFIG instanceConfig;
+
+ PAGED_CODE();
+
+ //
+ // Create and register WMI providers and instances blocks
+ //
+ WDF_WMI_PROVIDER_CONFIG_INIT(&providerConfig, Guid);
+ providerConfig.MinInstanceBufferSize = MinInstanceBufferSize;
+
+ WDF_WMI_INSTANCE_CONFIG_INIT_PROVIDER_CONFIG(&instanceConfig, &providerConfig);
+ instanceConfig.Register = TRUE;
+ instanceConfig.EvtWmiInstanceQueryInstance = EvtWmiInstanceQueryInstance;
+
+ return WdfWmiInstanceCreate(Device,
+ &instanceConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ WDF_NO_HANDLE);
+}
+
+NTSTATUS
+SerialWmiRegistration(
+ WDFDEVICE Device
+)
+/*++
+Routine Description
+
+ Registers with WMI as a data provider for this
+ instance of the device
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+
+ PAGED_CODE();
+
+ pDevExt = SerialGetDeviceExtension (Device);
+
+ //
+ // Fill in wmi perf data (all zero's)
+ //
+ RtlZeroMemory(&pDevExt->WmiPerfData, sizeof(pDevExt->WmiPerfData));
+
+ status = SerialWmiRegisterInstance(Device,
+ &MSSerial_PortName_GUID,
+ 0,
+ EvtWmiQueryPortName);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ status = SerialWmiRegisterInstance(Device,
+ &MSSerial_CommInfo_GUID,
+ sizeof(SERIAL_WMI_COMM_DATA),
+ EvtWmiQueryPortCommData);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ status = SerialWmiRegisterInstance(Device,
+ &MSSerial_HardwareConfiguration_GUID,
+ sizeof(SERIAL_WMI_HW_DATA),
+ EvtWmiQueryPortHWData);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ status = SerialWmiRegisterInstance(Device,
+ &MSSerial_PerformanceInformation_GUID,
+ sizeof(SERIAL_WMI_PERF_DATA),
+ EvtWmiQueryPortPerfData);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ status = SerialWmiRegisterInstance(Device,
+ &MSSerial_CommProperties_GUID,
+ sizeof(SERIAL_COMMPROP) + sizeof(ULONG),
+ EvtWmiQueryPortPropData);
+
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ return status;
+}
+
+//
+// WMI Call back functions
+//
+
+NTSTATUS
+EvtWmiQueryPortName(
+ IN WDFWMIINSTANCE WmiInstance,
+ IN ULONG OutBufferSize,
+ IN PVOID OutBuffer,
+ OUT PULONG BufferUsed
+ )
+{
+ WDFDEVICE device;
+ WCHAR pRegName[SYMBOLIC_NAME_LENGTH];
+ UNICODE_STRING string;
+ USHORT nameSize = sizeof(pRegName);
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+ device = WdfWmiInstanceGetDevice(WmiInstance);
+
+ status = SerialReadSymName(device, pRegName, &nameSize);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ RtlInitUnicodeString(&string, pRegName);
+
+ return WDF_WMI_BUFFER_APPEND_STRING(OutBuffer,
+ OutBufferSize,
+ &string,
+ BufferUsed);
+}
+
+NTSTATUS
+EvtWmiQueryPortCommData(
+ IN WDFWMIINSTANCE WmiInstance,
+ IN ULONG OutBufferSize,
+ IN PVOID OutBuffer,
+ OUT PULONG BufferUsed
+ )
+{
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+
+ UNREFERENCED_PARAMETER(OutBufferSize);
+
+ PAGED_CODE();
+
+ pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance));
+
+ *BufferUsed = sizeof(SERIAL_WMI_COMM_DATA);
+
+ if (OutBufferSize < *BufferUsed) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ *(PSERIAL_WMI_COMM_DATA)OutBuffer = pDevExt->WmiCommData;
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+EvtWmiQueryPortHWData(
+ IN WDFWMIINSTANCE WmiInstance,
+ IN ULONG OutBufferSize,
+ IN PVOID OutBuffer,
+ OUT PULONG BufferUsed
+ )
+{
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+
+ UNREFERENCED_PARAMETER(OutBufferSize);
+
+ PAGED_CODE();
+
+ pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance));
+
+ *BufferUsed = sizeof(SERIAL_WMI_HW_DATA);
+
+ if (OutBufferSize < *BufferUsed) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ *(PSERIAL_WMI_HW_DATA)OutBuffer = pDevExt->WmiHwData;
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+EvtWmiQueryPortPerfData(
+ IN WDFWMIINSTANCE WmiInstance,
+ IN ULONG OutBufferSize,
+ IN PVOID OutBuffer,
+ OUT PULONG BufferUsed
+ )
+{
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+
+ UNREFERENCED_PARAMETER(OutBufferSize);
+
+ PAGED_CODE();
+
+ pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance));
+
+ *BufferUsed = sizeof(SERIAL_WMI_PERF_DATA);
+
+ if (OutBufferSize < *BufferUsed) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ *(PSERIAL_WMI_PERF_DATA)OutBuffer = pDevExt->WmiPerfData;
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+EvtWmiQueryPortPropData(
+ IN WDFWMIINSTANCE WmiInstance,
+ IN ULONG OutBufferSize,
+ IN PVOID OutBuffer,
+ OUT PULONG BufferUsed
+ )
+{
+ PSERIAL_DEVICE_EXTENSION pDevExt;
+
+ UNREFERENCED_PARAMETER(OutBufferSize);
+
+ PAGED_CODE();
+
+ pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance));
+
+ *BufferUsed = sizeof(SERIAL_COMMPROP) + sizeof(ULONG);
+
+ if (OutBufferSize < *BufferUsed) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SerialGetProperties(
+ pDevExt,
+ (PSERIAL_COMMPROP)OutBuffer
+ );
+
+ *((PULONG)(((PSERIAL_COMMPROP)OutBuffer)->ProvChar)) = 0;
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/tests/projects/windows/driver/kmdf/serial/write.c b/tests/projects/windows/driver/kmdf/serial/write.c new file mode 100644 index 000000000..c67c062b4 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/write.c @@ -0,0 +1,1195 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ write.c
+
+Abstract:
+
+ This module contains the code that is very specific to write
+ operations in the serial driver
+
+Environment:
+
+ Kernel mode
+
+--*/
+
+#include "precomp.h"
+
+#if defined(EVENT_TRACING)
+#include "write.tmh"
+#endif
+
+EVT_WDF_REQUEST_CANCEL SerialCancelCurrentWrite;
+EVT_WDF_REQUEST_CANCEL SerialCancelCurrentXoff;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGiveWriteToIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGiveXoffToIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabWriteFromIsr;
+EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabXoffFromIsr;
+
+
+VOID
+SerialEvtIoWrite(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t Length
+ )
+
+/*++
+
+Routine Description:
+
+ This is the dispatch routine for write. It validates the parameters
+ for the write request and if all is ok then it places the request
+ on the work queue.
+
+Arguments:
+
+ Queue - Handle to the framework queue object that is associated
+ with the I/O request.
+ Request - Pointer to the WDFREQUEST for the current request
+
+ Length - Length of the IO operation
+ The default property of the queue is to not dispatch
+ zero lenght read & write requests to the driver and
+ complete is with status success. So we will never get
+ a zero length request.
+
+Return Value:
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION extension;
+ NTSTATUS status;
+ WDFDEVICE hDevice;
+ WDF_REQUEST_PARAMETERS params;
+ PREQUEST_CONTEXT reqContext;
+ size_t bufLen;
+
+ hDevice = WdfIoQueueGetDevice(Queue);
+ extension = SerialGetDeviceExtension(hDevice);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE,
+ ">SerialEvtIoWrite(%p, 0x%I64x)\n", Request, Length);
+
+ if (SerialCompleteIfError(extension, Request) != STATUS_SUCCESS) {
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialEvtIoWrite (2) %d\n", STATUS_CANCELLED);
+ return;
+
+ }
+
+
+ WDF_REQUEST_PARAMETERS_INIT(¶ms);
+
+ WdfRequestGetParameters(
+ Request,
+ ¶ms
+ );
+
+ //
+ // Initialize the scratch area of the request.
+ //
+ reqContext = SerialGetRequestContext(Request);
+ reqContext->MajorFunction = params.Type;
+ reqContext->Length = (ULONG) Length;
+
+ status = WdfRequestRetrieveInputBuffer (Request, Length, &reqContext->SystemBuffer, &bufLen);
+
+ if (!NT_SUCCESS (status)) {
+
+ SerialCompleteRequest(Request , status, 0);
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialEvtIoWrite (4) %X\n", status);
+ return;
+ }
+
+ SerialStartOrQueue(extension, Request, extension->WriteQueue,
+ &extension->CurrentWriteRequest,
+ SerialStartWrite);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialEvtIoWrite (5) %X\n", status);
+
+ return ;
+
+}
+
+VOID
+SerialStartWrite(
+ IN PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to start off any write. It initializes
+ the Iostatus fields of the request. It will set up any timers
+ that are used to control the write.
+
+Arguments:
+
+ Extension - Points to the serial device extension
+
+Return Value:
+
+--*/
+
+{
+
+ LARGE_INTEGER TotalTime;
+ BOOLEAN UseATimer;
+ SERIAL_TIMEOUTS Timeouts;
+ PREQUEST_CONTEXT reqContext;
+ PREQUEST_CONTEXT reqContextXoff;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE,
+ ">SerialStartWrite(%p)\n", Extension);
+
+ TotalTime.QuadPart = 0;
+
+ do {
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest);
+
+ //
+ // If there is an xoff counter then complete it.
+ //
+
+ //
+ // We see if there is a actually an Xoff counter request.
+ //
+ // If there is, we put the write request back on the head
+ // of the write list. We then complete the xoff counter.
+ // The xoff counter completing code will actually make the
+ // xoff counter back into the current write request, and
+ // in the course of completing the xoff (which is now
+ // the current write) we will restart this request.
+ //
+
+ if (Extension->CurrentXoffRequest) {
+
+ reqContextXoff =
+ SerialGetRequestContext(Extension->CurrentXoffRequest);
+
+ if (SERIAL_REFERENCE_COUNT(reqContextXoff)) {
+
+ //
+ // The reference count is non-zero. This implies that
+ // the xoff request has not made it through the completion
+ // path yet. We will increment the reference count
+ // and attempt to complete it ourseleves.
+ //
+
+ SERIAL_SET_REFERENCE(
+ reqContextXoff,
+ SERIAL_REF_XOFF_REF
+ );
+
+ reqContextXoff->Information = 0;
+
+ //
+ // The following call will actually release the
+ // cancel spin lock.
+ //
+
+ SerialTryToCompleteCurrent(
+ Extension,
+ SerialGrabXoffFromIsr,
+ STATUS_SERIAL_MORE_WRITES,
+ &Extension->CurrentXoffRequest,
+ NULL,
+ NULL,
+ Extension->XoffCountTimer,
+ NULL,
+ NULL,
+ SERIAL_REF_XOFF_REF
+ );
+
+ } else {
+
+ //
+ // The request is well on its way to being finished.
+ // We can let the regular completion code do the
+ // work. Just release the spin lock.
+ //
+
+ }
+
+ }
+
+ UseATimer = FALSE;
+
+ //
+ // Calculate the timeout value needed for the
+ // request. Note that the values stored in the
+ // timeout record are in milliseconds. Note that
+ // if the timeout values are zero then we won't start
+ // the timer.
+ //
+
+ Timeouts = Extension->Timeouts;
+
+ if (Timeouts.WriteTotalTimeoutConstant ||
+ Timeouts.WriteTotalTimeoutMultiplier) {
+
+ UseATimer = TRUE;
+
+ //
+ // We have some timer values to calculate.
+ //
+ // Take care, we might have an xoff counter masquerading
+ // as a write.
+ //
+
+ TotalTime.QuadPart =
+ ((LONGLONG)((UInt32x32To64(
+ (reqContext->MajorFunction == IRP_MJ_WRITE)?
+ (reqContext->Length) : (1),
+ Timeouts.WriteTotalTimeoutMultiplier
+ )
+ + Timeouts.WriteTotalTimeoutConstant)))
+ * -10000;
+
+ }
+
+ //
+ // The request may be going to the isr shortly. Now
+ // is a good time to initialize its reference counts.
+ //
+
+ SERIAL_INIT_REFERENCE(reqContext);
+
+ //
+ // We give the request to to the isr to write out.
+ // We set a cancel routine that knows how to
+ // grab the current write away from the isr.
+ //
+ SerialSetCancelRoutine(Extension->CurrentWriteRequest,
+ SerialCancelCurrentWrite);
+
+ if (UseATimer) {
+ BOOLEAN result;
+
+ result = SerialSetTimer(
+ Extension->WriteRequestTotalTimer,
+ TotalTime
+ );
+ if(result == FALSE) {
+ //
+ // This timer now has a reference to the request.
+ //
+
+ SERIAL_SET_REFERENCE( reqContext, SERIAL_REF_TOTAL_TIMER );
+ }
+ }
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGiveWriteToIsr,
+ Extension
+ );
+
+ } WHILE (FALSE);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialStartWrite \n");
+
+ return;
+}
+
+
+VOID
+SerialGetNextWrite(
+ IN WDFREQUEST *CurrentOpRequest,
+ IN WDFQUEUE QueueToProcess,
+ IN WDFREQUEST *NewRequest,
+ IN BOOLEAN CompleteCurrent,
+ PSERIAL_DEVICE_EXTENSION Extension
+ )
+
+/*++
+
+Routine Description:
+
+ This routine completes the old write as well as getting
+ a pointer to the next write.
+
+ The reason that we have have pointers to the current write
+ queue as well as the current write request is so that this
+ routine may be used in the common completion code for
+ read and write.
+
+Arguments:
+
+ CurrentOpRequest - Pointer to the pointer that points to the
+ current write request.
+
+ QueueToProcess - Pointer to the write queue.
+
+ NewRequest - A pointer to a pointer to the request that will be the
+ current request. Note that this could end up pointing
+ to a null pointer. This does NOT necessaryly mean
+ that there is no current write. What could occur
+ is that while the cancel lock is held the write
+ queue ended up being empty, but as soon as we release
+ the cancel spin lock a new request came in from
+ SerialStartWrite.
+
+ CompleteCurrent - Flag indicates whether the CurrentOpRequest should
+ be completed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PREQUEST_CONTEXT reqContext;
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialGetNextWrite\n");
+
+
+ do {
+
+ reqContext = SerialGetRequestContext(*CurrentOpRequest);
+
+ //
+ // We could be completing a flush.
+ //
+
+ if (reqContext->MajorFunction == IRP_MJ_WRITE) {
+
+ ASSERT(Extension->TotalCharsQueued >= reqContext->Length);
+
+ Extension->TotalCharsQueued -= reqContext->Length;
+
+ } else if (reqContext->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
+
+ WDFREQUEST request = *CurrentOpRequest;
+ PSERIAL_XOFF_COUNTER Xc;
+
+ Xc = reqContext->SystemBuffer;
+
+ //
+ // We should never have a xoff counter when we
+ // get to this point.
+ //
+
+ ASSERT(!Extension->CurrentXoffRequest);
+
+ //
+ // This could only be a xoff counter masquerading as
+ // a write request.
+ //
+
+ Extension->TotalCharsQueued--;
+
+ //
+ // Check to see of the xoff request has been set with success.
+ // This means that the write completed normally. If that
+ // is the case, and it hasn't been set to cancel in the
+ // meanwhile, then go on and make it the CurrentXoffRequest.
+ //
+
+ if (reqContext->Status != STATUS_SUCCESS || reqContext->Cancelled) {
+
+ // TODO: I see Xoff request getting abandoned due to loss of
+ // Total timer - SERIAL_REF_TOTAL_TIMER
+ //
+ // Oh well, we can just finish it off.
+ //
+ NOTHING;
+
+ } else {
+
+ SerialSetCancelRoutine(request, SerialCancelCurrentXoff);
+
+ //
+ // We don't want to complete the current request now. This
+ // will now get completed by the Xoff counter code.
+ //
+
+ CompleteCurrent = FALSE;
+
+ //
+ // Give the counter to the isr.
+ //
+
+ Extension->CurrentXoffRequest = request;
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialGiveXoffToIsr,
+ Extension
+ );
+
+ //
+ // Start the timer for the counter and increment
+ // the reference count since the timer has a
+ // reference to the request.
+ //
+
+ if (Xc->Timeout) {
+
+ LARGE_INTEGER delta;
+ BOOLEAN result;
+
+ delta.QuadPart = -((LONGLONG)UInt32x32To64(
+ 1000,
+ Xc->Timeout
+ ));
+
+ result = SerialSetTimer(
+ Extension->XoffCountTimer,
+ delta
+
+ );
+ if(result == FALSE) {
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_TOTAL_TIMER
+ );
+ }
+ }
+
+ }
+
+
+ }
+
+ //
+ // Note that the following call will (probably) also cause
+ // the current request to be completed.
+ //
+
+ SerialGetNextRequest(
+ CurrentOpRequest,
+ QueueToProcess,
+ NewRequest,
+ CompleteCurrent,
+ Extension
+ );
+
+ if (!*NewRequest) {
+
+
+ WdfInterruptSynchronize(
+ Extension->WdfInterrupt,
+ SerialProcessEmptyTransmit,
+ Extension
+ );
+
+ break;
+
+ } else if (SerialGetRequestContext(*NewRequest)->MajorFunction
+ == IRP_MJ_FLUSH_BUFFERS) {
+
+ //
+ // If we encounter a flush request we just want to get
+ // the next request and complete the flush.
+ //
+ // Note that if NewRequest is non-null then it is also
+ // equal to CurrentWriteRequest.
+ //
+
+
+ ASSERT((*NewRequest) == (*CurrentOpRequest));
+ SerialGetRequestContext(*NewRequest)->Status = STATUS_SUCCESS;
+
+ } else {
+
+ break;
+
+ }
+
+ } WHILE (TRUE);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialGetNextWrite\n");
+
+}
+
+
+VOID
+SerialCompleteWrite(
+ IN WDFDPC Dpc
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is merely used to complete any write. It
+ assumes that the status and the information fields of
+ the request are already correctly filled in.
+
+Arguments:
+
+ Dpc - Not Used.
+
+ DeferredContext - Really points to the device extension.
+
+ SystemContext1 - Not Used.
+
+ SystemContext2 - Not Used.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialCompleteWrite(%p)\n",
+ Extension);
+
+
+ SerialTryToCompleteCurrent(Extension, NULL, STATUS_SUCCESS,
+ &Extension->CurrentWriteRequest,
+ Extension->WriteQueue, NULL,
+ Extension->WriteRequestTotalTimer,
+ SerialStartWrite, SerialGetNextWrite,
+ SERIAL_REF_ISR);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialCompleteWrite\n");
+
+}
+
+
+BOOLEAN
+SerialProcessEmptyTransmit(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to determine if conditions are appropriate
+ to satisfy a wait for transmit empty event, and if so to complete
+ the request that is waiting for that event. It also call the code
+ that checks to see if we should lower the RTS line if we are
+ doing transmit toggling.
+
+ NOTE: This routine is called by WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that it is called with the cancel
+ spinlock held.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ if (Extension->IsrWaitMask && (Extension->IsrWaitMask & SERIAL_EV_TXEMPTY) &&
+ Extension->EmptiedTransmit && (!Extension->TransmitImmediate) &&
+ (!Extension->CurrentWriteRequest) && IsQueueEmpty(Extension->WriteQueue)) {
+
+ Extension->HistoryMask |= SERIAL_EV_TXEMPTY;
+ if (Extension->IrpMaskLocation) {
+
+ *Extension->IrpMaskLocation = Extension->HistoryMask;
+ Extension->IrpMaskLocation = NULL;
+ Extension->HistoryMask = 0;
+
+ SerialGetRequestContext(Extension->CurrentWaitRequest)->Information = sizeof(ULONG);
+ SerialInsertQueueDpc(
+ Extension->CommWaitDpc
+ );
+
+ }
+
+ Extension->CountOfTryingToLowerRTS++;
+ SerialPerhapsLowerRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ return FALSE;
+
+}
+
+
+BOOLEAN
+SerialGiveWriteToIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ Try to start off the write by slipping it in behind
+ a transmit immediate char, or if that isn't available
+ and the transmit holding register is empty, "tickle"
+ the UART into interrupting with a transmit buffer
+ empty.
+
+ NOTE: This routine is called by WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that it is called with the
+ cancel spin lock held.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ //
+ // The current stack location. This contains all of the
+ // information we need to process this particular request.
+ //
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest);
+
+ //
+ // We might have a xoff counter request masquerading as a
+ // write. The length of these requests will always be one
+ // and we can get a pointer to the actual character from
+ // the data supplied by the user.
+ //
+
+ if (reqContext->MajorFunction == IRP_MJ_WRITE) {
+
+ Extension->WriteLength = reqContext->Length;
+ Extension->WriteCurrentChar = reqContext->SystemBuffer;
+
+ } else {
+
+ Extension->WriteLength = 1;
+ Extension->WriteCurrentChar =
+ ((PUCHAR)reqContext->SystemBuffer) +
+ FIELD_OFFSET(
+ SERIAL_XOFF_COUNTER,
+ XoffChar
+ );
+
+ }
+
+ //
+ // The isr now has a reference to the request.
+ //
+
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ //
+ // Check first to see if an immediate char is transmitting.
+ // If it is then we'll just slip in behind it when its
+ // done.
+ //
+
+ if (!Extension->TransmitImmediate) {
+
+ //
+ // If there is no immediate char transmitting then we
+ // will "re-enable" the transmit holding register empty
+ // interrupt. The 8250 family of devices will always
+ // signal a transmit holding register empty interrupt
+ // *ANY* time this bit is set to one. By doing things
+ // this way we can simply use the normal interrupt code
+ // to start off this write.
+ //
+ // We've been keeping track of whether the transmit holding
+ // register is empty so it we only need to do this
+ // if the register is empty.
+ //
+
+ if (Extension->HoldingEmpty) {
+
+ DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+ ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller);
+
+ }
+
+ }
+
+ //
+ // The rts line may already be up from previous writes,
+ // however, it won't take much additional time to turn
+ // on the RTS line if we are doing transmit toggling.
+ //
+
+ if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) ==
+ SERIAL_TRANSMIT_TOGGLE) {
+
+ SerialSetRTS(Extension->WdfInterrupt, Extension);
+
+ }
+
+ return FALSE;
+
+}
+
+
+VOID
+SerialCancelCurrentWrite(
+ IN WDFREQUEST Request
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to cancel the current write.
+
+Arguments:
+
+ Device - Wdf handle for the device
+
+ Request - Pointer to the WDFREQUEST to be canceled.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension;
+ WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request));
+
+ UNREFERENCED_PARAMETER(Request);
+
+ Extension = SerialGetDeviceExtension(device);
+
+ SerialTryToCompleteCurrent(
+ Extension,
+ SerialGrabWriteFromIsr,
+ STATUS_CANCELLED,
+ &Extension->CurrentWriteRequest,
+ Extension->WriteQueue,
+ NULL,
+ Extension->WriteRequestTotalTimer,
+ SerialStartWrite,
+ SerialGetNextWrite,
+ SERIAL_REF_CANCEL
+ );
+
+}
+
+
+VOID
+SerialWriteTimeout(
+ IN WDFTIMER Timer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will try to timeout the current write.
+
+Arguments:
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialWriteTimeout(%p)\n",
+ Extension);
+
+ SerialTryToCompleteCurrent(Extension, SerialGrabWriteFromIsr,
+ STATUS_TIMEOUT, &Extension->CurrentWriteRequest,
+ Extension->WriteQueue, NULL,
+ Extension->WriteRequestTotalTimer,
+ SerialStartWrite, SerialGetNextWrite,
+ SERIAL_REF_TOTAL_TIMER);
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialWriteTimeout\n");
+}
+
+
+BOOLEAN
+SerialGrabWriteFromIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+
+ This routine is used to grab the current request, which could be timing
+ out or canceling, from the ISR
+
+ NOTE: This routine is being called from WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that the cancel spin lock is held
+ when this routine is called.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always false.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest);
+
+ //
+ // Check if the write length is non-zero. If it is non-zero
+ // then the ISR still owns the request. We calculate the the number
+ // of characters written and update the information field of the
+ // request with the characters written. We then clear the write length
+ // the isr sees.
+ //
+
+ if (Extension->WriteLength) {
+
+ //
+ // We could have an xoff counter masquerading as a
+ // write request. If so, don't update the write length.
+ //
+
+ if (reqContext->MajorFunction == IRP_MJ_WRITE) {
+
+ reqContext->Information = reqContext->Length -Extension->WriteLength;
+
+ } else {
+
+ reqContext->Information = 0;
+
+ }
+
+ //
+ // Since the isr no longer references this request, we can
+ // decrement it's reference count.
+ //
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ Extension->WriteLength = 0;
+
+ }
+
+ return FALSE;
+
+}
+
+
+BOOLEAN
+SerialGrabXoffFromIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to grab an xoff counter request from the
+ isr when it is no longer masquerading as a write request. This
+ routine is called by the cancel and timeout code for the
+ xoff counter ioctl.
+
+
+ NOTE: This routine is being called from WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that the cancel spin lock is held
+ when this routine is called.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ Always false.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+
+ PREQUEST_CONTEXT reqContext;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentXoffRequest);
+
+ if (Extension->CountSinceXoff) {
+
+ //
+ // This is only non-zero when there actually is a Xoff ioctl
+ // counting down.
+ //
+
+ Extension->CountSinceXoff = 0;
+
+ //
+ // We decrement the count since the isr no longer owns
+ // the request.
+ //
+
+ SERIAL_CLEAR_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ }
+
+ return FALSE;
+
+}
+
+
+VOID
+SerialCompleteXoff(
+ IN WDFDPC Dpc
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is merely used to truely complete an xoff counter request. It
+ assumes that the status and the information fields of the request are
+ already correctly filled in.
+
+Arguments:
+
+ Dpc - Not Used.
+
+ DeferredContext - Really points to the device extension.
+
+ SystemContext1 - Not Used.
+
+ SystemContext2 - Not Used.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialCompleteXoff(%p)\n",
+ Extension);
+
+
+ SerialTryToCompleteCurrent(Extension, NULL, STATUS_SUCCESS,
+ &Extension->CurrentXoffRequest, NULL, NULL,
+ Extension->XoffCountTimer, NULL, NULL,
+ SERIAL_REF_ISR);
+
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialCompleteXoff\n");
+
+}
+
+
+VOID
+SerialTimeoutXoff(
+ IN WDFTIMER Timer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is merely used to truely complete an xoff counter request,
+ if its timer has run out.
+
+Arguments:
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = NULL;
+
+ Extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer));
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialTimeoutXoff(%p)\n", Extension);
+
+ SerialTryToCompleteCurrent(Extension, SerialGrabXoffFromIsr,
+ STATUS_SERIAL_COUNTER_TIMEOUT,
+ &Extension->CurrentXoffRequest, NULL, NULL, NULL,
+ NULL, NULL, SERIAL_REF_TOTAL_TIMER);
+
+ SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "<SerialTimeoutXoff\n");
+}
+
+
+VOID
+SerialCancelCurrentXoff(
+ IN WDFREQUEST Request
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to cancel the current write.
+
+Arguments:
+
+ Device - Wdf handle for the device
+
+ Request - Pointer to the WDFREQUEST to be canceled.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension;
+ WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request));
+
+ UNREFERENCED_PARAMETER(Request);
+
+ Extension = SerialGetDeviceExtension(device);
+
+ SerialTryToCompleteCurrent(
+ Extension,
+ SerialGrabXoffFromIsr,
+ STATUS_CANCELLED,
+ &Extension->CurrentXoffRequest,
+ NULL,
+ NULL,
+ Extension->XoffCountTimer,
+ NULL,
+ NULL,
+ SERIAL_REF_CANCEL
+ );
+
+}
+
+
+BOOLEAN
+SerialGiveXoffToIsr(
+ IN WDFINTERRUPT Interrupt,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+
+ This routine starts off the xoff counter. It merely
+ has to set the xoff count and increment the reference
+ count to denote that the isr has a reference to the request.
+
+ NOTE: This routine is called by WdfInterruptSynchronize.
+
+ NOTE: This routine assumes that it is called with the
+ cancel spin lock held.
+
+Arguments:
+
+ Context - Really a pointer to the device extension.
+
+Return Value:
+
+ This routine always returns FALSE.
+
+--*/
+
+{
+
+ PSERIAL_DEVICE_EXTENSION Extension = Context;
+ PREQUEST_CONTEXT reqContext;
+ PSERIAL_XOFF_COUNTER Xc = NULL;
+
+ UNREFERENCED_PARAMETER(Interrupt);
+
+ reqContext = SerialGetRequestContext(Extension->CurrentXoffRequest);
+ Xc = reqContext->SystemBuffer;
+
+ //
+ // The current stack location. This contains all of the
+ // information we need to process this particular request.
+ //
+
+ ASSERT(Extension->CurrentXoffRequest);
+ Extension->CountSinceXoff = Xc->Counter;
+
+ //
+ // The isr now has a reference to the request.
+ //
+
+ SERIAL_SET_REFERENCE(
+ reqContext,
+ SERIAL_REF_ISR
+ );
+
+ return FALSE;
+
+}
+
+
diff --git a/tests/projects/windows/driver/kmdf/serial/xmake.lua b/tests/projects/windows/driver/kmdf/serial/xmake.lua new file mode 100644 index 000000000..53d59c650 --- /dev/null +++ b/tests/projects/windows/driver/kmdf/serial/xmake.lua @@ -0,0 +1,9 @@ +add_rules("mode.debug", "mode.release") + +target("wdfserial") + add_rules("wdk.env.kmdf", "wdk.driver") + add_values("wdk.tracewpp.flags", "-func:SerialDbgPrintEx(LEVEL,FLAGS,MSG,...)") + add_values("wdk.mc.header", "serlog.h") + add_files("*.c", {rule = "wdk.tracewpp"}) + add_files("*.mc", "*.rc", "*.inx") + diff --git a/tests/projects/windows/driver/umdf/echo/driver/device.c b/tests/projects/windows/driver/umdf/echo/driver/device.c new file mode 100644 index 000000000..bd522565f --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/driver/device.c @@ -0,0 +1,202 @@ +/*++
+
+Copyright (c) 1990-2000 Microsoft Corporation
+
+Module Name:
+
+ device.c - Device handling events for example driver.
+
+Abstract:
+
+ This is a C version of a very simple sample driver that illustrates
+ how to use the driver framework and demonstrates best practices.
+
+--*/
+
+#include "driver.h"
+
+NTSTATUS
+EchoDeviceCreate(
+ PWDFDEVICE_INIT DeviceInit
+ )
+/*++
+
+Routine Description:
+
+ Worker routine called to create a device and its software resources.
+
+Arguments:
+
+ DeviceInit - Pointer to an opaque init structure. Memory for this
+ structure will be freed by the framework when the WdfDeviceCreate
+ succeeds. So don't access the structure after that point.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ WDF_OBJECT_ATTRIBUTES deviceAttributes;
+ PDEVICE_CONTEXT deviceContext;
+ WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+ WDFDEVICE device;
+ NTSTATUS status;
+
+ WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+
+ //
+ // Register pnp/power callbacks so that we can start and stop the timer as the device
+ // gets started and stopped.
+ //
+ pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = EchoEvtDeviceSelfManagedIoStart;
+ pnpPowerCallbacks.EvtDeviceSelfManagedIoSuspend = EchoEvtDeviceSelfManagedIoSuspend;
+
+ #pragma prefast(suppress: 28024, "Function used for both Init and Restart Callbacks")
+ pnpPowerCallbacks.EvtDeviceSelfManagedIoRestart = EchoEvtDeviceSelfManagedIoStart;
+
+ //
+ // Register the PnP and power callbacks. Power policy related callbacks will be registered
+ // later in SotwareInit.
+ //
+ WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
+
+ status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
+
+ if (NT_SUCCESS(status)) {
+ //
+ // Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
+ // inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
+ // device.h header file. This function will do the type checking and return
+ // the device context. If you pass a wrong object handle
+ // it will return NULL and assert if run under framework verifier mode.
+ //
+ deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
+ deviceContext->PrivateDeviceData = 0;
+
+ //
+ // Create a device interface so that application can find and talk
+ // to us.
+ //
+ status = WdfDeviceCreateDeviceInterface(
+ device,
+ &GUID_DEVINTERFACE_ECHO,
+ NULL // ReferenceString
+ );
+
+ if (NT_SUCCESS(status)) {
+ //
+ // Initialize the I/O Package and any Queues
+ //
+ status = EchoQueueInitialize(device);
+ }
+ }
+
+ return status;
+}
+
+
+NTSTATUS
+EchoEvtDeviceSelfManagedIoStart(
+ IN WDFDEVICE Device
+ )
+/*++
+
+Routine Description:
+
+ This event is called by the Framework when the device is started
+ or restarted after a suspend operation.
+
+ This function is not marked pageable because this function is in the
+ device power up path. When a function is marked pagable and the code
+ section is paged out, it will generate a page fault which could impact
+ the fast resume behavior because the client driver will have to wait
+ until the system drivers can service this page fault.
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+Return Value:
+
+ NTSTATUS - Failures will result in the device stack being torn down.
+
+--*/
+{
+ PQUEUE_CONTEXT queueContext = QueueGetContext(WdfDeviceGetDefaultQueue(Device));
+ LARGE_INTEGER DueTime;
+
+ KdPrint(("--> EchoEvtDeviceSelfManagedIoInit\n"));
+
+ //
+ // Restart the queue and the periodic timer. We stopped them before going
+ // into low power state.
+ //
+ WdfIoQueueStart(WdfDeviceGetDefaultQueue(Device));
+
+ DueTime.QuadPart = WDF_REL_TIMEOUT_IN_MS(100);
+
+ WdfTimerStart(queueContext->Timer, DueTime.QuadPart);
+
+ KdPrint(( "<-- EchoEvtDeviceSelfManagedIoInit\n"));
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+EchoEvtDeviceSelfManagedIoSuspend(
+ IN WDFDEVICE Device
+ )
+/*++
+
+Routine Description:
+
+ This event is called by the Framework when the device is stopped
+ for resource rebalance or suspended when the system is entering
+ Sx state.
+
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+Return Value:
+
+ NTSTATUS - The driver is not allowed to fail this function. If it does, the
+ device stack will be torn down.
+
+--*/
+{
+ PQUEUE_CONTEXT queueContext = QueueGetContext(WdfDeviceGetDefaultQueue(Device));
+
+ PAGED_CODE();
+
+ KdPrint(("--> EchoEvtDeviceSelfManagedIoSuspend\n"));
+
+ //
+ // Before we stop the timer we should make sure there are no outstanding
+ // i/o. We need to do that because framework cannot suspend the device
+ // if there are requests owned by the driver. There are two ways to solve
+ // this issue: 1) We can wait for the outstanding I/O to be complete by the
+ // periodic timer 2) Register EvtIoStop callback on the queue and acknowledge
+ // the request to inform the framework that it's okay to suspend the device
+ // with outstanding I/O. In this sample we will use the 1st approach
+ // because it's pretty easy to do. We will restart the queue when the
+ // device is restarted.
+ //
+ WdfIoQueueStopSynchronously(WdfDeviceGetDefaultQueue(Device));
+
+ //
+ // Stop the watchdog timer and wait for DPC to run to completion if it's already fired.
+ //
+ WdfTimerStop(queueContext->Timer, TRUE);
+
+ KdPrint(( "<-- EchoEvtDeviceSelfManagedIoSuspend\n"));
+
+ return STATUS_SUCCESS;
+}
+
+
+
diff --git a/tests/projects/windows/driver/umdf/echo/driver/device.h b/tests/projects/windows/driver/umdf/echo/driver/device.h new file mode 100644 index 000000000..1e2c1f28e --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/driver/device.h @@ -0,0 +1,48 @@ +/*++
+
+Copyright (c) 1990-2000 Microsoft Corporation
+
+Module Name:
+
+ device.h
+
+Abstract:
+
+ This is a C version of a very simple sample driver that illustrates
+ how to use the driver framework and demonstrates best practices.
+
+--*/
+
+#include "public.h"
+
+//
+// The device context performs the same job as
+// a WDM device extension in the driver frameworks
+//
+typedef struct _DEVICE_CONTEXT
+{
+ ULONG PrivateDeviceData; // just a placeholder
+
+} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
+
+//
+// This macro will generate an inline function called WdfObjectGet_DEVICE_CONTEXT
+// which will be used to get a pointer to the device context memory
+// in a type safe manner.
+//
+WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)
+
+//
+// Function to initialize the device and its callbacks
+//
+NTSTATUS
+EchoDeviceCreate(
+ PWDFDEVICE_INIT DeviceInit
+ );
+
+//
+// Device events
+//
+EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT EchoEvtDeviceSelfManagedIoStart;
+EVT_WDF_DEVICE_SELF_MANAGED_IO_SUSPEND EchoEvtDeviceSelfManagedIoSuspend;
+
diff --git a/tests/projects/windows/driver/umdf/echo/driver/driver.c b/tests/projects/windows/driver/umdf/echo/driver/driver.c new file mode 100644 index 000000000..2835aa1dd --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/driver/driver.c @@ -0,0 +1,192 @@ +/*++
+
+Copyright (c) 1990-2000 Microsoft Corporation
+
+Module Name:
+
+ driver.c
+
+Abstract:
+
+ This driver demonstrates use of a default I/O Queue, its
+ request start events, cancellation event, and a synchronized DPC.
+
+ To demonstrate asynchronous operation, the I/O requests are not completed
+ immediately, but stored in the drivers private data structure, and a timer
+ will complete it next time the Timer callback runs.
+
+ During the time the request is waiting for the timer callback to run, it is
+ made cancellable by the call WdfRequestMarkCancelable. This
+ allows the test program to cancel the request and exit instantly.
+
+ This rather complicated set of events is designed to demonstrate
+ the driver frameworks synchronization of access to a device driver
+ data structure, and a pointer which can be a proxy for device hardware
+ registers or resources.
+
+ This common data structure, or resource is accessed by new request
+ events arriving, the Timer callback that completes it, and cancel processing.
+
+ Notice the lack of specific lock/unlock operations.
+
+ Even though this example utilizes a serial queue, a parallel queue
+ would not need any additional explicit synchronization, just a
+ strategy for managing multiple requests outstanding.
+
+--*/
+
+#include "driver.h"
+
+NTSTATUS
+DriverEntry(
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+ )
+/*++
+
+Routine Description:
+ DriverEntry initializes the driver and is the first routine called by the
+ system after the driver is loaded. DriverEntry specifies the other entry
+ points in the function driver, such as EvtDevice and DriverUnload.
+
+Parameters Description:
+
+ DriverObject - represents the instance of the function driver that is loaded
+ into memory. DriverEntry must initialize members of DriverObject before it
+ returns to the caller. DriverObject is allocated by the system before the
+ driver is loaded, and it is released by the system after the system unloads
+ the function driver from memory.
+
+ RegistryPath - represents the driver specific path in the Registry.
+ The function driver can use the path to store driver related data between
+ reboots. The path does not store hardware instance specific data.
+
+Return Value:
+
+ STATUS_SUCCESS if successful,
+ STATUS_UNSUCCESSFUL otherwise.
+
+--*/
+{
+ WDF_DRIVER_CONFIG config;
+ NTSTATUS status;
+
+ WDF_DRIVER_CONFIG_INIT(&config,
+ EchoEvtDeviceAdd
+ );
+
+ status = WdfDriverCreate(DriverObject,
+ RegistryPath,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &config,
+ WDF_NO_HANDLE);
+ if (!NT_SUCCESS(status)) {
+ KdPrint(("Error: WdfDriverCreate failed 0x%x\n", status));
+ return status;
+ }
+
+#if DBG
+ EchoPrintDriverVersion();
+#endif
+
+ return status;
+}
+
+NTSTATUS
+EchoEvtDeviceAdd(
+ IN WDFDRIVER Driver,
+ IN PWDFDEVICE_INIT DeviceInit
+ )
+/*++
+Routine Description:
+
+ EvtDeviceAdd is called by the framework in response to AddDevice
+ call from the PnP manager. We create and initialize a device object to
+ represent a new instance of the device.
+
+Arguments:
+
+ Driver - Handle to a framework driver object created in DriverEntry
+
+ DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ NTSTATUS status;
+
+ UNREFERENCED_PARAMETER(Driver);
+
+ KdPrint(("Enter EchoEvtDeviceAdd\n"));
+
+ status = EchoDeviceCreate(DeviceInit);
+
+ return status;
+}
+
+NTSTATUS
+EchoPrintDriverVersion(
+ )
+/*++
+Routine Description:
+
+ This routine shows how to retrieve framework version string and
+ also how to find out to which version of framework library the
+ client driver is bound to.
+
+Arguments:
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ NTSTATUS status;
+ WDFSTRING string;
+ UNICODE_STRING us;
+ WDF_DRIVER_VERSION_AVAILABLE_PARAMS ver;
+
+ //
+ // 1) Retreive version string and print that in the debugger.
+ //
+ status = WdfStringCreate(NULL, WDF_NO_OBJECT_ATTRIBUTES, &string);
+ if (!NT_SUCCESS(status)) {
+ KdPrint(("Error: WdfStringCreate failed 0x%x\n", status));
+ return status;
+ }
+
+ status = WdfDriverRetrieveVersionString(WdfGetDriver(), string);
+ if (!NT_SUCCESS(status)) {
+ //
+ // No need to worry about delete the string object because
+ // by default it's parented to the driver and it will be
+ // deleted when the driverobject is deleted when the DriverEntry
+ // returns a failure status.
+ //
+ KdPrint(("Error: WdfDriverRetrieveVersionString failed 0x%x\n", status));
+ return status;
+ }
+
+ WdfStringGetUnicodeString(string, &us);
+ KdPrint(("Echo Sample %wZ\n", &us));
+
+ WdfObjectDelete(string);
+ string = NULL; // To avoid referencing a deleted object.
+
+ //
+ // 2) Find out to which version of framework this driver is bound to.
+ //
+ WDF_DRIVER_VERSION_AVAILABLE_PARAMS_INIT(&ver, 1, 0);
+ if (WdfDriverIsVersionAvailable(WdfGetDriver(), &ver) == TRUE) {
+ KdPrint(("Yes, framework version is 1.0\n"));
+ }else {
+ KdPrint(("No, framework verison is not 1.0\n"));
+ }
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/tests/projects/windows/driver/umdf/echo/driver/driver.h b/tests/projects/windows/driver/umdf/echo/driver/driver.h new file mode 100644 index 000000000..6539d6cf5 --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/driver/driver.h @@ -0,0 +1,46 @@ +/*++
+
+Copyright (c) 1990-2000 Microsoft Corporation
+
+Module Name:
+
+ driver.h
+
+Abstract:
+
+ This is a C version of a very simple sample driver that illustrates
+ how to use the driver framework and demonstrates best practices.
+
+--*/
+
+#define INITGUID
+
+#include <windows.h>
+#include <wdf.h>
+#include "device.h"
+#include "queue.h"
+
+#ifndef ASSERT
+#if DBG
+#define ASSERT( exp ) \
+ ((!(exp)) ? \
+ (KdPrint(( "\n*** Assertion failed: " #exp "\n\n")), \
+ DebugBreak(), \
+ FALSE) : \
+ TRUE)
+#else
+#define ASSERT( exp )
+#endif // DBG
+#endif // ASSERT
+
+//
+// WDFDRIVER Events
+//
+
+DRIVER_INITIALIZE DriverEntry;
+EVT_WDF_DRIVER_DEVICE_ADD EchoEvtDeviceAdd;
+
+NTSTATUS
+EchoPrintDriverVersion(
+ );
+
diff --git a/tests/projects/windows/driver/umdf/echo/driver/echoum.inx b/tests/projects/windows/driver/umdf/echo/driver/echoum.inx Binary files differnew file mode 100644 index 000000000..cae8b45fe --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/driver/echoum.inx diff --git a/tests/projects/windows/driver/umdf/echo/driver/queue.c b/tests/projects/windows/driver/umdf/echo/driver/queue.c new file mode 100644 index 000000000..e0f3d7b6b --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/driver/queue.c @@ -0,0 +1,538 @@ +/*++
+
+Copyright (c) 1990-2000 Microsoft Corporation
+
+Module Name:
+
+ queue.c
+
+Abstract:
+
+ This is a C version of a very simple sample driver that illustrates
+ how to use the driver framework and demonstrates best practices.
+
+--*/
+
+#include "driver.h"
+
+NTSTATUS
+EchoQueueInitialize(
+ WDFDEVICE Device
+ )
+/*++
+
+Routine Description:
+
+
+ The I/O dispatch callbacks for the frameworks device object
+ are configured in this function.
+
+ A single default I/O Queue is configured for serial request
+ processing, and a driver context memory allocation is created
+ to hold our structure QUEUE_CONTEXT.
+
+ This memory may be used by the driver automatically synchronized
+ by the Queue's presentation lock.
+
+ The lifetime of this memory is tied to the lifetime of the I/O
+ Queue object, and we register an optional destructor callback
+ to release any private allocations, and/or resources.
+
+
+Arguments:
+
+ Device - Handle to a framework device object.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ WDFQUEUE queue;
+ NTSTATUS status;
+ PQUEUE_CONTEXT queueContext;
+ WDF_IO_QUEUE_CONFIG queueConfig;
+ WDF_OBJECT_ATTRIBUTES queueAttributes;
+
+ //
+ // Configure a default queue so that requests that are not
+ // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
+ // other queues get dispatched here.
+ //
+ WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
+ &queueConfig,
+ WdfIoQueueDispatchSequential
+ );
+
+ queueConfig.EvtIoRead = EchoEvtIoRead;
+ queueConfig.EvtIoWrite = EchoEvtIoWrite;
+
+ //
+ // Fill in a callback for destroy, and our QUEUE_CONTEXT size
+ //
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&queueAttributes, QUEUE_CONTEXT);
+
+ //
+ // Set synchronization scope on queue and have the timer to use queue as
+ // the parent object so that queue and timer callbacks are synchronized
+ // with the same lock.
+ //
+ queueAttributes.SynchronizationScope = WdfSynchronizationScopeQueue;
+
+ queueAttributes.EvtDestroyCallback = EchoEvtIoQueueContextDestroy;
+
+ status = WdfIoQueueCreate(
+ Device,
+ &queueConfig,
+ &queueAttributes,
+ &queue
+ );
+
+ if( !NT_SUCCESS(status) ) {
+ KdPrint(("WdfIoQueueCreate failed 0x%x\n",status));
+ return status;
+ }
+
+ // Get our Driver Context memory from the returned Queue handle
+ queueContext = QueueGetContext(queue);
+
+ queueContext->WriteMemory = NULL;
+ queueContext->Timer = NULL;
+
+ queueContext->CurrentRequest = NULL;
+ queueContext->CurrentStatus = STATUS_INVALID_DEVICE_REQUEST;
+
+ //
+ // Create the Queue timer
+ //
+ status = EchoTimerCreate(&queueContext->Timer, queue);
+ if (!NT_SUCCESS(status)) {
+ KdPrint(("Error creating timer 0x%x\n",status));
+ return status;
+ }
+
+ return status;
+}
+
+
+NTSTATUS
+EchoTimerCreate(
+ IN WDFTIMER* Timer,
+ IN WDFQUEUE Queue
+ )
+/*++
+
+Routine Description:
+
+ Subroutine to create timer. By associating the timerobject with
+ the queue, we are basically telling the framework to serialize the queue
+ callbacks with the timer callback. By doing so, we don't have to worry
+ about protecting queue-context structure from multiple threads accessing
+ it simultaneously.
+
+Arguments:
+
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ NTSTATUS Status;
+ WDF_TIMER_CONFIG timerConfig;
+ WDF_OBJECT_ATTRIBUTES timerAttributes;
+
+ //
+ // Create a non-periodic timer since WDF does not allow periodic timer
+ // at passive level, which is the level UMDF callbacks are invoked at.
+ // The workaround is to always restart the timer in the timer callback.
+ //
+ // WDF_TIMER_CONFIG_INIT sets AutomaticSerialization to TRUE by default.
+ //
+ WDF_TIMER_CONFIG_INIT(&timerConfig, EchoEvtTimerFunc);
+
+ WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
+ timerAttributes.ParentObject = Queue; // Synchronize with the I/O Queue
+ timerAttributes.ExecutionLevel = WdfExecutionLevelPassive;
+
+ Status = WdfTimerCreate(&timerConfig,
+ &timerAttributes,
+ Timer // Output handle
+ );
+
+ return Status;
+}
+
+
+
+VOID
+EchoEvtIoQueueContextDestroy(
+ WDFOBJECT Object
+)
+/*++
+
+Routine Description:
+
+ This is called when the Queue that our driver context memory
+ is associated with is destroyed.
+
+Arguments:
+
+ Context - Context that's being freed.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ PQUEUE_CONTEXT queueContext = QueueGetContext(Object);
+
+ //
+ // Release any resources pointed to in the queue context.
+ //
+ // The body of the queue context will be released after
+ // this callback handler returns
+ //
+
+ //
+ // If Queue context has an I/O buffer, release it
+ //
+ if( queueContext->WriteMemory != NULL ) {
+ WdfObjectDelete(queueContext->WriteMemory);
+ queueContext->WriteMemory = NULL;
+ }
+
+ return;
+}
+
+
+VOID
+EchoEvtRequestCancel(
+ IN WDFREQUEST Request
+ )
+/*++
+
+Routine Description:
+
+
+ Called when an I/O request is cancelled after the driver has marked
+ the request cancellable. This callback is automatically synchronized
+ with the I/O callbacks since we have chosen to use frameworks Device
+ level locking.
+
+Arguments:
+
+ Request - Request being cancelled.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ PQUEUE_CONTEXT queueContext = QueueGetContext(WdfRequestGetIoQueue(Request));
+
+ KdPrint(("EchoEvtRequestCancel called on Request 0x%p\n", Request));
+
+ //
+ // The following is race free by the callside or DPC side
+ // synchronizing completion by calling
+ // WdfRequestMarkCancelable(Queue, Request, FALSE) before
+ // completion and not calling WdfRequestComplete if the
+ // return status == STATUS_CANCELLED.
+ //
+ WdfRequestCompleteWithInformation(Request, STATUS_CANCELLED, 0L);
+
+ //
+ // This book keeping is synchronized by the common
+ // Queue presentation lock
+ //
+ ASSERT(queueContext->CurrentRequest == Request);
+ queueContext->CurrentRequest = NULL;
+
+ return;
+}
+
+VOID
+EchoEvtIoRead(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t Length
+ )
+/*++
+
+Routine Description:
+
+ This event is called when the framework receives IRP_MJ_READ request.
+ It will copy the content from the queue-context buffer to the request buffer.
+ If the driver hasn't received any write request earlier, the read returns zero.
+
+Arguments:
+
+ Queue - Handle to the framework queue object that is associated with the
+ I/O request.
+
+ Request - Handle to a framework request object.
+
+ Length - number of bytes to be read.
+ The default property of the queue is to not dispatch
+ zero lenght read & write requests to the driver and
+ complete is with status success. So we will never get
+ a zero length request.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ NTSTATUS Status;
+ PQUEUE_CONTEXT queueContext = QueueGetContext(Queue);
+ WDFMEMORY memory;
+ size_t writeMemoryLength;
+
+ _Analysis_assume_(Length > 0);
+
+ KdPrint(("EchoEvtIoRead Called! Queue 0x%p, Request 0x%p Length %d\n",
+ Queue,Request,Length));
+ //
+ // No data to read
+ //
+ if( (queueContext->WriteMemory == NULL) ) {
+ WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, (ULONG_PTR)0L);
+ return;
+ }
+
+ //
+ // Read what we have
+ //
+ WdfMemoryGetBuffer(queueContext->WriteMemory, &writeMemoryLength);
+ _Analysis_assume_(writeMemoryLength > 0);
+
+ if( writeMemoryLength < Length ) {
+ Length = writeMemoryLength;
+ }
+
+ //
+ // Get the request memory
+ //
+ Status = WdfRequestRetrieveOutputMemory(Request, &memory);
+ if( !NT_SUCCESS(Status) ) {
+ KdPrint(("EchoEvtIoRead Could not get request memory buffer 0x%x\n", Status));
+ WdfVerifierDbgBreakPoint();
+ WdfRequestCompleteWithInformation(Request, Status, 0L);
+ return;
+ }
+
+ // Copy the memory out
+ Status = WdfMemoryCopyFromBuffer( memory, // destination
+ 0, // offset into the destination memory
+ WdfMemoryGetBuffer(queueContext->WriteMemory, NULL),
+ Length );
+ if( !NT_SUCCESS(Status) ) {
+ KdPrint(("EchoEvtIoRead: WdfMemoryCopyFromBuffer failed 0x%x\n", Status));
+ WdfRequestComplete(Request, Status);
+ return;
+ }
+
+ // Set transfer information
+ WdfRequestSetInformation(Request, (ULONG_PTR)Length);
+
+ // Mark the request is cancelable
+ WdfRequestMarkCancelable(Request, EchoEvtRequestCancel);
+
+
+ // Defer the completion to another thread from the timer dpc
+ queueContext->CurrentRequest = Request;
+ queueContext->CurrentStatus = Status;
+
+ return;
+}
+
+VOID
+EchoEvtIoWrite(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t Length
+ )
+/*++
+
+Routine Description:
+
+ This event is invoked when the framework receives IRP_MJ_WRITE request.
+ This routine allocates memory buffer, copies the data from the request to it,
+ and stores the buffer pointer in the queue-context with the length variable
+ representing the buffers length. The actual completion of the request
+ is defered to the periodic timer dpc.
+
+Arguments:
+
+ Queue - Handle to the framework queue object that is associated with the
+ I/O request.
+
+ Request - Handle to a framework request object.
+
+ Length - number of bytes to be read.
+ The default property of the queue is to not dispatch
+ zero lenght read & write requests to the driver and
+ complete is with status success. So we will never get
+ a zero length request.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ NTSTATUS Status;
+ WDFMEMORY memory;
+ PQUEUE_CONTEXT queueContext = QueueGetContext(Queue);
+ PVOID writeBuffer = NULL;
+
+ _Analysis_assume_(Length > 0);
+
+ KdPrint(("EchoEvtIoWrite Called! Queue 0x%p, Request 0x%p Length %d\n",
+ Queue,Request,Length));
+
+ if( Length > MAX_WRITE_LENGTH ) {
+ KdPrint(("EchoEvtIoWrite Buffer Length to big %d, Max is %d\n",
+ Length,MAX_WRITE_LENGTH));
+ WdfRequestCompleteWithInformation(Request, STATUS_BUFFER_OVERFLOW, 0L);
+ return;
+ }
+
+ // Get the memory buffer
+ Status = WdfRequestRetrieveInputMemory(Request, &memory);
+ if( !NT_SUCCESS(Status) ) {
+ KdPrint(("EchoEvtIoWrite Could not get request memory buffer 0x%x\n",
+ Status));
+ WdfVerifierDbgBreakPoint();
+ WdfRequestComplete(Request, Status);
+ return;
+ }
+
+ // Release previous buffer if set
+ if( queueContext->WriteMemory != NULL ) {
+ WdfObjectDelete(queueContext->WriteMemory);
+ queueContext->WriteMemory = NULL;
+ }
+
+ Status = WdfMemoryCreate(WDF_NO_OBJECT_ATTRIBUTES,
+ NonPagedPoolNx,
+ 'sam1',
+ Length,
+ &queueContext->WriteMemory,
+ &writeBuffer
+ );
+
+ if(!NT_SUCCESS(Status)) {
+ KdPrint(("EchoEvtIoWrite: Could not allocate %d byte buffer\n", Length));
+ WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
+ return;
+ }
+
+
+ // Copy the memory in
+ Status = WdfMemoryCopyToBuffer( memory,
+ 0, // offset into the source memory
+ writeBuffer,
+ Length );
+ if( !NT_SUCCESS(Status) ) {
+ KdPrint(("EchoEvtIoWrite WdfMemoryCopyToBuffer failed 0x%x\n", Status));
+ WdfVerifierDbgBreakPoint();
+
+ WdfObjectDelete(queueContext->WriteMemory);
+ queueContext->WriteMemory = NULL;
+
+ WdfRequestComplete(Request, Status);
+ return;
+ }
+
+ // Set transfer information
+ WdfRequestSetInformation(Request, (ULONG_PTR)Length);
+
+ // Specify the request is cancelable
+ WdfRequestMarkCancelable(Request, EchoEvtRequestCancel);
+
+ // Defer the completion to another thread from the timer dpc
+ queueContext->CurrentRequest = Request;
+ queueContext->CurrentStatus = Status;
+
+ return;
+}
+
+
+VOID
+EchoEvtTimerFunc(
+ IN WDFTIMER Timer
+ )
+/*++
+
+Routine Description:
+
+ This is the TimerDPC the driver sets up to complete requests.
+ This function is registered when the WDFTIMER object is created, and
+ will automatically synchronize with the I/O Queue callbacks
+ and cancel routine.
+
+Arguments:
+
+ Timer - Handle to a framework Timer object.
+
+Return Value:
+
+ VOID
+
+--*/
+{
+ NTSTATUS Status;
+ WDFREQUEST Request;
+ WDFQUEUE queue;
+ PQUEUE_CONTEXT queueContext ;
+
+ queue = WdfTimerGetParentObject(Timer);
+ queueContext = QueueGetContext(queue);
+
+ //
+ // DPC is automatically synchronized to the Queue lock,
+ // so this is race free without explicit driver managed locking.
+ //
+ Request = queueContext->CurrentRequest;
+ if( Request != NULL ) {
+
+ //
+ // Attempt to remove cancel status from the request.
+ //
+ // The request is not completed if it is already cancelled
+ // since the EchoEvtIoCancel function has run, or is about to run
+ // and we are racing with it.
+ //
+ Status = WdfRequestUnmarkCancelable(Request);
+ if( Status != STATUS_CANCELLED ) {
+
+ queueContext->CurrentRequest = NULL;
+ Status = queueContext->CurrentStatus;
+
+ KdPrint(("CustomTimerDPC Completing request 0x%p, Status 0x%x \n", Request,Status));
+
+ WdfRequestComplete(Request, Status);
+ }
+ else {
+ KdPrint(("CustomTimerDPC Request 0x%p is STATUS_CANCELLED, not completing\n",
+ Request));
+ }
+ }
+
+ //
+ // Restart the Timer since WDF does not allow periodic timer
+ // with autosynchronization at passive level
+ //
+ WdfTimerStart(Timer, WDF_REL_TIMEOUT_IN_MS(TIMER_PERIOD));
+
+ return;
+}
+
+
diff --git a/tests/projects/windows/driver/umdf/echo/driver/queue.h b/tests/projects/windows/driver/umdf/echo/driver/queue.h new file mode 100644 index 000000000..5c04dc8f7 --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/driver/queue.h @@ -0,0 +1,62 @@ +/*++
+
+Copyright (c) 1990-2000 Microsoft Corporation
+
+Module Name:
+
+ queue.h
+
+Abstract:
+
+ This is a C version of a very simple sample driver that illustrates
+ how to use the driver framework and demonstrates best practices.
+
+--*/
+
+// Set max write length for testing
+#define MAX_WRITE_LENGTH 1024*40
+
+// Set timer period in ms
+#define TIMER_PERIOD 1000*2
+
+//
+// This is the context that can be placed per queue
+// and would contain per queue information.
+//
+typedef struct _QUEUE_CONTEXT {
+
+ // Here we allocate a buffer from a test write so it can be read back
+ WDFMEMORY WriteMemory;
+
+ // Timer DPC for this queue
+ WDFTIMER Timer;
+
+ // Virtual I/O
+ WDFREQUEST CurrentRequest;
+ NTSTATUS CurrentStatus;
+
+} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)
+
+NTSTATUS
+EchoQueueInitialize(
+ WDFDEVICE hDevice
+ );
+
+EVT_WDF_IO_QUEUE_CONTEXT_DESTROY_CALLBACK EchoEvtIoQueueContextDestroy;
+
+//
+// Events from the IoQueue object
+//
+EVT_WDF_REQUEST_CANCEL EchoEvtRequestCancel;
+EVT_WDF_IO_QUEUE_IO_READ EchoEvtIoRead;
+EVT_WDF_IO_QUEUE_IO_WRITE EchoEvtIoWrite;
+
+NTSTATUS
+EchoTimerCreate(
+ IN WDFTIMER* pTimer,
+ IN WDFQUEUE Queue
+ );
+
+EVT_WDF_TIMER EchoEvtTimerFunc;
diff --git a/tests/projects/windows/driver/umdf/echo/exe/echoapp.cpp b/tests/projects/windows/driver/umdf/echo/exe/echoapp.cpp new file mode 100644 index 000000000..47c0ae0a1 --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/exe/echoapp.cpp @@ -0,0 +1,652 @@ +/*++
+
+Copyright (c) Microsoft Corporation
+
+Module Name:
+
+ EchoApp.cpp
+
+Abstract:
+
+ An application to exercise the WDF "echo" sample driver.
+
+
+Environment:
+
+ user mode only
+
+--*/
+
+
+#include <DriverSpecs.h>
+_Analysis_mode_(_Analysis_code_type_user_code_)
+
+#define INITGUID
+
+#include <windows.h>
+#include <strsafe.h>
+#include <cfgmgr32.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "public.h"
+
+#define NUM_ASYNCH_IO 100
+#define BUFFER_SIZE (40*1024)
+
+#define READER_TYPE 1
+#define WRITER_TYPE 2
+
+#define MAX_DEVPATH_LENGTH 256
+
+BOOLEAN G_PerformAsyncIo;
+BOOLEAN G_LimitedLoops;
+ULONG G_AsyncIoLoopsNum;
+WCHAR G_DevicePath[MAX_DEVPATH_LENGTH];
+
+
+ULONG
+AsyncIo(
+ PVOID ThreadParameter
+ );
+
+BOOLEAN
+PerformWriteReadTest(
+ IN HANDLE hDevice,
+ IN ULONG TestLength
+ );
+
+BOOL
+GetDevicePath(
+ IN LPGUID InterfaceGuid,
+ _Out_writes_(BufLen) PWCHAR DevicePath,
+ _In_ size_t BufLen
+ );
+
+
+int __cdecl
+main(
+ _In_ int argc,
+ _In_reads_(argc) char* argv[]
+ )
+{
+ HANDLE hDevice = INVALID_HANDLE_VALUE;
+ HANDLE th1 = NULL;
+ BOOLEAN result = TRUE;
+
+
+ if (argc > 1) {
+ if(!_strnicmp (argv[1], "-Async", 6) ) {
+ G_PerformAsyncIo = TRUE;
+ if (argc > 2) {
+ G_AsyncIoLoopsNum = atoi(argv[2]);
+ G_LimitedLoops = TRUE;
+ }
+ else {
+ G_LimitedLoops = FALSE;
+ }
+
+ } else {
+ printf("Usage:\n");
+ printf(" Echoapp.exe --- Send single write and read request synchronously\n");
+ printf(" Echoapp.exe -Async --- Send reads and writes asynchronously without terminating\n");
+ printf(" Echoapp.exe -Async <number> --- Send <number> reads and writes asynchronously\n");
+ printf("Exit the app anytime by pressing Ctrl-C\n");
+ result = FALSE;
+ goto exit;
+ }
+ }
+
+ if ( !GetDevicePath(
+ (LPGUID) &GUID_DEVINTERFACE_ECHO,
+ G_DevicePath,
+ sizeof(G_DevicePath)/sizeof(G_DevicePath[0])) )
+ {
+ result = FALSE;
+ goto exit;
+ }
+
+ printf("DevicePath: %ws\n", G_DevicePath);
+
+ hDevice = CreateFile(G_DevicePath,
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL );
+
+ if (hDevice == INVALID_HANDLE_VALUE) {
+ printf("Failed to open device. Error %d\n",GetLastError());
+ result = FALSE;
+ goto exit;
+ }
+
+ printf("Opened device successfully\n");
+
+ if(G_PerformAsyncIo) {
+
+ printf("Starting AsyncIo\n");
+
+ //
+ // Create a reader thread
+ //
+ th1 = CreateThread( NULL, // Default Security Attrib.
+ 0, // Initial Stack Size,
+ (LPTHREAD_START_ROUTINE) AsyncIo, // Thread Func
+ (LPVOID)READER_TYPE,
+ 0, // Creation Flags
+ NULL ); // Don't need the Thread Id.
+
+ if (th1 == NULL) {
+ printf("Couldn't create reader thread - error %d\n", GetLastError());
+ result = FALSE;
+ goto exit;
+ }
+
+ //
+ // Use this thread for peforming write.
+ //
+ result = (BOOLEAN)AsyncIo((PVOID)WRITER_TYPE);
+
+ }else {
+ //
+ // Write pattern buffers and read them back, then verify them
+ //
+ result = PerformWriteReadTest(hDevice, 512);
+ if(!result) {
+ goto exit;
+ }
+
+ result = PerformWriteReadTest(hDevice, 30*1024);
+ if(!result) {
+ goto exit;
+ }
+
+ }
+
+exit:
+
+ if (th1 != NULL) {
+ WaitForSingleObject(th1, INFINITE);
+ CloseHandle(th1);
+ }
+
+ if (hDevice != INVALID_HANDLE_VALUE) {
+ CloseHandle(hDevice);
+ }
+
+ return ((result == TRUE) ? 0 : 1);
+
+}
+
+PUCHAR
+CreatePatternBuffer(
+ IN ULONG Length
+ )
+{
+ unsigned int i;
+ PUCHAR p, pBuf;
+
+ pBuf = (PUCHAR)malloc(Length);
+ if( pBuf == NULL ) {
+ printf("Could not allocate %d byte buffer\n",Length);
+ return NULL;
+ }
+
+ p = pBuf;
+
+ for(i=0; i < Length; i++ ) {
+ *p = (UCHAR)i;
+ p++;
+ }
+
+ return pBuf;
+}
+
+BOOLEAN
+VerifyPatternBuffer(
+ _In_reads_bytes_(Length) PUCHAR pBuffer,
+ _In_ ULONG Length
+ )
+{
+ unsigned int i;
+ PUCHAR p = pBuffer;
+
+ for( i=0; i < Length; i++ ) {
+
+ if( *p != (UCHAR)(i & 0xFF) ) {
+ printf("Pattern changed. SB 0x%x, Is 0x%x\n",
+ (UCHAR)(i & 0xFF), *p);
+ return FALSE;
+ }
+
+ p++;
+ }
+
+ return TRUE;
+}
+
+BOOLEAN
+PerformWriteReadTest(
+ IN HANDLE hDevice,
+ IN ULONG TestLength
+ )
+/*
+*/
+{
+ ULONG bytesReturned =0;
+ PUCHAR WriteBuffer = NULL,
+ ReadBuffer = NULL;
+ BOOLEAN result = TRUE;
+
+ WriteBuffer = CreatePatternBuffer(TestLength);
+ if( WriteBuffer == NULL ) {
+
+ result = FALSE;
+ goto Cleanup;
+ }
+
+ ReadBuffer = (PUCHAR)malloc(TestLength);
+ if( ReadBuffer == NULL ) {
+
+ printf("PerformWriteReadTest: Could not allocate %d "
+ "bytes ReadBuffer\n",TestLength);
+
+ result = FALSE;
+ goto Cleanup;
+
+ }
+
+ //
+ // Write the pattern to the device
+ //
+ bytesReturned = 0;
+
+ if (!WriteFile ( hDevice,
+ WriteBuffer,
+ TestLength,
+ &bytesReturned,
+ NULL)) {
+
+ printf ("PerformWriteReadTest: WriteFile failed: "
+ "Error %d\n", GetLastError());
+
+ result = FALSE;
+ goto Cleanup;
+
+ } else {
+
+ if( bytesReturned != TestLength ) {
+
+ printf("bytes written is not test length! Written %d, "
+ "SB %d\n",bytesReturned, TestLength);
+
+ result = FALSE;
+ goto Cleanup;
+ }
+
+ printf ("%d Pattern Bytes Written successfully\n",
+ bytesReturned);
+ }
+
+ bytesReturned = 0;
+
+ if ( !ReadFile (hDevice,
+ ReadBuffer,
+ TestLength,
+ &bytesReturned,
+ NULL)) {
+
+ printf ("PerformWriteReadTest: ReadFile failed: "
+ "Error %d\n", GetLastError());
+
+ result = FALSE;
+ goto Cleanup;
+
+ } else {
+
+ if( bytesReturned != TestLength ) {
+
+ printf("bytes Read is not test length! Read %d, "
+ "SB %d\n",bytesReturned, TestLength);
+
+ //
+ // Note: Is this a Failure Case??
+ //
+ result = FALSE;
+ goto Cleanup;
+ }
+
+ printf ("%d Pattern Bytes Read successfully\n",bytesReturned);
+ }
+
+ //
+ // Now compare
+ //
+ if( !VerifyPatternBuffer(ReadBuffer, TestLength) ) {
+
+ printf("Verify failed\n");
+
+ result = FALSE;
+ goto Cleanup;
+ }
+
+ printf("Pattern Verified successfully\n");
+
+Cleanup:
+
+ //
+ // Free WriteBuffer if non NULL.
+ //
+ if (WriteBuffer) {
+ free (WriteBuffer);
+ }
+
+ //
+ // Free ReadBuffer if non NULL
+ //
+ if (ReadBuffer) {
+ free (ReadBuffer);
+ }
+
+ return result;
+}
+
+ULONG
+AsyncIo(
+ PVOID ThreadParameter
+ )
+{
+ HANDLE hDevice = INVALID_HANDLE_VALUE;
+ HANDLE hCompletionPort = NULL;
+ OVERLAPPED *pOvList = NULL;
+ PUCHAR buf = NULL;
+ ULONG numberOfBytesTransferred;
+ OVERLAPPED *completedOv;
+ ULONG_PTR i;
+ ULONG ioType = (ULONG)(ULONG_PTR)ThreadParameter;
+ ULONG_PTR key;
+ ULONG error;
+ BOOLEAN result = TRUE;
+ ULONG maxPendingRequests = NUM_ASYNCH_IO;
+ ULONG remainingRequestsToSend = 0;
+ ULONG remainingRequestsToReceive = 0;
+
+ hDevice = CreateFile(G_DevicePath,
+ GENERIC_WRITE|GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,
+ NULL );
+
+
+ if (hDevice == INVALID_HANDLE_VALUE) {
+ printf("Cannot open %ws error %d\n", G_DevicePath, GetLastError());
+ result = FALSE;
+ goto Error;
+ }
+
+ hCompletionPort = CreateIoCompletionPort(hDevice, NULL, 1, 0);
+ if (hCompletionPort == NULL) {
+ printf("Cannot open completion port %d \n",GetLastError());
+ result = FALSE;
+ goto Error;
+ }
+
+ //
+ // We will only have NUM_ASYNCH_IO or G_AsyncIoLoopsNum pending at any
+ // time (whichever is less)
+ //
+ if (G_LimitedLoops == TRUE) {
+ remainingRequestsToReceive = G_AsyncIoLoopsNum;
+ if (G_AsyncIoLoopsNum > NUM_ASYNCH_IO) {
+ //
+ // After we send the initial NUM_ASYNCH_IO, we will have additional
+ // (G_AsyncIoLoopsNum - NUM_ASYNCH_IO) I/Os to send
+ //
+ maxPendingRequests = NUM_ASYNCH_IO;
+ remainingRequestsToSend = G_AsyncIoLoopsNum - NUM_ASYNCH_IO;
+ }
+ else {
+ maxPendingRequests = G_AsyncIoLoopsNum;
+ remainingRequestsToSend = 0;
+
+ }
+ }
+
+ pOvList = (OVERLAPPED *)malloc(maxPendingRequests * sizeof(OVERLAPPED));
+ if (pOvList == NULL) {
+ printf("Cannot allocate overlapped array \n");
+ result = FALSE;
+ goto Error;
+ }
+
+ buf = (PUCHAR)malloc(maxPendingRequests * BUFFER_SIZE);
+ if (buf == NULL) {
+ printf("Cannot allocate buffer \n");
+ result = FALSE;
+ goto Error;
+ }
+
+ ZeroMemory(pOvList, maxPendingRequests * sizeof(OVERLAPPED));
+ ZeroMemory(buf, maxPendingRequests * BUFFER_SIZE);
+
+ //
+ // Issue asynch I/O
+ //
+
+ for (i = 0; i < maxPendingRequests; i++) {
+ if (ioType == READER_TYPE) {
+ if ( ReadFile( hDevice,
+ buf + (i* BUFFER_SIZE),
+ BUFFER_SIZE,
+ NULL,
+ &pOvList[i]) == 0) {
+
+ error = GetLastError();
+ if (error != ERROR_IO_PENDING) {
+ printf(" %dth Read failed %d \n", (ULONG) i, GetLastError());
+ result = FALSE;
+ goto Error;
+ }
+ }
+
+ } else {
+ if ( WriteFile( hDevice,
+ buf + (i* BUFFER_SIZE),
+ BUFFER_SIZE,
+ NULL,
+ &pOvList[i]) == 0) {
+ error = GetLastError();
+ if (error != ERROR_IO_PENDING) {
+ printf(" %dth Write failed %d \n", (ULONG) i, GetLastError());
+ result = FALSE;
+ goto Error;
+ }
+ }
+ }
+ }
+
+ //
+ // Wait for the I/Os to complete. If one completes then reissue the I/O
+ //
+
+ WHILE (1) {
+
+ if ( GetQueuedCompletionStatus(hCompletionPort, &numberOfBytesTransferred, &key, &completedOv, INFINITE) == 0) {
+ printf("GetQueuedCompletionStatus failed %d\n", GetLastError());
+ result = FALSE;
+ goto Error;
+ }
+
+ //
+ // Read successfully completed. If we're doing unlimited I/Os then Issue another one.
+ //
+
+ if (ioType == READER_TYPE) {
+
+ i = completedOv - pOvList;
+ printf("Number of bytes read by request number %Id is %d\n", i, numberOfBytesTransferred);
+
+ //
+ // If we're done with the I/Os, then exit
+ //
+ if (G_LimitedLoops == TRUE) {
+ if ((--remainingRequestsToReceive) == 0) {
+ break;
+ }
+
+ if (remainingRequestsToSend == 0) {
+ continue;
+ }
+ else {
+ remainingRequestsToSend--;
+ }
+ }
+
+
+ if ( ReadFile( hDevice,
+ buf + (i * BUFFER_SIZE),
+ BUFFER_SIZE,
+ NULL,
+ completedOv) == 0) {
+ error = GetLastError();
+ if (error != ERROR_IO_PENDING) {
+ printf("%Idth Read failed %d \n", i, GetLastError());
+ result = FALSE;
+ goto Error;
+ }
+ }
+ } else {
+
+ i = completedOv - pOvList;
+
+ printf("Number of bytes written by request number %Id is %d\n", i, numberOfBytesTransferred);
+
+ //
+ // If we're done with the I/Os, then exit
+ //
+ if (G_LimitedLoops == TRUE) {
+ if ((--remainingRequestsToReceive) == 0) {
+ break;
+ }
+
+ if (remainingRequestsToSend == 0) {
+ continue;
+ }
+ else {
+ remainingRequestsToSend--;
+ }
+ }
+
+
+ if ( WriteFile( hDevice,
+ buf + (i * BUFFER_SIZE),
+ BUFFER_SIZE,
+ NULL,
+ completedOv) == 0) {
+ error = GetLastError();
+ if (error != ERROR_IO_PENDING) {
+
+ printf("%Idth write failed %d \n", i, GetLastError());
+ result = FALSE;
+ goto Error;
+ }
+ }
+ }
+ }
+
+Error:
+ if(hDevice != INVALID_HANDLE_VALUE) {
+ CloseHandle(hDevice);
+ }
+
+ if(hCompletionPort) {
+ CloseHandle(hCompletionPort);
+ }
+
+ if(buf) {
+ free(buf);
+ }
+ if(pOvList) {
+ free(pOvList);
+ }
+
+ return (ULONG)result;
+
+}
+
+BOOL
+GetDevicePath(
+ _In_ LPGUID InterfaceGuid,
+ _Out_writes_(BufLen) PWCHAR DevicePath,
+ _In_ size_t BufLen
+ )
+{
+ CONFIGRET cr = CR_SUCCESS;
+ PWSTR deviceInterfaceList = NULL;
+ ULONG deviceInterfaceListLength = 0;
+ PWSTR nextInterface;
+ HRESULT hr = E_FAIL;
+ BOOL bRet = TRUE;
+
+ cr = CM_Get_Device_Interface_List_Size(
+ &deviceInterfaceListLength,
+ InterfaceGuid,
+ NULL,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
+ if (cr != CR_SUCCESS) {
+ printf("Error 0x%x retrieving device interface list size.\n", cr);
+ goto clean0;
+ }
+
+ if (deviceInterfaceListLength <= 1) {
+ bRet = FALSE;
+ printf("Error: No active device interfaces found.\n"
+ " Is the sample driver loaded?");
+ goto clean0;
+ }
+
+ deviceInterfaceList = (PWSTR)malloc(deviceInterfaceListLength * sizeof(WCHAR));
+ if (deviceInterfaceList == NULL) {
+ printf("Error allocating memory for device interface list.\n");
+ goto clean0;
+ }
+ ZeroMemory(deviceInterfaceList, deviceInterfaceListLength * sizeof(WCHAR));
+
+ cr = CM_Get_Device_Interface_List(
+ InterfaceGuid,
+ NULL,
+ deviceInterfaceList,
+ deviceInterfaceListLength,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
+ if (cr != CR_SUCCESS) {
+ printf("Error 0x%x retrieving device interface list.\n", cr);
+ goto clean0;
+ }
+
+ nextInterface = deviceInterfaceList + wcslen(deviceInterfaceList) + 1;
+ if (*nextInterface != UNICODE_NULL) {
+ printf("Warning: More than one device interface instance found. \n"
+ "Selecting first matching device.\n\n");
+ }
+
+ hr = StringCchCopy(DevicePath, BufLen, deviceInterfaceList);
+ if (FAILED(hr)) {
+ bRet = FALSE;
+ printf("Error: StringCchCopy failed with HRESULT 0x%x", hr);
+ goto clean0;
+ }
+
+clean0:
+ if (deviceInterfaceList != NULL) {
+ free(deviceInterfaceList);
+ }
+ if (CR_SUCCESS != cr) {
+ bRet = FALSE;
+ }
+
+ return bRet;
+}
+
diff --git a/tests/projects/windows/driver/umdf/echo/exe/public.h b/tests/projects/windows/driver/umdf/echo/exe/public.h new file mode 100644 index 000000000..defcded56 --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/exe/public.h @@ -0,0 +1,30 @@ +/*++
+Copyright (c) 1990-2000 Microsoft Corporation All Rights Reserved
+
+Module Name:
+
+ public.h
+
+Abstract:
+
+ This module contains the common declarations shared by driver
+ and user applications.
+
+
+Environment:
+
+ user and kernel
+
+--*/
+
+#define WHILE(a) \
+__pragma(warning(suppress:4127)) while(a)
+
+//
+// Define an Interface Guid so that app can find the device and talk to it.
+//
+
+DEFINE_GUID (GUID_DEVINTERFACE_ECHO,
+ 0xcdc35b6e, 0xbe4, 0x4936, 0xbf, 0x5f, 0x55, 0x37, 0x38, 0xa, 0x7c, 0x1a);
+// {CDC35B6E-0BE4-4936-BF5F-5537380A7C1A}
+
diff --git a/tests/projects/windows/driver/umdf/echo/xmake.lua b/tests/projects/windows/driver/umdf/echo/xmake.lua new file mode 100644 index 000000000..0a5ec9517 --- /dev/null +++ b/tests/projects/windows/driver/umdf/echo/xmake.lua @@ -0,0 +1,22 @@ +add_rules("mode.debug", "mode.release") + +add_defines("_UNICODE", "UNICODE") + +target("echo") + add_rules("wdk.env.umdf", "wdk.driver") + + -- set test sign +-- set_values("wdk.sign.mode", "test") + + -- set release sign +-- set_values("wdk.sign.mode", "release") +-- set_values("wdk.sign.certfile", path.join(os.projectdir(), "xxx.cer")) + + add_files("driver/*.c") + add_files("driver/*.inx") + add_includedirs("exe") + +target("app") + add_rules("wdk.env.umdf", "wdk.binary") + add_files("exe/*.cpp") + diff --git a/tests/projects/windows/driver/umdf/skeleton/Skeleton.rc b/tests/projects/windows/driver/umdf/skeleton/Skeleton.rc new file mode 100644 index 000000000..0e202c4f5 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/Skeleton.rc @@ -0,0 +1,21 @@ +//---------------------------------------------------------------------------
+// Skeleton.rc
+//
+// Copyright (c) Microsoft Corporation, All Rights Reserved
+//---------------------------------------------------------------------------
+
+
+#include <windows.h>
+#include <ntverp.h>
+
+//
+// TODO: Change the file description and file names to match your binary.
+//
+
+#define VER_FILETYPE VFT_DLL
+#define VER_FILESUBTYPE VFT_UNKNOWN
+#define VER_FILEDESCRIPTION_STR "WDF:UMDF Skeleton User-Mode Driver Sample"
+#define VER_INTERNALNAME_STR "UMDFSkeleton"
+#define VER_ORIGINALFILENAME_STR "UMDFSkeleton.dll"
+
+#include "common.ver"
diff --git a/tests/projects/windows/driver/umdf/skeleton/UMDFSkeleton_OSR.inx b/tests/projects/windows/driver/umdf/skeleton/UMDFSkeleton_OSR.inx Binary files differnew file mode 100644 index 000000000..3da01ae62 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/UMDFSkeleton_OSR.inx diff --git a/tests/projects/windows/driver/umdf/skeleton/UMDFSkeleton_Root.inx b/tests/projects/windows/driver/umdf/skeleton/UMDFSkeleton_Root.inx Binary files differnew file mode 100644 index 000000000..63d9f94c5 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/UMDFSkeleton_Root.inx diff --git a/tests/projects/windows/driver/umdf/skeleton/comsup.cpp b/tests/projects/windows/driver/umdf/skeleton/comsup.cpp new file mode 100644 index 000000000..fb0b0807c --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/comsup.cpp @@ -0,0 +1,344 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved
+
+Module Name:
+
+ ComSup.cpp
+
+Abstract:
+
+ This module contains implementations for the functions and methods
+ used for providing COM support.
+
+Environment:
+
+ Windows User-Mode Driver Framework (WUDF)
+
+--*/
+
+#include "internal.h"
+
+#include "comsup.tmh"
+
+//
+// Implementation of CUnknown methods.
+//
+
+CUnknown::CUnknown(
+ VOID
+ ) : m_ReferenceCount(1)
+/*++
+
+ Routine Description:
+
+ Constructor for an instance of the CUnknown class. This simply initializes
+ the reference count of the object to 1. The caller is expected to
+ call Release() if it wants to delete the object once it has been allocated.
+
+ Arguments:
+
+ None
+
+ Return Value:
+
+ None
+
+--*/
+{
+ // do nothing.
+}
+
+HRESULT
+STDMETHODCALLTYPE
+CUnknown::QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ )
+/*++
+
+ Routine Description:
+
+ This method provides the basic support for query interface on CUnknown.
+ If the interface requested is IUnknown it references the object and
+ returns an interface pointer. Otherwise it returns an error.
+
+ Arguments:
+
+ InterfaceId - the IID being requested
+
+ Object - a location to store the interface pointer to return.
+
+ Return Value:
+
+ S_OK or E_NOINTERFACE
+
+--*/
+{
+ if (IsEqualIID(InterfaceId, __uuidof(IUnknown)))
+ {
+ *Object = QueryIUnknown();
+ return S_OK;
+ }
+ else
+ {
+ *Object = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+IUnknown *
+CUnknown::QueryIUnknown(
+ VOID
+ )
+/*++
+
+ Routine Description:
+
+ This helper method references the object and returns a pointer to the
+ object's IUnknown interface.
+
+ This allows other methods to convert a CUnknown pointer into an IUnknown
+ pointer without a typecast and without calling QueryInterface and dealing
+ with the return value.
+
+ Arguments:
+
+ None
+
+ Return Value:
+
+ A pointer to the object's IUnknown interface.
+
+--*/
+{
+ AddRef();
+ return static_cast<IUnknown *>(this);
+}
+
+ULONG
+STDMETHODCALLTYPE
+CUnknown::AddRef(
+ VOID
+ )
+/*++
+
+ Routine Description:
+
+ This method adds one to the object's reference count.
+
+ Arguments:
+
+ None
+
+ Return Value:
+
+ The new reference count. The caller should only use this for debugging
+ as the object's actual reference count can change while the caller
+ examines the return value.
+
+--*/
+{
+ return InterlockedIncrement(&m_ReferenceCount);
+}
+
+ULONG
+STDMETHODCALLTYPE
+CUnknown::Release(
+ VOID
+ )
+/*++
+
+ Routine Description:
+
+ This method subtracts one to the object's reference count. If the count
+ goes to zero, this method deletes the object.
+
+ Arguments:
+
+ None
+
+ Return Value:
+
+ The new reference count. If the caller uses this value it should only be
+ to check for zero (i.e. this call caused or will cause deletion) or
+ non-zero (i.e. some other call may have caused deletion, but this one
+ didn't).
+
+--*/
+{
+ ULONG count = InterlockedDecrement(&m_ReferenceCount);
+
+ if (count == 0)
+ {
+ delete this;
+ }
+ return count;
+}
+
+//
+// Implementation of CClassFactory methods.
+//
+
+//
+// Define storage for the factory's static lock count variable.
+//
+
+LONG CClassFactory::s_LockCount = 0;
+
+IClassFactory *
+CClassFactory::QueryIClassFactory(
+ VOID
+ )
+/*++
+
+ Routine Description:
+
+ This helper method references the object and returns a pointer to the
+ object's IClassFactory interface.
+
+ This allows other methods to convert a CClassFactory pointer into an
+ IClassFactory pointer without a typecast and without dealing with the
+ return value QueryInterface.
+
+ Arguments:
+
+ None
+
+ Return Value:
+
+ A referenced pointer to the object's IClassFactory interface.
+
+--*/
+{
+ AddRef();
+ return static_cast<IClassFactory *>(this);
+}
+
+HRESULT
+CClassFactory::QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ )
+/*++
+
+ Routine Description:
+
+ This method attempts to retrieve the requested interface from the object.
+
+ If the interface is found then the reference count on that interface (and
+ thus the object itself) is incremented.
+
+ Arguments:
+
+ InterfaceId - the interface the caller is requesting.
+
+ Object - a location to store the interface pointer.
+
+ Return Value:
+
+ S_OK or E_NOINTERFACE
+
+--*/
+{
+ //
+ // This class only supports IClassFactory so check for that.
+ //
+
+ if (IsEqualIID(InterfaceId, __uuidof(IClassFactory)))
+ {
+ *Object = QueryIClassFactory();
+ return S_OK;
+ }
+ else
+ {
+ //
+ // See if the base class supports the interface.
+ //
+
+ return CUnknown::QueryInterface(InterfaceId, Object);
+ }
+}
+
+HRESULT
+STDMETHODCALLTYPE
+CClassFactory::CreateInstance(
+ _In_opt_ IUnknown * /* OuterObject */,
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ )
+/*++
+
+ Routine Description:
+
+ This COM method is the factory routine - it creates instances of the driver
+ callback class and returns the specified interface on them.
+
+ Arguments:
+
+ OuterObject - only used for aggregation, which our driver callback class
+ does not support.
+
+ InterfaceId - the interface ID the caller would like to get from our
+ new object.
+
+ Object - a location to store the referenced interface pointer to the new
+ object.
+
+ Return Value:
+
+ Status.
+
+--*/
+{
+ HRESULT hr;
+
+ PCMyDriver driver;
+
+ *Object = NULL;
+
+ hr = CMyDriver::CreateInstance(&driver);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = driver->QueryInterface(InterfaceId, Object);
+ driver->Release();
+ }
+
+ return hr;
+}
+
+HRESULT
+STDMETHODCALLTYPE
+CClassFactory::LockServer(
+ _In_ BOOL Lock
+ )
+/*++
+
+ Routine Description:
+
+ This COM method can be used to keep the DLL in memory. However since the
+ driver's DllCanUnloadNow function always returns false, this has little
+ effect. Still it tracks the number of lock and unlock operations.
+
+ Arguments:
+
+ Lock - Whether the caller wants to lock or unlock the "server"
+
+ Return Value:
+
+ S_OK
+
+--*/
+{
+ if (Lock)
+ {
+ InterlockedIncrement(&s_LockCount);
+ }
+ else
+ {
+ InterlockedDecrement(&s_LockCount);
+ }
+ return S_OK;
+}
+
diff --git a/tests/projects/windows/driver/umdf/skeleton/comsup.h b/tests/projects/windows/driver/umdf/skeleton/comsup.h new file mode 100644 index 000000000..ba4cd89c3 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/comsup.h @@ -0,0 +1,215 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved
+
+Module Name:
+
+ ComSup.h
+
+Abstract:
+
+ This module contains classes and functions use for providing COM support
+ code.
+
+Environment:
+
+ Windows User-Mode Driver Framework (WUDF)
+
+--*/
+
+#pragma once
+
+//
+// Forward type declarations. They are here rather than in internal.h as
+// you only need them if you choose to use these support classes.
+//
+
+typedef class CUnknown *PCUnknown;
+typedef class CClassFactory *PCClassFactory;
+
+//
+// Base class to implement IUnknown. You can choose to derive your COM
+// classes from this class, or simply implement IUnknown in each of your
+// classes.
+//
+
+class CUnknown : public IUnknown
+{
+
+//
+// Private data members and methods. These are only accessible by the methods
+// of this class.
+//
+private:
+
+ //
+ // The reference count for this object. Initialized to 1 in the
+ // constructor.
+ //
+
+ LONG m_ReferenceCount;
+
+//
+// Protected data members and methods. These are accessible by the subclasses
+// but not by other classes.
+//
+protected:
+
+ //
+ // The constructor and destructor are protected to ensure that only the
+ // subclasses of CUnknown can create and destroy instances.
+ //
+
+ CUnknown(
+ VOID
+ );
+
+ //
+ // The destructor MUST be virtual. Since any instance of a CUnknown
+ // derived class should only be deleted from within CUnknown::Release,
+ // the destructor MUST be virtual or only CUnknown::~CUnknown will get
+ // invoked on deletion.
+ //
+ // If you see that your CMyDevice specific destructor is never being
+ // called, make sure you haven't deleted the virtual destructor here.
+ //
+
+ virtual
+ ~CUnknown(
+ VOID
+ )
+ {
+ // Do nothing
+ }
+
+//
+// Public Methods. These are accessible by any class.
+//
+public:
+
+ IUnknown *
+ QueryIUnknown(
+ VOID
+ );
+
+//
+// COM Methods.
+//
+public:
+
+ //
+ // IUnknown methods
+ //
+
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ AddRef(
+ VOID
+ );
+
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ Release(
+ VOID
+ );
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ );
+};
+
+//
+// Class factory support class. Create an instance of this from your
+// DllGetClassObject method and modify the implementation to create
+// an instance of your driver event handler class.
+//
+
+class CClassFactory : public CUnknown, public IClassFactory
+{
+//
+// Private data members and methods. These are only accessible by the methods
+// of this class.
+//
+private:
+
+ //
+ // The lock count. This is shared across all instances of IClassFactory
+ // and can be queried through the public IsLocked method.
+ //
+
+ static LONG s_LockCount;
+
+//
+// Public Methods. These are accessible by any class.
+//
+public:
+
+ IClassFactory *
+ QueryIClassFactory(
+ VOID
+ );
+
+//
+// COM Methods.
+//
+public:
+
+ //
+ // IUnknown methods
+ //
+
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ AddRef(
+ VOID
+ )
+ {
+ return __super::AddRef();
+ }
+
+ _At_(this, __drv_freesMem(object))
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ Release(
+ VOID
+ )
+ {
+ return __super::Release();
+ }
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ );
+
+ //
+ // IClassFactory methods.
+ //
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ CreateInstance(
+ _In_opt_ IUnknown *OuterObject,
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ );
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ LockServer(
+ _In_ BOOL Lock
+ );
+};
diff --git a/tests/projects/windows/driver/umdf/skeleton/device.cpp b/tests/projects/windows/driver/umdf/skeleton/device.cpp new file mode 100644 index 000000000..2efcd36f0 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/device.cpp @@ -0,0 +1,238 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved.
+
+Module Name:
+
+ Device.cpp
+
+Abstract:
+
+ This module contains the implementation of the UMDF Skeleton sample driver's
+ device callback object.
+
+ The skeleton sample device does very little. It does not implement either
+ of the PNP interfaces so once the device is setup, it won't ever get any
+ callbacks until the device is removed.
+
+Environment:
+
+ Windows User-Mode Driver Framework (WUDF)
+
+--*/
+
+#include "internal.h"
+#include "device.tmh"
+
+HRESULT
+CMyDevice::CreateInstance(
+ _In_ IWDFDriver *FxDriver,
+ _In_ IWDFDeviceInitialize * FxDeviceInit,
+ _Out_ PCMyDevice *Device
+ )
+/*++
+
+ Routine Description:
+
+ This method creates and initializs an instance of the skeleton driver's
+ device callback object.
+
+ Arguments:
+
+ FxDeviceInit - the settings for the device.
+
+ Device - a location to store the referenced pointer to the device object.
+
+ Return Value:
+
+ Status
+
+--*/
+{
+ PCMyDevice device;
+ HRESULT hr;
+
+ //
+ // Allocate a new instance of the device class.
+ //
+
+ device = new CMyDevice();
+
+ if (NULL == device)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ //
+ // Initialize the instance.
+ //
+
+ hr = device->Initialize(FxDriver, FxDeviceInit);
+
+ if (SUCCEEDED(hr))
+ {
+ *Device = device;
+ }
+ else
+ {
+ device->Release();
+ }
+
+ return hr;
+}
+
+HRESULT
+CMyDevice::Initialize(
+ _In_ IWDFDriver * FxDriver,
+ _In_ IWDFDeviceInitialize * FxDeviceInit
+ )
+/*++
+
+ Routine Description:
+
+ This method initializes the device callback object and creates the
+ partner device object.
+
+ The method should perform any device-specific configuration that:
+ * could fail (these can't be done in the constructor)
+ * must be done before the partner object is created -or-
+ * can be done after the partner object is created and which aren't
+ influenced by any device-level parameters the parent (the driver
+ in this case) might set.
+
+ Arguments:
+
+ FxDeviceInit - the settings for this device.
+
+ Return Value:
+
+ status.
+
+--*/
+{
+ IWDFDevice *fxDevice;
+ HRESULT hr;
+
+ //
+ // Configure things like the locking model before we go to create our
+ // partner device.
+ //
+
+ //
+ // Set no locking unless you need an automatic callbacks synchronization
+ //
+
+ FxDeviceInit->SetLockingConstraint(None);
+
+ //
+ // TODO: If you're writing a filter driver then indicate that here.
+ //
+ // FxDeviceInit->SetFilter();
+ //
+
+ //
+ // TODO: Any per-device initialization which must be done before
+ // creating the partner object.
+ //
+
+ //
+ // Create a new FX device object and assign the new callback object to
+ // handle any device level events that occur.
+ //
+
+ //
+ // QueryIUnknown references the IUnknown interface that it returns
+ // (which is the same as referencing the device). We pass that to
+ // CreateDevice, which takes its own reference if everything works.
+ //
+
+ {
+ IUnknown *unknown = this->QueryIUnknown();
+
+ hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
+
+ unknown->Release();
+ }
+
+ //
+ // If that succeeded then set our FxDevice member variable.
+ //
+
+ if (SUCCEEDED(hr))
+ {
+ m_FxDevice = fxDevice;
+
+ //
+ // Drop the reference we got from CreateDevice. Since this object
+ // is partnered with the framework object they have the same
+ // lifespan - there is no need for an additional reference.
+ //
+
+ fxDevice->Release();
+ }
+
+ return hr;
+}
+
+HRESULT
+CMyDevice::Configure(
+ VOID
+ )
+/*++
+
+ Routine Description:
+
+ This method is called after the device callback object has been initialized
+ and returned to the driver. It would setup the device's queues and their
+ corresponding callback objects.
+
+ Arguments:
+
+ FxDevice - the framework device object for which we're handling events.
+
+ Return Value:
+
+ status
+
+--*/
+{
+ //
+ // TODO: Setup your device queues and I/O forwarding.
+ //
+
+ return S_OK;
+}
+
+HRESULT
+CMyDevice::QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ )
+/*++
+
+ Routine Description:
+
+ This method is called to get a pointer to one of the object's callback
+ interfaces.
+
+ Since the skeleton driver doesn't support any of the device events, this
+ method simply calls the base class's BaseQueryInterface.
+
+ If the skeleton is extended to include device event interfaces then this
+ method must be changed to check the IID and return pointers to them as
+ appropriate.
+
+ Arguments:
+
+ InterfaceId - the interface being requested
+
+ Object - a location to store the interface pointer if successful
+
+ Return Value:
+
+ S_OK or E_NOINTERFACE
+
+--*/
+{
+ return CUnknown::QueryInterface(InterfaceId, Object);
+}
diff --git a/tests/projects/windows/driver/umdf/skeleton/device.h b/tests/projects/windows/driver/umdf/skeleton/device.h new file mode 100644 index 000000000..26dd0e329 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/device.h @@ -0,0 +1,115 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved
+
+Module Name:
+
+ Device.h
+
+Abstract:
+
+ This module contains the type definitions for the UMDF Skeleton sample
+ driver's device callback class.
+
+Environment:
+
+ Windows User-Mode Driver Framework (WUDF)
+
+--*/
+
+#pragma once
+
+//
+// Class for the iotrace driver.
+//
+
+class CMyDevice : public CUnknown
+{
+
+//
+// Private data members.
+//
+private:
+
+ IWDFDevice *m_FxDevice;
+
+//
+// Private methods.
+//
+
+private:
+
+ CMyDevice(
+ VOID
+ )
+ {
+ m_FxDevice = NULL;
+ }
+
+ HRESULT
+ Initialize(
+ _In_ IWDFDriver *FxDriver,
+ _In_ IWDFDeviceInitialize *FxDeviceInit
+ );
+
+//
+// Public methods
+//
+public:
+
+ //
+ // The factory method used to create an instance of this driver.
+ //
+
+ static
+ HRESULT
+ CreateInstance(
+ _In_ IWDFDriver *FxDriver,
+ _In_ IWDFDeviceInitialize *FxDeviceInit,
+ _Out_ PCMyDevice *Device
+ );
+
+ HRESULT
+ Configure(
+ VOID
+ );
+
+//
+// COM methods
+//
+public:
+
+ //
+ // IUnknown methods.
+ //
+
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ AddRef(
+ VOID
+ )
+ {
+ return __super::AddRef();
+ }
+
+ _At_(this, __drv_freesMem(object))
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ Release(
+ VOID
+ )
+ {
+ return __super::Release();
+ }
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ );
+
+};
diff --git a/tests/projects/windows/driver/umdf/skeleton/dllsup.cpp b/tests/projects/windows/driver/umdf/skeleton/dllsup.cpp new file mode 100644 index 000000000..e540452d9 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/dllsup.cpp @@ -0,0 +1,177 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved.
+
+Module Name:
+
+ dllsup.cpp
+
+Abstract:
+
+ This module contains the implementation of the UMDF Skeleton Sample
+ Driver's entry point and its exported functions for providing COM support.
+
+ This module can be copied without modification to a new UMDF driver. It
+ depends on some of the code in comsup.cpp & comsup.h to handle DLL
+ registration and creating the first class factory.
+
+ This module is dependent on the following defines:
+
+ MYDRIVER_TRACING_ID - A wide string passed to WPP when initializing
+ tracing. For example the skeleton uses
+ L"Microsoft\\UMDF\\Skeleton"
+
+ MYDRIVER_CLASS_ID - A GUID encoded in struct format used to
+ initialize the driver's ClassID.
+
+ These are defined in internal.h for the sample. If you choose
+ to use a different primary include file, you should ensure they are
+ defined there as well.
+
+Environment:
+
+ WDF User-Mode Driver Framework (WDF:UMDF)
+
+--*/
+
+#include "internal.h"
+#include "dllsup.tmh"
+
+const GUID CLSID_MyDriverCoClass = MYDRIVER_CLASS_ID;
+
+BOOL
+WINAPI
+DllMain(
+ HINSTANCE ModuleHandle,
+ DWORD Reason,
+ PVOID /* Reserved */
+ )
+/*++
+
+ Routine Description:
+
+ This is the entry point and exit point for the I/O trace driver. This
+ does very little as the I/O trace driver has minimal global data.
+
+ This method initializes tracing.
+
+ Arguments:
+
+ ModuleHandle - the DLL handle for this module.
+
+ Reason - the reason this entry point was called.
+
+ Reserved - unused
+
+ Return Value:
+
+ TRUE
+
+--*/
+{
+
+ UNREFERENCED_PARAMETER( ModuleHandle );
+
+ if (DLL_PROCESS_ATTACH == Reason)
+ {
+ //
+ // Initialize tracing.
+ //
+
+ WPP_INIT_TRACING(MYDRIVER_TRACING_ID);
+
+ }
+ else if (DLL_PROCESS_DETACH == Reason)
+ {
+ //
+ // Cleanup tracing.
+ //
+
+ WPP_CLEANUP();
+ }
+
+ return TRUE;
+}
+
+HRESULT
+STDAPICALLTYPE
+DllGetClassObject(
+ _In_ REFCLSID ClassId,
+ _In_ REFIID InterfaceId,
+ _Outptr_ LPVOID *Interface
+ )
+/*++
+
+ Routine Description:
+
+ This routine is called by COM in order to instantiate the
+ driver callback object and do an initial query interface on it.
+
+ This method only creates an instance of the driver's class factory, as this
+ is the minimum required to support UMDF.
+
+ Arguments:
+
+ ClassId - the CLSID of the object being "gotten"
+
+ InterfaceId - the interface the caller wants from that object.
+
+ Interface - a location to store the referenced interface pointer
+
+ Return Value:
+
+ S_OK if the function succeeds or error indicating the cause of the
+ failure.
+
+--*/
+{
+ PCClassFactory factory;
+
+ HRESULT hr = S_OK;
+
+ *Interface = NULL;
+
+ //
+ // If the CLSID doesn't match that of our "coclass" (defined in the IDL
+ // file) then we can't create the object the caller wants. This may
+ // indicate that the COM registration is incorrect, and another CLSID
+ // is referencing this drvier.
+ //
+
+ if (IsEqualCLSID(ClassId, CLSID_MyDriverCoClass) == false)
+ {
+ Trace(
+ TRACE_LEVEL_ERROR,
+ L"ERROR: Called to create instance of unrecognized class (%!GUID!)",
+ &ClassId
+ );
+
+ return CLASS_E_CLASSNOTAVAILABLE;
+ }
+
+ //
+ // Create an instance of the class factory for the caller.
+ //
+
+ factory = new CClassFactory();
+
+ if (NULL == factory)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ //
+ // Query the object we created for the interface the caller wants. After
+ // that we release the object. This will drive the reference count to
+ // 1 (if the QI succeeded an referenced the object) or 0 (if the QI failed).
+ // In the later case the object is automatically deleted.
+ //
+
+ if (SUCCEEDED(hr))
+ {
+ hr = factory->QueryInterface(InterfaceId, Interface);
+ factory->Release();
+ }
+
+ return hr;
+}
diff --git a/tests/projects/windows/driver/umdf/skeleton/driver.cpp b/tests/projects/windows/driver/umdf/skeleton/driver.cpp new file mode 100644 index 000000000..d91b4b0b5 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/driver.cpp @@ -0,0 +1,220 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved.
+
+Module Name:
+
+ Driver.cpp
+
+Abstract:
+
+ This module contains the implementation of the UMDF Skeleton Sample's
+ core driver callback object.
+
+Environment:
+
+ Windows User-Mode Driver Framework (WUDF)
+
+--*/
+
+#include "internal.h"
+#include "driver.tmh"
+
+HRESULT
+CMyDriver::CreateInstance(
+ _Out_ PCMyDriver *Driver
+ )
+/*++
+
+ Routine Description:
+
+ This static method is invoked in order to create and initialize a new
+ instance of the driver class. The caller should arrange for the object
+ to be released when it is no longer in use.
+
+ Arguments:
+
+ Driver - a location to store a referenced pointer to the new instance
+
+ Return Value:
+
+ S_OK if successful, or error otherwise.
+
+--*/
+{
+ PCMyDriver driver;
+ HRESULT hr;
+
+ //
+ // Allocate the callback object.
+ //
+
+ driver = new CMyDriver();
+
+ if (NULL == driver)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ //
+ // Initialize the callback object.
+ //
+
+ hr = driver->Initialize();
+
+ if (SUCCEEDED(hr))
+ {
+ //
+ // Store a pointer to the new, initialized object in the output
+ // parameter.
+ //
+
+ *Driver = driver;
+ }
+ else
+ {
+
+ //
+ // Release the reference on the driver object to get it to delete
+ // itself.
+ //
+
+ driver->Release();
+ }
+
+ return hr;
+}
+
+HRESULT
+CMyDriver::Initialize(
+ VOID
+ )
+/*++
+
+ Routine Description:
+
+ This method is called to initialize a newly created driver callback object
+ before it is returned to the creator. Unlike the constructor, the
+ Initialize method contains operations which could potentially fail.
+
+ Arguments:
+
+ None
+
+ Return Value:
+
+ None
+
+--*/
+{
+ return S_OK;
+}
+
+HRESULT
+CMyDriver::QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Interface
+ )
+/*++
+
+ Routine Description:
+
+ This method returns a pointer to the requested interface on the callback
+ object..
+
+ Arguments:
+
+ InterfaceId - the IID of the interface to query/reference
+
+ Interface - a location to store the interface pointer.
+
+ Return Value:
+
+ S_OK if the interface is supported.
+ E_NOINTERFACE if it is not supported.
+
+--*/
+{
+ if (IsEqualIID(InterfaceId, __uuidof(IDriverEntry)))
+ {
+ *Interface = QueryIDriverEntry();
+ return S_OK;
+ }
+ else
+ {
+ return CUnknown::QueryInterface(InterfaceId, Interface);
+ }
+}
+
+HRESULT
+CMyDriver::OnDeviceAdd(
+ _In_ IWDFDriver *FxWdfDriver,
+ _In_ IWDFDeviceInitialize *FxDeviceInit
+ )
+/*++
+
+ Routine Description:
+
+ The FX invokes this method when it wants to install our driver on a device
+ stack. This method creates a device callback object, then calls the Fx
+ to create an Fx device object and associate the new callback object with
+ it.
+
+ Arguments:
+
+ FxWdfDriver - the Fx driver object.
+
+ FxDeviceInit - the initialization information for the device.
+
+ Return Value:
+
+ status
+
+--*/
+{
+ HRESULT hr;
+
+ PCMyDevice device = NULL;
+
+ //
+ // TODO: Do any per-device initialization (reading settings from the
+ // registry for example) that's necessary before creating your
+ // device callback object here. Otherwise you can leave such
+ // initialization to the initialization of the device event
+ // handler.
+ //
+
+ //
+ // Create a new instance of our device callback object
+ //
+
+ hr = CMyDevice::CreateInstance(FxWdfDriver, FxDeviceInit, &device);
+
+ //
+ // TODO: Change any per-device settings that the object exposes before
+ // calling Configure to let it complete its initialization.
+ //
+
+ //
+ // If that succeeded then call the device's construct method. This
+ // allows the device to create any queues or other structures that it
+ // needs now that the corresponding fx device object has been created.
+ //
+
+ if (SUCCEEDED(hr))
+ {
+ hr = device->Configure();
+ }
+
+ //
+ // Release the reference on the device callback object now that it's been
+ // associated with an fx device object.
+ //
+
+ if (NULL != device)
+ {
+ device->Release();
+ }
+
+ return hr;
+}
diff --git a/tests/projects/windows/driver/umdf/skeleton/driver.h b/tests/projects/windows/driver/umdf/skeleton/driver.h new file mode 100644 index 000000000..e764f517d --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/driver.h @@ -0,0 +1,149 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved
+
+Module Name:
+
+ Driver.h
+
+Abstract:
+
+ This module contains the type definitions for the UMDF Skeleton sample's
+ driver callback class.
+
+Environment:
+
+ Windows User-Mode Driver Framework (WUDF)
+
+--*/
+
+#pragma once
+
+//
+// This class handles driver events for the skeleton sample. In particular
+// it supports the OnDeviceAdd event, which occurs when the driver is called
+// to setup per-device handlers for a new device stack.
+//
+
+class CMyDriver : public CUnknown, public IDriverEntry
+{
+//
+// Private data members.
+//
+private:
+
+//
+// Private methods.
+//
+private:
+
+ //
+ // Returns a refernced pointer to the IDriverEntry interface.
+ //
+
+ IDriverEntry *
+ QueryIDriverEntry(
+ VOID
+ )
+ {
+ AddRef();
+ return static_cast<IDriverEntry*>(this);
+ }
+
+ HRESULT
+ Initialize(
+ VOID
+ );
+
+//
+// Public methods
+//
+public:
+
+ //
+ // The factory method used to create an instance of this driver.
+ //
+
+ static
+ HRESULT
+ CreateInstance(
+ _Out_ PCMyDriver *Driver
+ );
+
+//
+// COM methods
+//
+public:
+
+ //
+ // IDriverEntry methods
+ //
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ OnInitialize(
+ _In_ IWDFDriver *FxWdfDriver
+ )
+ {
+ UNREFERENCED_PARAMETER( FxWdfDriver );
+
+ return S_OK;
+ }
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ OnDeviceAdd(
+ _In_ IWDFDriver *FxWdfDriver,
+ _In_ IWDFDeviceInitialize *FxDeviceInit
+ );
+
+ virtual
+ VOID
+ STDMETHODCALLTYPE
+ OnDeinitialize(
+ _In_ IWDFDriver *FxWdfDriver
+ )
+ {
+ UNREFERENCED_PARAMETER( FxWdfDriver );
+
+ return;
+ }
+
+ //
+ // IUnknown methods.
+ //
+ // We have to implement basic ones here that redirect to the
+ // base class becuase of the multiple inheritance.
+ //
+
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ AddRef(
+ VOID
+ )
+ {
+ return __super::AddRef();
+ }
+
+ _At_(this, __drv_freesMem(object))
+ virtual
+ ULONG
+ STDMETHODCALLTYPE
+ Release(
+ VOID
+ )
+ {
+ return __super::Release();
+ }
+
+ virtual
+ HRESULT
+ STDMETHODCALLTYPE
+ QueryInterface(
+ _In_ REFIID InterfaceId,
+ _Out_ PVOID *Object
+ );
+};
diff --git a/tests/projects/windows/driver/umdf/skeleton/exports.def b/tests/projects/windows/driver/umdf/skeleton/exports.def new file mode 100644 index 000000000..7d46558b7 --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/exports.def @@ -0,0 +1,10 @@ +; Skeleton.def : Declares the module parameters.
+
+;
+; TODO: Change the library name here to match your binary name.
+;
+
+LIBRARY "UMDFSkeleton.DLL"
+
+EXPORTS
+ DllGetClassObject PRIVATE
diff --git a/tests/projects/windows/driver/umdf/skeleton/internal.h b/tests/projects/windows/driver/umdf/skeleton/internal.h new file mode 100644 index 000000000..f338a9b6c --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/internal.h @@ -0,0 +1,90 @@ +/*++
+
+Copyright (C) Microsoft Corporation, All Rights Reserved
+
+Module Name:
+
+ Internal.h
+
+Abstract:
+
+ This module contains the local type definitions for the UMDF Skeleton
+ driver sample.
+
+Environment:
+
+ Windows User-Mode Driver Framework (WUDF)
+
+--*/
+
+#pragma once
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
+
+//
+// Include the WUDF DDI
+//
+
+#include "wudfddi.h"
+
+//
+// Use specstrings for in/out annotation of function parameters.
+//
+
+#include "specstrings.h"
+
+//
+// Forward definitions of classes in the other header files.
+//
+
+typedef class CMyDriver *PCMyDriver;
+typedef class CMyDevice *PCMyDevice;
+
+//
+// Define the tracing flags.
+//
+// TODO: Choose a different trace control GUID
+//
+
+#define WPP_CONTROL_GUIDS \
+ WPP_DEFINE_CONTROL_GUID( \
+ MyDriverTraceControl, (e7541cdd,30e8,4b50,aeb0,51927330ae64), \
+ \
+ WPP_DEFINE_BIT(MYDRIVER_ALL_INFO) \
+ )
+
+#define WPP_FLAG_LEVEL_LOGGER(flag, level) \
+ WPP_LEVEL_LOGGER(flag)
+
+#define WPP_FLAG_LEVEL_ENABLED(flag, level) \
+ (WPP_LEVEL_ENABLED(flag) && \
+ WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)
+
+//
+// This comment block is scanned by the trace preprocessor to define our
+// Trace function.
+//
+// begin_wpp config
+// FUNC Trace{FLAG=MYDRIVER_ALL_INFO}(LEVEL, MSG, ...);
+// end_wpp
+//
+
+//
+// Driver specific #defines
+//
+// TODO: Change these values to be appropriate for your driver.
+//
+
+#define MYDRIVER_TRACING_ID L"Microsoft\\UMDF\\Skeleton"
+#define MYDRIVER_CLASS_ID { 0xd4112073, 0xd09b, 0x458f, { 0xa5, 0xaa, 0x35, 0xef, 0x21, 0xee, 0xf5, 0xde } }
+
+
+//
+// Include the type specific headers.
+//
+
+#include "comsup.h"
+#include "driver.h"
+#include "device.h"
diff --git a/tests/projects/windows/driver/umdf/skeleton/xmake.lua b/tests/projects/windows/driver/umdf/skeleton/xmake.lua new file mode 100644 index 000000000..8a48274ca --- /dev/null +++ b/tests/projects/windows/driver/umdf/skeleton/xmake.lua @@ -0,0 +1,13 @@ +add_rules("mode.debug", "mode.release") + +add_defines("_UNICODE", "UNICODE") + +target("UMDFSkeleton") + add_rules("wdk.env.umdf", "wdk.driver") + add_values("wdk.tracewpp.flags", "-scan:internal.h") + add_files("*.cpp", {rule = "wdk.tracewpp"}) + add_files("*.rc", "*.inx") + set_values("wdk.umdf.sdkver", "1.9") + add_shflags("/DEF:exports.def", {force = true}) + add_shflags("/ENTRY:_DllMainCRTStartup" .. (is_arch("x86") and "@12" or ""), {force = true}) + diff --git a/tests/projects/windows/driver/wdm/msdsm/SampleDSM.inf b/tests/projects/windows/driver/wdm/msdsm/SampleDSM.inf Binary files differnew file mode 100644 index 000000000..f632f6e50 --- /dev/null +++ b/tests/projects/windows/driver/wdm/msdsm/SampleDSM.inf diff --git a/tests/projects/windows/driver/wdm/msdsm/dsmmain.c b/tests/projects/windows/driver/wdm/msdsm/dsmmain.c new file mode 100644 index 000000000..bdf875184 --- /dev/null +++ b/tests/projects/windows/driver/wdm/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)¤tTickCount);
+ 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)¤tTickCount);
+
+ 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)¤tTickCount);
+ 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)¤tTickCount);
+
+ 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/windows/driver/wdm/msdsm/dsmtrace.mof b/tests/projects/windows/driver/wdm/msdsm/dsmtrace.mof new file mode 100644 index 000000000..d9e8cf4cc --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/intrface.c b/tests/projects/windows/driver/wdm/msdsm/intrface.c new file mode 100644 index 000000000..eeebf7f93 --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/msdsm.h b/tests/projects/windows/driver/wdm/msdsm/msdsm.h new file mode 100644 index 000000000..e1ddeb52d --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/msdsm.mof b/tests/projects/windows/driver/wdm/msdsm/msdsm.mof new file mode 100644 index 000000000..53ee61392 --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/msdsm.rc b/tests/projects/windows/driver/wdm/msdsm/msdsm.rc new file mode 100644 index 000000000..743640342 --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/msdsmdsm.mof b/tests/projects/windows/driver/wdm/msdsm/msdsmdsm.mof new file mode 100644 index 000000000..f85eea270 --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/precomp.h b/tests/projects/windows/driver/wdm/msdsm/precomp.h new file mode 100644 index 000000000..bca8595db --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/precompsrc.c b/tests/projects/windows/driver/wdm/msdsm/precompsrc.c new file mode 100644 index 000000000..5944cf515 --- /dev/null +++ b/tests/projects/windows/driver/wdm/msdsm/precompsrc.c @@ -0,0 +1 @@ +#include "precomp.h"
\ No newline at end of file diff --git a/tests/projects/windows/driver/wdm/msdsm/prototypes.h b/tests/projects/windows/driver/wdm/msdsm/prototypes.h new file mode 100644 index 000000000..ebcf42029 --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/trace.h b/tests/projects/windows/driver/wdm/msdsm/trace.h new file mode 100644 index 000000000..b9449cc8f --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/utils.c b/tests/projects/windows/driver/wdm/msdsm/utils.c new file mode 100644 index 000000000..91d8f44ce --- /dev/null +++ b/tests/projects/windows/driver/wdm/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, ¶metersKey);
+
+ 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, ¶metersKey);
+
+ 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(¶metersKeyName, DSM_SERVICE_PARAMETERS);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ ¶metersKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ serviceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(¶metersKey,
+ 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(¶metersKeyName, DSM_SERVICE_PARAMETERS);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ ¶metersKeyName,
+ (OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE),
+ serviceKey,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwOpenKey(¶metersKey,
+ 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(¶metersKeyName, DSM_SERVICE_PARAMETERS);
+
+ RtlZeroMemory(&objectAttributes, sizeof(OBJECT_ATTRIBUTES));
+
+ InitializeObjectAttributes(&objectAttributes,
+ ¶metersKeyName,
+ (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, ¶metersKey);
+
+ 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/windows/driver/wdm/msdsm/wmi.c b/tests/projects/windows/driver/wdm/msdsm/wmi.c new file mode 100644 index 000000000..0ee882192 --- /dev/null +++ b/tests/projects/windows/driver/wdm/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/windows/driver/wdm/msdsm/xmake.lua b/tests/projects/windows/driver/wdm/msdsm/xmake.lua new file mode 100644 index 000000000..3c68aa1b8 --- /dev/null +++ b/tests/projects/windows/driver/wdm/msdsm/xmake.lua @@ -0,0 +1,15 @@ +add_rules("mode.debug", "mode.release") + +target("sampledsm") + add_rules("wdk.env.wdm", "wdk.driver") + add_values("wdk.tracewpp.flags", "-func:TracePrint((LEVEL,FLAGS,MSG,...))") + add_files("*.c", {rule = "wdk.tracewpp"}) + add_files("*.rc", "*.inf") + add_files("*.mof|msdsm.mof") + + -- add file msdsm.mof and modify default wdk.mof.header for this file + add_files("msdsm.mof", {values = {wdk_mof_header = "msdsmwmi.h"}}) + + set_pcheader("precomp.h") + add_links("mpio") + diff --git a/tests/projects/windows/driver/wdm/perfcounters/kcs.c b/tests/projects/windows/driver/wdm/perfcounters/kcs.c new file mode 100644 index 000000000..9996addcb --- /dev/null +++ b/tests/projects/windows/driver/wdm/perfcounters/kcs.c @@ -0,0 +1,407 @@ +/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ kcs.c
+
+Abstract:
+
+ This module contains sample code to demonstrate how to provide
+ counter data from a kernel driver.
+
+Environment:
+
+ Kernel mode only.
+
+--*/
+
+
+#include <wdm.h>
+#include "kcs.h"
+#include "kcsCounters.h"
+
+#pragma code_seg("PAGE")
+
+DRIVER_INITIALIZE DriverEntry;
+DRIVER_UNLOAD KcsUnload;
+
+NTSTATUS
+KcsAddGeometricInstance (
+ _In_ PPCW_BUFFER Buffer,
+ _In_ PCWSTR Name,
+ _In_ ULONG MinimalValue,
+ _In_ ULONG Amplitude
+ )
+
+/*++
+
+Routine Description:
+
+ This utility function adds instance to the callback buffer.
+
+Arguments:
+
+ Buffer - Data will be returned in this buffer.
+
+ Name - Name of instances to be added.
+
+ MinimalValue - Minimum value of the wave.
+
+ Amplitude - Amplitude of the wave.
+
+Return Value:
+
+ NTSTATUS indicating if the function succeeded.
+
+--*/
+
+{
+ ULONG Index;
+ LARGE_INTEGER Timestamp;
+ UNICODE_STRING UnicodeName;
+ GEOMETRIC_WAVE_VALUES Values;
+
+ PAGED_CODE();
+
+ KeQuerySystemTime(&Timestamp);
+
+ Index = (Timestamp.QuadPart / 10000000) % 10;
+
+ Values.Triangle = MinimalValue + Amplitude * abs(5 - Index) / 5;
+ Values.Square = MinimalValue + Amplitude * (Index < 5);
+
+ RtlInitUnicodeString(&UnicodeName, Name);
+
+ return KcsAddGeometricWave(Buffer, &UnicodeName, 0, &Values);
+}
+
+NTSTATUS NTAPI
+KcsGeometricWaveCallback (
+ _In_ PCW_CALLBACK_TYPE Type,
+ _In_ PPCW_CALLBACK_INFORMATION Info,
+ _In_opt_ PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This function returns the list of counter instances and counter data.
+
+Arguments:
+
+ Type - Request type.
+
+ Info - Buffer for returned data.
+
+ Context - Not used.
+
+Return Value:
+
+ NTSTATUS indicating if the function succeeded.
+
+--*/
+
+{
+ NTSTATUS Status;
+ UNICODE_STRING UnicodeName;
+
+ UNREFERENCED_PARAMETER(Context);
+
+ PAGED_CODE();
+
+ switch (Type) {
+ case PcwCallbackEnumerateInstances:
+
+ //
+ // Instances are being enumerated, so we add them without values.
+ //
+
+ RtlInitUnicodeString(&UnicodeName, L"Small Wave");
+ Status = KcsAddGeometricWave(Info->EnumerateInstances.Buffer,
+ &UnicodeName,
+ 0,
+ NULL);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ RtlInitUnicodeString(&UnicodeName, L"Medium Wave");
+ Status = KcsAddGeometricWave(Info->EnumerateInstances.Buffer,
+ &UnicodeName,
+ 0,
+ NULL);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ RtlInitUnicodeString(&UnicodeName, L"Large Wave");
+ Status = KcsAddGeometricWave(Info->EnumerateInstances.Buffer,
+ &UnicodeName,
+ 0,
+ NULL);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ break;
+
+ case PcwCallbackCollectData:
+
+ //
+ // Add values for 3 instances of Geometric Wave Counter Set.
+ //
+
+ Status = KcsAddGeometricInstance(Info->CollectData.Buffer,
+ L"Small Wave",
+ 40,
+ 20);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ Status = KcsAddGeometricInstance(Info->CollectData.Buffer,
+ L"Medium Wave",
+ 30,
+ 40);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ Status = KcsAddGeometricInstance(Info->CollectData.Buffer,
+ L"Large Wave",
+ 20,
+ 60);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ break;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+KcsAddTrignometricInstance (
+ _In_ PPCW_BUFFER Buffer,
+ _In_ PCWSTR Name,
+ _In_ ULONG MinimalValue,
+ _In_ ULONG Amplitude
+ )
+
+/*++
+
+Routine Description:
+
+ This utility function adds instance to the callback buffer.
+
+Arguments:
+
+ Buffer - Data will be returned in this buffer.
+
+ Name - Name of instances to be added.
+
+ MinimalValue - Minimum value of the wave.
+
+ Amplitude - Amplitude of the wave.
+
+Return Value:
+
+ NTSTATUS indicating if the function succeeded.
+
+--*/
+
+{
+ double Angle;
+ KFLOATING_SAVE FloatSave;
+ NTSTATUS Status;
+ LARGE_INTEGER Timestamp;
+ UNICODE_STRING UnicodeName;
+ TRIGNOMETRIC_WAVE_VALUES Values;
+
+ PAGED_CODE();
+
+ Status = KeSaveFloatingPointState(&FloatSave);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ KeQuerySystemTime(&Timestamp);
+
+ Angle = (double)(Timestamp.QuadPart / 400000) * (22/7) / 180;
+
+ Values.Constant = MinimalValue;
+ Values.Cosine = (ULONG)(MinimalValue + Amplitude * cos(Angle));
+ Values.Sine = (ULONG)(MinimalValue + Amplitude * sin(Angle));
+
+ KeRestoreFloatingPointState(&FloatSave);
+
+ //
+ // Add instance name & values to the caller's buffer.
+ //
+
+ RtlInitUnicodeString(&UnicodeName, Name);
+
+ return KcsAddTrignometricWave(Buffer, &UnicodeName, 0, &Values);
+}
+
+NTSTATUS NTAPI
+KcsTrignometricWaveCallback (
+ _In_ PCW_CALLBACK_TYPE Type,
+ _In_ PPCW_CALLBACK_INFORMATION Info,
+ _In_opt_ PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This function returns the list of counter instances and counter data.
+
+Arguments:
+
+ Type - Request type.
+
+ Info - Buffer for returned data.
+
+ Context - Not used.
+
+Return Value:
+
+ NTSTATUS indicating if the function succeeded.
+
+--*/
+
+{
+ NTSTATUS Status;
+ UNICODE_STRING UnicodeName;
+
+ UNREFERENCED_PARAMETER(Context);
+
+ PAGED_CODE();
+
+ switch (Type) {
+ case PcwCallbackEnumerateInstances:
+ RtlInitUnicodeString(&UnicodeName, L"default");
+ Status = KcsAddTrignometricWave(Info->EnumerateInstances.Buffer,
+ &UnicodeName,
+ 0,
+ NULL);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ break;
+
+ case PcwCallbackCollectData:
+
+ //
+ // Add values for Single Instance of Trignometirc Wave Counter Set.
+ //
+
+ return KcsAddTrignometricInstance(Info->CollectData.Buffer,
+ L"default",
+ 50,
+ 30);
+ }
+
+ return STATUS_SUCCESS;
+}
+
+VOID
+KcsUnload (
+ _In_ PDRIVER_OBJECT DriverObject
+ )
+
+/*++
+
+Routine Description:
+
+ This function unregisters countersets
+
+Arguments:
+
+ DriverObject - Not used.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER(DriverObject);
+
+ PAGED_CODE();
+
+ //
+ // Unregister Countersets.
+ //
+
+ KcsUnregisterGeometricWave();
+ KcsUnregisterTrignometricWave();
+}
+
+NTSTATUS
+DriverEntry (
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_ PUNICODE_STRING RegistryPath
+ )
+
+/*++
+
+Routine Description:
+
+ This function registers countersets on initial loading of the driver.
+
+Arguments:
+
+ DriverObject - Supplies the driver object of the driver being loaded.
+
+ RegistryPath - Not used.
+
+Return Value:
+
+ NTSTATUS indicating if driver was properly loaded.
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ UNREFERENCED_PARAMETER(RegistryPath);
+
+ PAGED_CODE();
+
+ //
+ // Register Countersets.
+ //
+
+ Status = KcsRegisterGeometricWave(KcsGeometricWaveCallback, NULL);
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ Status = KcsRegisterTrignometricWave(KcsTrignometricWaveCallback, NULL);
+ if (!NT_SUCCESS(Status)) {
+ KcsUnregisterTrignometricWave();
+ return Status;
+ }
+
+ //
+ // Success path - set up unload routine and return success.
+ //
+
+ DriverObject->DriverUnload = KcsUnload;
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/tests/projects/windows/driver/wdm/perfcounters/kcs.h b/tests/projects/windows/driver/wdm/perfcounters/kcs.h new file mode 100644 index 000000000..f575b816d --- /dev/null +++ b/tests/projects/windows/driver/wdm/perfcounters/kcs.h @@ -0,0 +1,34 @@ +/*++
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+Module Name:
+
+ kcs.h
+
+Abstract:
+
+ This module contains sample code to demonstrate how to provide
+ counter data from a kernel driver.
+
+Environment:
+
+ Kernel mode only.
+
+--*/
+
+typedef struct _GEOMETRIC_WAVE_VALUES {
+ ULONG Square;
+ ULONG Triangle;
+} GEOMETRIC_WAVE_VALUES, *PGEOMETRIC_WAVE_VALUES;
+
+typedef struct _TRIGNOMETRIC_WAVE_VALUES {
+ ULONG Constant;
+ ULONG Cosine;
+ ULONG Sine;
+} TRIGNOMETRIC_WAVE_VALUES, *PTRIGNOMETRIC_WAVE_VALUES;
\ No newline at end of file diff --git a/tests/projects/windows/driver/wdm/perfcounters/kcs.man b/tests/projects/windows/driver/wdm/perfcounters/kcs.man new file mode 100644 index 000000000..a5a16ffbd --- /dev/null +++ b/tests/projects/windows/driver/wdm/perfcounters/kcs.man @@ -0,0 +1,101 @@ +<instrumentationManifest + xmlns="http://schemas.microsoft.com/win/2004/08/events" + xmlns:trace="http://schemas.microsoft.com/win/2004/08/events/trace" + xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events eventman.xsd" + > + <instrumentation> + <counters + xmlns="http://schemas.microsoft.com/win/2005/12/counters" + xmlns:auto-ns1="http://schemas.microsoft.com/win/2004/08/events" + schemaVersion="1.1" + > + <provider callback = "custom" + applicationIdentity = "Kcs.sys" + providerType = "kernelMode" + providerName = "KernelCountersSample" + providerGuid = "{d2ffffff-965a-4cf9-9c07-fe25378c2a23}"> + <counterSet guid = "{d2ffffff-c923-4794-b696-70577630b5cf}" + uri = "Microsoft.Wdk.Samples.Kcs.GeometricWave" + name = "Geometric Waves" + description = "This counter set displays a Triangle and a Square wave" + symbol = "GeometricWave" + instances = "multipleAggregate" + > + <structs> + <struct name="GeometricWaveValues" type="GEOMETRIC_WAVE_VALUES"/> + </structs> + <counter id = "1" + uri = "Microsoft.Wdk.Samples.Kcs.GeometricWave.Triangle" + name = "Triangle" + struct = "GeometricWaveValues" + field = "Triangle" + description = "This counter displays triangle wave" + aggregate = "avg" + type = "perf_counter_rawcount" + detailLevel = "standard"> + </counter> + + <counter id = "2" + uri = "Microsoft.Wdk.Samples.Kcs.GeometricWave.Square" + name = "Square Wave" + struct = "GeometricWaveValues" + field = "Square" + description = "This counter displays Square Wave" + aggregate = "avg" + type = "perf_counter_rawcount" + detailLevel = "standard"> + </counter> + </counterSet> + <counterSet guid = "{ffffffff-eaa6-45ba-bf6d-4c7cb0d6ef73}" + uri = "Microsoft.Wdk.Samples.Kcs.TrignometricWave" + name = "Trignometric Waves" + description = "This counter set displays a sine, cosine and a constant wave" + symbol = "TrignometricWave" + instances = "single"> + <structs> + <struct name="TrignometricWaveValues" type="TRIGNOMETRIC_WAVE_VALUES"/> + </structs> + <counter id = "1" + uri = "Microsoft.Wdk.Samples.Kcs.TrignometricWave.Sine" + name = "Sine Wave" + description = "This counter displays Sine Wave" + struct = "TrignometricWaveValues" + field = "Sine" + type = "perf_counter_rawcount" + detailLevel = "standard"> + <counterAttributes> + <counterAttribute name = "reference" /> + </counterAttributes> + </counter> + <counter id = "2" + uri = "Microsoft.Wdk.Samples.Kcs.TrignometricWave.Cosine" + name = "Cosine Wave" + description = "This counter displays Cosine Wave" + struct = "TrignometricWaveValues" + field = "Cosine" + type = "perf_counter_rawcount" + detailLevel = "standard"> + <counterAttributes> + <counterAttribute name = "reference" /> + </counterAttributes> + </counter> + <counter id = "3" + uri = "Microsoft.Wdk.Samples.Kcs.TrignometricWave.Constant" + name = "Constant Value" + description = "This counter displays Constant Value" + struct = "TrignometricWaveValues" + field = "Constant" + type = "perf_counter_rawcount" + detailLevel = "standard"> + <counterAttributes> + <counterAttribute name = "reference" /> + </counterAttributes> + </counter> + </counterSet> + </provider> + </counters> + </instrumentation> +</instrumentationManifest>
\ No newline at end of file diff --git a/tests/projects/windows/driver/wdm/perfcounters/kcs.rc b/tests/projects/windows/driver/wdm/perfcounters/kcs.rc new file mode 100644 index 000000000..7ebb6b97b --- /dev/null +++ b/tests/projects/windows/driver/wdm/perfcounters/kcs.rc @@ -0,0 +1 @@ +#include "kcsCounters.rc"
diff --git a/tests/projects/windows/driver/wdm/perfcounters/xmake.lua b/tests/projects/windows/driver/wdm/perfcounters/xmake.lua new file mode 100644 index 000000000..83e4e4d9b --- /dev/null +++ b/tests/projects/windows/driver/wdm/perfcounters/xmake.lua @@ -0,0 +1,10 @@ +add_rules("mode.debug", "mode.release") + +target("kcs") + add_rules("wdk.env.wdm", "wdk.driver") + add_values("wdk.man.prefix", "Kcs") + add_values("wdk.man.resource", "kcsCounters.rc") + add_values("wdk.man.header", "kcsCounters.h") + add_values("wdk.man.counter_header", "kcsCounters_counters.h") + add_files("*.c", "*.rc", "*.man") + |
