Hardware Resources for Kernel-Mode SPB Peripheral Drivers

The code examples in this topic show how the Kernel-Mode Driver Framework (KMDF) driver for a peripheral device on a simple peripheral bus (SPB) obtains the hardware resources that it requires to operate the device. Included in these resources is the information that the driver uses to establish a logical connection to the device. Additional resources might include an interrupt and one or more GPIO input or output pins. (A GPIO pin is a pin on a general-purpose I/O controller device that is configured as an input or an output; for more information, see General-Purpose I/O (GPIO) Drivers.) Unlike a device that is memory-mapped, an SPB-connected peripheral device doesn't require a block of system memory addresses to map its registers into.

This driver implements a set of Plug and Play and power management event callback functions. To register these functions with KMDF, the driver's EvtDriverDeviceAdd event callback function calls the WdfDeviceInitSetPnpPowerEventCallbacks method. The framework calls the power management event callback functions to notify the driver of changes in the power state of the peripheral device. Included in these functions is the EvtDevicePrepareHardware function, which performs any operations that are needed to make the device accessible to the driver.

When power is restored to the peripheral device, the driver framework calls the EvtDevicePrepareHardware function to notify the SPB peripheral driver that this device must be prepared for use. During this call, the driver receives two lists of hardware resources as input parameters. The ResourcesRaw parameter is a WDFCMRESLIST object handle to the list of raw resources, and the ResourcesTranslated parameter is a WDFCMRESLIST object handle to the list of translated resources. The translated resources include the connection ID that the driver requires to establish a logical connection to the peripheral device. For more information, see Connection IDs for SPB-Connected Peripheral Devices.

The following code example shows how the EvtDevicePrepareHardware function obtains the connection ID from the ResourcesTranslated parameter.

BOOLEAN fConnectionIdFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;
NTSTATUS status = STATUS_SUCCESS;

resourceCount = WdfCmResourceListGetCount(ResourcesTranslated);

// Loop through the resources and save the relevant ones.

for (ULONG ix = 0; ix < resourceCount; ix++)
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;

    pDescriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, ix);

    if (pDescriptor == NULL)
    {
        status = E_POINTER;
        break;
    }

    // Determine the resource type.
    switch (pDescriptor->Type)
    {
    case CmResourceTypeConnection:
        {
            // Check against the expected connection types.

            UCHAR Class = pDescriptor->u.Connection.Class;
            UCHAR Type = pDescriptor->u.Connection.Type;

            if (Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL)
            {
                if (Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_I2C)
                {
                    if (fConnectionIdFound == FALSE)
                    {
                        // Save the SPB connection ID.

                        connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
                        connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
                        fConnectionIdFound = TRUE;
                    }
                }
            }

            if (Class == CM_RESOURCE_CONNECTION_CLASS_GPIO)
            {
                // Check for GPIO pin resource.
                ...
            }
        }
        break;

    case CmResourceTypeInterrupt:
        {
            // Check for interrupt resource.
            ...
        }
        break;

    default:
        // Don't care about other resource descriptors.
        break;
    }
}

The preceding code example copies the connection ID for an SPB-connected peripheral device into a variable named connectionId.

The following code example shows how to incorporate this connection ID into a device path name that can be used to open a logical connection to the peripheral device. This device path name identifies the resource hub as the system component from which to obtain the parameters required to access the peripheral device.

// Use the connection ID to create the full device path name.
 
DECLARE_UNICODE_STRING_SIZE(szDeviceName, RESOURCE_HUB_PATH_SIZE);

status = RESOURCE_HUB_CREATE_PATH_FROM_ID(&szDeviceName,
                                          connectionId.LowPart,
                                          connectionId.HighPart);

if (!NT_SUCCESS(status))
{
     // Error handling
     ...
}

In the preceding code example, the DECLARE_UNICODE_STRING_SIZE macro creates the declaration of an initialized UNICODE_STRING variable named szDeviceName that has a buffer large enough to contain a device path name in the format used by the resource hub. This macro is defined in the Ntdef.h header file. The RESOURCE_HUB_PATH_SIZE constant specifies the number of bytes in the device path name. The RESOURCE_HUB_CREATE_PATH_FROM_ID macro generates the device path name from the connection ID. RESOURCE_HUB_PATH_SIZE and RESOURCE_HUB_CREATE_PATH_FROM_ID are defined in the Reshub.h header file.

