在上一节 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