NFC CX 快速入门指南

本指南演示如何使用 NFC 类扩展 (NFC CX) 驱动程序编写 NFC 功能驱动程序。

注意

在其实现中使用类扩展驱动程序的驱动程序称为“客户端驱动程序”。 也就是说,类扩展驱动程序的客户端。

先决条件

客户端驱动程序职责

NFC CX 驱动程序负责处理发送到驱动程序的 I/O 请求并创建相关的 NCI 命令数据包。 客户端驱动程序负责将这些 NCI 数据包发送到 NFC 控制器,并将 NCI 响应数据包发回 NFC CX 驱动程序。

由客户端驱动程序决定如何将 NCI 数据包发送到 NFC 控制器。 此过程因使用的硬件总线类型而异。 NFC 控制器使用的常见总线包括 I2C、SPI 和 USB。

完整的项目代码

GitHub 上提供了此示例代码的完整版本: NFC CX 客户端驱动程序示例

项目设置

  1. 在 Visual Studio 中,创建新的“用户模式驱动程序,空 (UMDF V2) ”项目。

    “文件” 菜单上,指向 “新建”,然后选择 “项目”。 在 Visual C++ 节点的 “Windows 驱动程序”下,选择“ WDF”,然后选择“ 用户模式驱动程序”、“空 (UMDF V2)

    显示 Visual Studio“新建项目”对话框的屏幕截图,其中选择了“用户模式驱动程序,空 (UMDF V2) ”。

  2. 打开 INF 文件。

    解决方案资源管理器,在“驱动程序文件”文件夹中的<“project-name>”节点下,双击“<project-name.inf>”。

  3. 在 INF 文件中,使用以下步骤删除自定义设备类:

    1. 删除以下两个部分:

      [ClassInstall32]
      AddReg=SampleClass_RegistryAdd
      
      [SampleClass_RegistryAdd]
      HKR,,,,%ClassName%
      HKR,,Icon,,"-10"
      
    2. [Strings] 部分下,删除以下行。

      ClassName="Samples" ; TODO: edit ClassName
      
  4. 在 INF 文件中,将驱动程序的设备类设置为 邻近感应

    1. 将 的值 Class 更改为 Proximity
    2. 将 的值 ClassGuid 更改为 {5630831C-06C9-4856-B327-F5D32586E060}
      • 这是邻近感应设备类的 GUID。
    [Version]
    ...
    Class=Proximity
    ClassGuid={5630831C-06C9-4856-B327-F5D32586E060} ; Proximity class GUID
    ...
    
  5. 在 INF 文件中,添加对 NFC 类扩展的引用。 这样做可确保 Windows 驱动程序框架 (WDF) 客户端驱动程序加载时加载 NFC CX 驱动程序。

    1. 找到 <project-name>_Install 节。
    2. 添加 UmdfExtensions=NfcCx0102
    [<project-name>_Install]
    ...
    UmdfExtensions=NfcCx0102
    
  6. 在驱动程序生成设置中,链接到 NFC 类扩展。 这样做可确保 NFC CX API 在代码编译期间可用。

    1. 在“解决方案资源管理器” 中,右键单击项目,然后选择“属性” 。 在“配置属性”中的“驱动程序设置”下,选择“NFC”。
    2. 确保 “配置” 设置为 All Configurations
    3. 确保 将“平台 ”设置为 All Platforms
    4. “NFC 类扩展的链接 ”设置为 Yes

    Visual Studio 对话框:MyNfcDriver 属性页,其中显示“链接到 NFC 类扩展”设置为“是”。

  7. 将名为 Driver.cpp 的文件添加到项目。

  8. DriverEntry在 中创建Driver.cpp例程。 这是驱动程序的入口点。 它的主要用途是初始化 WDF 并注册 EvtDriverDeviceAdd 回调函数。

    #include <windows.h>
    #include <wdf.h>
    
    #include "Device.h" // created in Step 9
    
    // The entry point for the driver.
    extern "C" NTSTATUS DriverEntry(
        _In_ PDRIVER_OBJECT DriverObject,
        _In_ PUNICODE_STRING RegistryPath
        )
    {
        NTSTATUS status = STATUS_SUCCESS;
    
        // Specify `DeviceContext::AddDevice` as the
        // `EvtDriverDeviceAdd` function for the driver.
        WDF_DRIVER_CONFIG driverConfig;
        WDF_DRIVER_CONFIG_INIT(&driverConfig, DeviceContext::AddDevice);
    
        // Initialize WDF.
        status = WdfDriverCreate(
            DriverObject,
            RegistryPath,
            WDF_NO_OBJECT_ATTRIBUTES,
            &driverConfig,
            WDF_NO_HANDLE);
        if (!NT_SUCCESS(status))
        {
            return status;
        }
    
        return STATUS_SUCCESS;
    }
    
  9. 将两个名为 Device.cppDevice.h 的文件添加到项目中。

  10. Device.h中, DeviceContext 定义 类。

    #pragma once
    
    #include <windows.h>
    #include <wdf.h>
    #include <NfcCx.h>
    
    // The class that will store the driver's custom state for
    // a device instance.
    class DeviceContext
    {
    public:
        // Implementation of `EvtDriverDeviceAdd`.
        static NTSTATUS AddDevice(
            _In_ WDFDRIVER Driver,
            _Inout_ PWDFDEVICE_INIT DeviceInit);
    
    private:
        // Implementation of `EvtDevicePrepareHardware`.
        static NTSTATUS PrepareHardware(
            _In_ WDFDEVICE Device,
            _In_ WDFCMRESLIST ResourcesRaw,
            _In_ WDFCMRESLIST ResourcesTranslated);
    
        // Implementation of `EvtDeviceReleaseHardware`.
        static NTSTATUS ReleaseHardware(
            _In_ WDFDEVICE Device,
            _In_ WDFCMRESLIST ResourcesTranslated);
    
        // Implementation of `EvtDeviceD0Entry`.
        static NTSTATUS D0Entry(
            _In_ WDFDEVICE Device,
            _In_ WDF_POWER_DEVICE_STATE PreviousState);
    
        // Implementation of `EvtDeviceD0Exit`.
        static NTSTATUS D0Exit(
            _In_ WDFDEVICE Device,
            _In_ WDF_POWER_DEVICE_STATE TargetState);
    
        // Implementation of `EvtNfcCxWriteNciPacket`.
        static void WriteNciPacket(
            _In_ WDFDEVICE Device,
            _In_ WDFREQUEST Request);
    };
    
    // Define the `DeviceGetContext` function.
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DeviceContext, DeviceGetContext);
    
  11. Device.cpp中,开始 DeviceContext::AddDevice 函数的定义。

    #include "Device.h"
    
    NTSTATUS DeviceContext::AddDevice(
        _In_ WDFDRIVER Driver,
        _Inout_ PWDFDEVICE_INIT DeviceInit)
    {
        NTSTATUS status;
    
    
  12. 设置 NFC CX 设备配置。 设备配置包括提供 EvtNfcCxWriteNciPacket 回调函数。 此回调从 NFC CX 驱动程序接收 NCI 数据包,客户端驱动程序应将其转发到 NFC 控制器。

        // Create the NfcCx config.
        NFC_CX_CLIENT_CONFIG nfcCxConfig;
        NFC_CX_CLIENT_CONFIG_INIT(&nfcCxConfig, NFC_CX_TRANSPORT_CUSTOM);
        nfcCxConfig.EvtNfcCxWriteNciPacket = WriteNciPacket;
        nfcCxConfig.DriverFlags = NFC_CX_DRIVER_ENABLE_EEPROM_WRITE_PROTECTION;
    
        // Set the NfcCx config.
        status = NfcCxDeviceInitConfig(DeviceInit, &nfcCxConfig);
        if (!NT_SUCCESS(status))
        {
            return status;
        }
    
  13. 注册客户端驱动程序所需的 PnP 电源回调

    典型的客户端驱动程序可能需要 EvtDevicePrepareHardwareEvtDeviceReleaseHardwareEvtDeviceD0EntryEvtDeviceD0Exit 函数。 要求可能因客户端驱动程序处理电源管理的方式而异。

        // Create the PnP power callbacks configuration.
        WDF_PNPPOWER_EVENT_CALLBACKS pnpCallbacks;
        WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpCallbacks);
        pnpCallbacks.EvtDevicePrepareHardware = PrepareHardware;
        pnpCallbacks.EvtDeviceReleaseHardware = ReleaseHardware;
        pnpCallbacks.EvtDeviceD0Entry = D0Entry;
        pnpCallbacks.EvtDeviceD0Exit = D0Exit;
    
        // Set the PnP power callbacks.
        WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpCallbacks);
    
  14. WdfDeviceCreate调用 函数以创建 WDFDEVICE 对象。

        // Create WDF object attributes for the WDFDEVICE object.
        WDF_OBJECT_ATTRIBUTES deviceAttributes;
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DeviceContext);
    
        // Create the device.
        WDFDEVICE device;
        status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
        if (!NT_SUCCESS(status))
        {
            return status;
        }
    
  15. 调用 NfcCxDeviceInitialize 函数。

    应在创建 对象后 WDFDEVICE 调用此函数,以允许 NFC CX 驱动程序完成设备实例的初始化。

        // Let NFC CX finish initializing the device instance.
        status = NfcCxDeviceInitialize(device);
        if (!NT_SUCCESS(status))
        {
            return status;
        }
    
  16. 调用 NfcCxSetRfDiscoveryConfig 以指定 NFC 控制器支持的 NFC 技术和协议。

        // Create the RF config. (Enable everything.)
        NFC_CX_RF_DISCOVERY_CONFIG discoveryConfig;
        NFC_CX_RF_DISCOVERY_CONFIG_INIT(&discoveryConfig);
        discoveryConfig.PollConfig =
            NFC_CX_POLL_NFC_A | NFC_CX_POLL_NFC_B |
            NFC_CX_POLL_NFC_F_212 | NFC_CX_POLL_NFC_F_424 |
            NFC_CX_POLL_NFC_15693 | NFC_CX_POLL_NFC_ACTIVE |
            NFC_CX_POLL_NFC_A_KOVIO;
        discoveryConfig.NfcIPMode =
            NFC_CX_NFCIP_NFC_A | NFC_CX_NFCIP_NFC_F_212 |
            NFC_CX_NFCIP_NFC_F_424 | NFC_CX_NFCIP_NFC_ACTIVE |
            NFC_CX_NFCIP_NFC_ACTIVE_A | NFC_CX_NFCIP_NFC_ACTIVE_F_212 |
            NFC_CX_NFCIP_NFC_ACTIVE_F_424;
        discoveryConfig.NfcIPTgtMode =
            NFC_CX_NFCIP_TGT_NFC_A | NFC_CX_NFCIP_TGT_NFC_F |
            NFC_CX_NFCIP_TGT_NFC_ACTIVE_A | NFC_CX_NFCIP_TGT_NFC_ACTIVE_F;
        discoveryConfig.NfcCEMode =
            NFC_CX_CE_NFC_A | NFC_CX_CE_NFC_B |
            NFC_CX_CE_NFC_F;
    
        // Set the RF config.
        status = NfcCxSetRfDiscoveryConfig(device, &discoveryConfig);
        if (!NT_SUCCESS(status))
        {
            return status;
        }
    
  17. DeviceContext::AddDevice结束函数。

        return STATUS_SUCCESS;
    }
    
  18. PrepareHardware实现 和 ReleaseHardware 回调函数。

    这两个函数用于初始化和取消初始化分配给 NFC 控制器设备实例的硬件资源。 其实现取决于设备连接到 (的总线类型,例如 I2C、SPI 和 USB) 。

    NTSTATUS DeviceContext::PrepareHardware(
        _In_ WDFDEVICE Device,
        _In_ WDFCMRESLIST ResourcesRaw,
        _In_ WDFCMRESLIST ResourcesTranslated)
    {
        // FIX ME: Initialize hardware resources.
        return STATUS_SUCCESS;
    }
    
    NTSTATUS DeviceContext::ReleaseHardware(
        _In_ WDFDEVICE Device,
        _In_ WDFCMRESLIST ResourcesTranslated)
    {
        // FIX ME: Uninitialize hardware resources.
        return STATUS_SUCCESS;
    }
    
  19. NfcCxHardwareEvent使用 HostActionStartHostActionStop 调用 函数,以在适当的时间启动和停止 NCI 状态机。

    某些驱动程序在 D0EntryD0Exit PnP 电源回调期间执行此操作。 不过,这可能因客户端驱动程序处理电源管理的方式而异。

    // Device exiting low power state (or is booting up).
    NTSTATUS DeviceContext::D0Entry(
        _In_ WDFDEVICE Device,
        _In_ WDF_POWER_DEVICE_STATE PreviousState)
    {
        (void)PreviousState;
    
        NTSTATUS status;
    
        // Invoke the HostActionStart event, so that the NFC CX initializes
        // the NFC Controller.
        NFC_CX_HARDWARE_EVENT eventArgs = {};
        eventArgs.HostAction = HostActionStart;
    
        status = NfcCxHardwareEvent(Device, &eventArgs);
        if (!NT_SUCCESS(status))
        {
            return status;
        }
    
        return STATUS_SUCCESS;
    }
    
    // Device entering low power state.
    NTSTATUS DeviceContext::D0Exit(
        _In_ WDFDEVICE Device,
        _In_ WDF_POWER_DEVICE_STATE TargetState)
    {
        (void)TargetState;
    
        NTSTATUS status;
    
        // Trigger the HostActionStop event, so that the NFC CX
        // uninitializes the NFC Controller.
        NFC_CX_HARDWARE_EVENT eventArgs = {};
        eventArgs.HostAction = HostActionStop;
    
        status = NfcCxHardwareEvent(Device, &eventArgs);
        if (!NT_SUCCESS(status))
        {
            return status;
        }
    
        return STATUS_SUCCESS;
    }
    
  20. 实现 WriteNciPacket 函数。

    当有要发送到 NFC 控制器的 NCI 数据包时,NFC CX 会调用此回调。

    void DeviceContext::WriteNciPacket(
        _In_ WDFDEVICE Device,
        _In_ WDFREQUEST Request)
    {
        NTSTATUS status;
    
        // Get the NCI packet as a raw byte buffer.
        void* nciPacket;
        size_t nciPacketLength;
        status = WdfRequestRetrieveInputBuffer(Request, 0, &nciPacket, &nciPacketLength);
        if (!NT_SUCCESS(status))
        {
            WdfRequestComplete(Request, status);
            return;
        }
    
        // FIX ME: Use the NCI packet in some way.
    
        // FIX ME: Call `WdfRequestComplete` on `Request` with failure
        // or success `NTSTATUS` code.
    };
    
  21. NfcCxNciReadNotification当 NFC 控制器具有应发送到 NFC CX 的 NCI 数据包时,调用 函数。 这通常在硬件事件回调中完成。

    例如:

日志记录

请考虑将日志记录添加到客户端驱动程序,使其更易于调试。 ETW 跟踪WPP 跟踪都是不错的选择。