The following code example uses the device path name to open a file handle (named SpbIoTarget) to the SPB-connected peripheral device.

// Open the SPB peripheral device as a remote I/O target.
 
WDF_IO_TARGET_OPEN_PARAMS openParams;
WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&openParams,
                                            &szDeviceName,
                                            (GENERIC_READ | GENERIC_WRITE));

openParams.ShareAccess = 0;
openParams.CreateDisposition = FILE_OPEN;
openParams.FileAttributes = FILE_ATTRIBUTE_NORMAL;

status = WdfIoTargetOpen(SpbIoTarget, &openParams);

if (!NT_SUCCESS(status))
{
    // Error handling
    ...
}

In the preceding code example, the WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME function initializes the WDF_IO_TARGET_OPEN_PARAMS structure so the driver can open a logical connection to the peripheral device by specifying the name of the device. The SpbIoTarget variable is a WDFIOTARGET handle to a framework I/O target object. This handle was obtained from a previous call to the WdfIoTargetCreate method, which is not shown in the example. If the call to the WdfIoTargetOpen method succeeds, the driver can use the SpbIoTarget handle to send I/O requests to the peripheral device.

In the EvtDriverDeviceAdd event callback function, the SPB peripheral driver can call the WdfRequestCreate method to allocate a framework request object for use by the driver. Later, when the object is no longer needed, the driver calls the WdfObjectDelete method to delete the object. The driver can reuse the framework request object obtained from the WdfRequestCreate call multiple times to send I/O requests to the peripheral device. For a read, write, or IOCTL request, the driver calls the WdfIoTargetSendReadSynchronously, WdfIoTargetSendWriteSynchronously, or WdfIoTargetSendIoctlSynchronously method to send the request.

In the following code example, the driver calls WdfIoTargetSendWriteSynchronously to synchronously send an IRP_MJ_WRITE request to the SPB-connected peripheral device. At the start of this example, the pBuffer variable points to a nonpaged buffer that contains the data that is to be written to the peripheral device, and the dataSize variable specifies the size, in bytes, of this data.

ULONG_PTR bytesWritten;
NTSTATUS status;

// Describe the input buffer.

WDF_MEMORY_DESCRIPTOR memoryDescriptor;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, pBuffer, dataSize);

// Configure the write request to time out after 2 seconds.

WDF_REQUEST_SEND_OPTIONS requestOptions;
WDF_REQUEST_SEND_OPTIONS_INIT(&requestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT);
requestOptions.Timeout = WDF_REL_TIMEOUT_IN_SEC(2);

// Send the write request synchronously.

status = WdfIoTargetSendWriteSynchronously(SpbIoTarget,
                                           SpbRequest,
                                           &memoryDescriptor,
                                           NULL,
                                           &requestOptions,
                                           &bytesWritten);
if (!NT_SUCCESS(status))
{
    // Error handling
    ...
}

The preceding code example does the following:

  1. The WDF_MEMORY_DESCRIPTOR_INIT_BUFFER function call initializes the memoryDescriptor variable, which is a WDF_MEMORY_DESCRIPTOR structure that describes the input buffer. Previously, the driver called a routine such as ExAllocatePoolWithTag to allocate the buffer from nonpaged pool, and copied the write data to this buffer.
  2. The WDF_REQUEST_SEND_OPTIONS_INIT function call initializes the requestOptions variable, which is a WDF_REQUEST_SEND_OPTIONS structure that contains the optional settings for the write request. In this example, the structure configures the request to time out if it doesn't complete after two seconds.
  3. The call to the WdfIoTargetSendWriteSynchronously method sends the write request to the SPB-connected peripheral device. The method returns synchronously, after the write operation completes or times out. If necessary, another driver thread can call WdfRequestCancelSentRequest to cancel the request.

In the WdfIoTargetSendWriteSynchronously call, the driver supplies a variable named SpbRequest, which is a handle to a framework request object that the driver previously created. After the WdfIoTargetSendWriteSynchronously call, the driver should typically call the WdfRequestReuse method to prepare the framework request object to be used again.