将 KMDF 驱动程序连接到 GPIO I/O 管脚

GPIO I/O 资源是一组配置为数据输入或数据输出的一个或多个 GPIO 引脚。 物理连接到这些引脚的外围设备的驱动程序从操作系统获取相应的 GPIO I/O 资源。 外围设备驱动程序会打开与此资源中的 GPIO 引脚的连接,并将 I/O 请求发送到表示此连接的句柄。

下面的代码示例演示了外围设备的内核模式驱动程序框架 (KMDF) 驱动程序如何获取即插即用 (PnP) 管理器分配给驱动程序的 GPIO I/O 资源的说明。

NTSTATUS
  EvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourcesRaw,
    _In_ WDFCMRESLIST ResourcesTranslated
    )
{
    int ResourceCount, Index;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor;
    XYZ_DEVICE_CONTEXT *DeviceExtension;

    ...

    DeviceExtension = XyzDrvGetDeviceExtension(Device);
    ResourceCount = WdfCmResourceListGetCount(ResourcesTranslated);
    for (Index = 0; Index < ResourceCount; Index += 1) {
        Descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, Index);
        switch (Descriptor->Type) {

        //
        // GPIO I/O descriptors
        //

        case CmResourceTypeConnection:

            //
            // Check against expected connection type.
            //

            if ((Descriptor->u.Connection.Class == CM_RESOURCE_CONNECTION_CLASS_GPIO) &&
                (Descriptor->u.Connection.Type == CM_RESOURCE_CONNECTION_TYPE_GPIO_IO)) {

                DeviceExtension->ConnectionId.LowPart = Descriptor->u.Connection.IdLowPart;
                DeviceExtension->ConnectionId.HighPart = Descriptor->u.Connection.IdHighPart;

        ...

}

在前面的代码示例中 DeviceExtension , 变量是指向外围设备的设备上下文的指针。 用于 XyzDrvGetDeviceExtension 检索此设备上下文的函数由外围设备驱动程序实现。 此驱动程序以前通过调用 WdfDeviceInitSetPnpPowerEventCallbacks 方法注册了其 EvtDevicePrepareHardware 回调函数。

下面的代码示例演示了外围设备驱动程序如何使用它在上一代码示例中获取的 GPIO 资源说明打开驱动程序 GPIO I/O 资源的 WDFIOTARGET 句柄。

NTSTATUS IoRoutine(WDFDEVICE Device, BOOLEAN ReadOperation) 
{
    WDFIOTARGET IoTarget;
    XYZ_DEVICE_CONTEXT *DeviceExtension;
    UNICODE_STRING ReadString;
    WCHAR ReadStringBuffer[100];;
    BOOL DesiredAccess;
    NTSTATUS Status;
    WDF_OBJECT_ATTRIBUTES ObjectAttributes;
    WDF_IO_TARGET_OPEN_PARAMS OpenParams

    DeviceExtension = XyzDrvGetDeviceExtension(Device);
    RtlInitEmptyUnicodeString(&ReadString,
                              ReadStringBuffer,
                              sizeof(ReadStringBuffer));

    Status = RESOURCE_HUB_CREATE_PATH_FROM_ID(&ReadString,
                                              DeviceExtension->ConnectionId.LowPart,
                                              DeviceExtension->ConnectionId.HighPart);

    NT_ASSERT(NT_SUCCESS(Status));

    WDF_OBJECT_ATTRIBUTES_INIT(&ObjectAttributes);
    ObjectAttributes.ParentObject = Device;

    Status = WdfIoTargetCreate(Device, &ObjectAttributes, &IoTarget);
    if (!NT_SUCCESS(Status)) {
        goto IoErrorEnd;
    }   

    if (ReadOperation != FALSE) {
        DesiredAccess = GENERIC_READ;
    } else {
        DesiredAccess = GENERIC_WRITE;
    }

    WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&OpenParams, ReadString, DesiredAccess);

    Status = WdfIoTargetOpen(IoTarget, &OpenParams);
    if (!NT_SUCCESS(Status)) {
        goto IoErrorEnd;
    }
    ...

在前面的代码示例中 Device ,变量是外围设备的框架设备对象的 WDFDEVICE 句柄。 RESOURCE_HUB_CREATE_PATH_FROM_ID 函数创建一个字符串,其中包含 GPIO I/O 资源的名称。 代码示例使用此字符串按名称打开 GPIO I/O 资源。

外围设备驱动程序获取 GPIO I/O 资源的句柄后,此驱动程序可以发送 I/O 控制请求,以从 GPIO 引脚读取数据或将数据写入数据。 为读取打开 GPIO I/O 资源的驱动程序使用 IOCTL_GPIO_READ_PINS I/O 控制请求从资源中的引脚读取数据。 打开 GPIO I/O 资源进行写入的驱动程序使用 IOCTL_GPIO_WRITE_PINS I/O 控制请求将数据写入资源中的引脚。 下面的代码示例演示如何执行 GPIO 读取或写入操作。

    WDF_OBJECT_ATTRIBUTES RequestAttributes;
    WDF_OBJECT_ATTRIBUTES Attributes;
    WDF_REQUEST_SEND_OPTIONS SendOptions;
    WDFREQUEST IoctlRequest;
    WDFIOTARGET IoTarget;
    WDFMEMORY WdfMemory;
    NTSTATUS Status;

    WDF_OBJECT_ATTRIBUTES_INIT(&RequestAttributes);
    Status = WdfRequestCreate(&RequestAttributes, IoTarget, &IoctlRequest);
    if (!NT_SUCCESS(Status)) {
        goto RwErrorExit;
    }

    //
    // Set up a WDF memory object for the IOCTL request.
    //

    WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
    Attributes.ParentObject = IoctlRequest;
    Status = WdfMemoryCreatePreallocated(&Attributes, Data, Size, &WdfMemory);
    if (!NT_SUCCESS(Status)) {
        goto RwErrorExit;
    }

    //
    // Format the request.
    //

    if (ReadOperation != FALSE) {
        Status = WdfIoTargetFormatRequestForIoctl(IoTarget,
                                                  IoctlRequest,
                                                  IOCTL_GPIO_READ_PINS,
                                                  NULL,
                                                  0,
                                                  WdfMemory,
                                                  0);

    } else {
        Status = WdfIoTargetFormatRequestForIoctl(IoTarget,
                                                  IoctlRequest,
                                                  IOCTL_GPIO_WRITE_PINS,
                                                  WdfMemory,
                                                  0,
                                                  WdfMemory,
                                                  0);
    }

    if (!NT_SUCCESS(Status)) {
        goto RwErrorExit;
    }

    //
    // Send the request synchronously (with a 60-second time-out).
    //

    WDF_REQUEST_SEND_OPTIONS_INIT(&SendOptions,
                                  WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);
    WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&SendOptions,
                                         WDF_REL_TIMEOUT_IN_SEC(60));

    Status = WdfRequestAllocateTimer(IoctlRequest);
    if (!NT_SUCCESS(Status)) {
        goto RwErrorExit;
    }

    if (!WdfRequestSend(IoctlRequest, IoTarget, &SendOptions)) {
        Status = WdfRequestGetStatus(IoctlRequest);
    }

    ...

在前面的代码示例中, Data 是指向数据缓冲区的指针, Size 是此数据缓冲区的大小(以字节为单位),并 ReadOperation 指示请求的操作是读取 (TRUE) 还是写入 (FALSE) 。

更多信息

有关 IOCTL_GPIO_READ_PINS 请求的详细信息,包括将数据输入引脚映射到请求输出缓冲区中的位,请参阅 IOCTL_GPIO_READ_PINS。 有关 IOCTL_GPIO_WRITE_PINS 请求的详细信息,包括将请求输入缓冲区中的位映射到数据输出引脚,请参阅 IOCTL_GPIO_WRITE_PINS

有关演示如何编写在内核模式下运行的 GPIO 外围驱动程序的示例驱动程序,请参阅 GitHub 上的 GPIO 示例驱动程序 集合中的 SimDevice 示例驱动程序。