Windows Phone 8.1 驱动开发——GPIO Device

在上一节 Windows Phone 8.1 驱动开发——GPIO 简介 中,我们了解了Windows 8系统中GPIO驱动的大体架构,由于在工作中手机驱动开发人员很少涉及到GPIO Controller驱动的开发,该部分都由平台厂商开发完成,所以这里给大家讲解一下GPIO Peripheral Device Driver的开发步骤。


本文以微软官方提供的GPIO Sample为例进行讲解,你也可以到MSDN官网进行源码下载:GPIO Sample Drivers


电路示意图

根据该Sample Code,本人画了一张电路示意图,如下:



ACPI资源配置

编写驱动之前,我们首先看一下在ACPI配置表中,GPIO I/O Resource的分配:

       //
       // Sample peripheral device
       //
       Device (DEV1) {
         Name (_HID, "TEST0001")
         Name (_CID, "TEST0001")
         Name (_UID, 1)

         Method (_CRS, 0x0, NotSerialized) {
           Name (RBUF, ResourceTemplate () {

             // GPIO Interrupt Resources
             GpioInt(Edge, ActiveHigh, Shared, PullUp, 0, "\\_SB.GPIO", 0, ResourceConsumer,, RawDataBuffer() {1}) {1}
             
             // GPIO IO Resources
             GpioIo(Exclusive, PullUp, 0, 0,, "\\_SB.GPIO",0, ResourceConsumer, , RawDataBuffer() {1}) {10}
             GpioIo(Exclusive, PullUp, 0, 0,, "\\_SB.GPIO",0, ResourceConsumer, , RawDataBuffer() {1}) {11}
           })

           Return (RBUF)
         }

         Method (_STA, 0x0, NotSerialized) {
             Return(0xf)
         }
       }

从资源表中可以看到,该设备DEV1使用了GPIO10、GPIO11和GPIO1三个引脚,其中GPIO10/11用作普通IO口,而GPIO1则用作中断输入引脚。至于这里对GPIO10/11的引脚配置到底作输入还是输出,是否上拉等,其实大家并不用关心,因为在电源管理中,也就是加载PEP模块的时候,会重新根据电源管理中的配置文件(pep_common.asl)进行相应设置,这里的GpioIo方法只是告诉Peripheral Driver该设备使用到了哪些I/O引脚。同样,GpioInt方法也只是告诉驱动当前设备使用到那个GPIO作外部中断引脚,至于具体的配置细节也是在电源管理配置文件中进行描述的。


获取I/O资源

当PnP Manager加载设备时,会在EvtDevicePrepareHardware回调函数中,去读取I/O资源:

NTSTATUS
SampleDrvEvtDevicePrepareHardware (
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourcesRaw,
    _In_ WDFCMRESLIST ResourcesTranslated
    )
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor;
    PSAMPLE_DRV_DEVICE_EXTENSION SampleDrvExtension;
    ULONG Index;
    ULONG ResourceCount;

    ...

    SampleDrvExtension = SampleDrvGetDeviceExtension(Device);
    //
    // Walk through the resource list and map all the resources. Only two
    // I/O resource and one interrupt is expected.
    //
    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)) {

                SampleDrvExtension->ConnectionIds[IoResourceIndex].LowPart = Descriptor->u.Connection.IdLowPart;
                SampleDrvExtension->ConnectionIds[IoResourceIndex].HighPart = Descriptor->u.Connection.IdHighPart;
            }
            break;

        //
        //  Interrupt resource
        //
        case CmResourceTypeInterrupt:
            SampleDrvExtension->InterruptCount++;

        default:
            break;
        }
    }
    
    ...

}

在以上代码中需要注意的是,函数WdfCmResourceListGetDescriptor()是按照ACPI资源表中的顺序依次读取的,所以这里SampleDrvExtension->ConnectionIds[0]对应GPIO10,SampleDrvExtension->ConnectionIds[1]对应GPIO11,顺序不能弄错了。


创建IO Target

当获取到I/O资源后,就可以使用该资源ID号创建一个IO Target来操作对应的GPIO口。这里对原始Sample Code做了点修改,增加了结构体SAMPLE_DRV_DEVICE_EXTENSION成员变量ReadIoTarget和WriteIoTarget,分别用于保存读、写IO TARGET的句柄。

创建一个读操作的IO Target:

NTSTATUS ReadIoTargetInitialize(WDFDEVICE Device)
{
    NTSTATUS Status;
    PSAMPLE_DRV_DEVICE_EXTENSION SampleDrvExtension;
    UNICODE_STRING ReadString;
    WCHAR ReadStringBuffer[100];
    WDF_OBJECT_ATTRIBUTES ObjectAttributes;
    WDF_IO_TARGET_OPEN_PARAMS OpenParams

    ...

    SampleDrvExtension = SampleDrvGetDeviceExtension(Device);

    WDF_OBJECT_ATTRIBUTES_INIT(&ObjectAttributes);
    ObjectAttributes.ParentObject = Device;
    WdfIoTargetCreate(Device, &ObjectAttributes, &SampleDrvExtension->ReadIoTarget);

    RtlInitEmptyUnicodeString(&ReadString, ReadStringBuffer, sizeof(ReadStringBuffer));
    RESOURCE_HUB_CREATE_PATH_FROM_ID(&ReadString,
                                     SampleDrvExtension->ConnectionIds[0].LowPart,
                                     SampleDrvExtension->ConnectionIds[0].HighPart);
    WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&OpenParams, ReadString, FILE_GENERIC_READ);
    WdfIoTargetOpen(SampleDrvExtension->ReadIoTarget, &OpenParams);

    ...
}



创建一个写操作的IO Target:

NTSTATUS WriteIoTargetInitialize(WDFDEVICE Device)
{
    NTSTATUS Status;
    PSAMPLE_DRV_DEVICE_EXTENSION SampleDrvExtension;
    UNICODE_STRING WriteString;
    WCHAR WriteStringBuffer[100];
    WDF_OBJECT_ATTRIBUTES ObjectAttributes;
    WDF_IO_TARGET_OPEN_PARAMS OpenParams

    ...

    SampleDrvExtension = SampleDrvGetDeviceExtension(Device);

    WDF_OBJECT_ATTRIBUTES_INIT(&ObjectAttributes);
    ObjectAttributes.ParentObject = Device;
    WdfIoTargetCreate(Device, &ObjectAttributes, &SampleDrvExtension->WriteIoTarget);

    RtlInitEmptyUnicodeString(&WriteString, WriteStringBuffer, sizeof(WriteStringBuffer));
    RESOURCE_HUB_CREATE_PATH_FROM_ID(&WriteString,
                                     SampleDrvExtension->ConnectionIds[1].LowPart,
                                     SampleDrvExtension->ConnectionIds[1].HighPart);
    WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&OpenParams, WriteString, FILE_GENERIC_WRITE);
    WdfIoTargetOpen(SampleDrvExtension->WriteIoTarget, &OpenParams);

    ...
}

发送IO请求

当创建并打开了一个IO TARGET后,就可以通过对该IO TARGET发送IOCTL请求来实现I/O引脚的读写操作。发送IOCTL_GPIO_READ_PINS进行读操作,发送IOCTL_GPIO_WRITE_PINS进行写操作。

NTSTATUS
ReadWriteGPIO (
    _In_ WDFDEVICE Device,
    _In_ BOOLEAN ReadOperation,
    _Inout_ PUCHAR Data,
    _In_ _In_range_(>, 0) ULONG Size,
    )
{

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

    SampleDrvExtension = SampleDrvGetDeviceExtension(Device);

    if(ReadOperation != FALSE) 
        IoTarget = SampleDrvExtension->ReadIoTarget;
    else
        IoTarget = SampleDrvExtension->WriteIoTarget;

    WDF_OBJECT_ATTRIBUTES_INIT(&RequestAttributes);    
    WdfRequestCreate(&RequestAttributes, IoTarget, &IoctlRequest);

    WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);    
    Attributes.ParentObject = IoctlRequest;    

    WdfMemoryCreatePreallocated(&Attributes, Data, Size, &WdfMemory);    

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

    WDF_REQUEST_SEND_OPTIONS_INIT(&SendOptions, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);    
    WdfRequestSend(IoctlRequest, IoTarget, &SendOptions);        

    ...
}

设备初始化

实现以上函数后,就可以对设备进行初始化了。一般设备的初始化操作在PnP Manager的回调函数EvtDeviceD0Entry中完成:

NTSTATUS
SampleDrvEvtDeviceD0Entry (
    _In_ WDFDEVICE Device,
    _In_ WDF_POWER_DEVICE_STATE PreviousPowerState
    )
{

    BYTE Data;
    NTSTATUS Status;

    ReadIoTargetInitialize(Device);

    Data = 0x0;
    Status = ReadWriteGPIO(Device, TRUE, &Data, sizeof(Data));
    if (!NT_SUCCESS(Status)) {
        goto Cleanup;
    }

    WriteIoTargetInitialize(Device);

    Data = 0x1;
    Status = ReadWriteGPIO(Device, FALSE, &Data, sizeof(Data));
    if (!NT_SUCCESS(Status)) {
        goto Cleanup;
    }

    ...
}
至此,GPIO外设驱动就完成了。


关于GPIO外设驱动更多详细信息,请参考MSDN官方文档:Connecting a KMDF Driver to GPIO I/O Pins



发布了68 篇原创文章 · 获赞 112 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/hexiaolong2009/article/details/42709145