NFC CX quick start guide
This guide demonstrates how to write an NFC functional driver using the NFC Class Extension (NFC CX) driver.
Note
A driver that uses a class extension driver in its implementation is known as a 'client driver'. That is to say, a client of the class extension driver.
Prerequisites
- Your NFC Controller's firmware must implement the NFC Forum's NFC Controller Interface (NCI) protocol.
- Visual Studio 2017 (or later).
- The Windows SDK.
- The Windows Driver Kit (WDK).
Client driver responsibilities
The NFC CX driver is responsible for handling I/O requests sent to the driver and creating the relevant NCI command packets. The client driver is responsible for sending those NCI packets to the NFC Controller and sending back the NCI response packets to the NFC CX driver.
It's up to the client driver to determine how to send the NCI packets to the NFC Controller. This process varies depending on what type of hardware bus is used. Common buses used by NFC Controllers include I2C, SPI and USB.
Complete project code
A complete version of this sample code is available on GitHub: NFC CX client driver sample.
Project setup
In Visual Studio, create a new "User Mode Driver, Empty (UMDF V2)" project.
On the File menu, point to New, and then select Project. In the Visual C++ node, under Windows Drivers, select WDF, and then select User Mode Driver, Empty (UMDF V2)
Open the INF file.
In Solution Explorer, under the <project-name> node, in the Driver Files folder, double-click <project-name>.inf.
In the INF file, remove the custom device class, by using the following steps:
Remove the following two sections:
[ClassInstall32] AddReg=SampleClass_RegistryAdd [SampleClass_RegistryAdd] HKR,,,,%ClassName% HKR,,Icon,,"-10"
Under the
[Strings]
section, remove the following line.ClassName="Samples" ; TODO: edit ClassName
In the INF file, set driver's device class to Proximity:
- Change the value of
Class
toProximity
- Change the value of
ClassGuid
to{5630831C-06C9-4856-B327-F5D32586E060}
- This is the GUID of the Proximity device class.
[Version] ... Class=Proximity ClassGuid={5630831C-06C9-4856-B327-F5D32586E060} ; Proximity class GUID ...
- Change the value of
In the INF file, add a reference to the NFC Class Extension. Doing this ensures that Windows Driver Framework (WDF) loads the NFC CX driver when the client driver loads.
- Find the
<project-name>_Install
section. - Add
UmdfExtensions=NfcCx0102
.
[<project-name>_Install] ... UmdfExtensions=NfcCx0102
- Find the
In the driver build settings, link to the NFC Class Extension. Doing this ensures that the NFC CX API is available during code compilation.
- In Solution Explorer, right-click the project, and select Properties. In Configuration Properties, under Driver Settings, select NFC.
- Ensure that Configuration is set to
All Configurations
. - Ensure that Platform is to set to
All Platforms
. - Set Link to NFC Class Extension to
Yes
.
Add file named
Driver.cpp
to the project.Create a
DriverEntry
routine inDriver.cpp
. This is the entry point for the driver. Its primary purpose is to initialize WDF and to register theEvtDriverDeviceAdd
callback function.#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; }
Add two files named
Device.cpp
andDevice.h
to the project.In
Device.h
, define theDeviceContext
class.#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);
In
Device.cpp
, begin theDeviceContext::AddDevice
function's definition.#include "Device.h" NTSTATUS DeviceContext::AddDevice( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit) { NTSTATUS status;
Set the NFC CX device configuration. The device configuration includes providing the
EvtNfcCxWriteNciPacket
callback function. This callback receives the NCI packets from the NFC CX driver that the client driver should forward to the NFC Controller.// 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; }
Register the PnP power callbacks required by your client driver.
A typical client driver will likely require the
EvtDevicePrepareHardware
, theEvtDeviceReleaseHardware
, theEvtDeviceD0Entry
and theEvtDeviceD0Exit
functions. Requirements can vary depending on how your client driver handles power management.// 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);
Call the
WdfDeviceCreate
function to create theWDFDEVICE
object.// 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; }
Call the
NfcCxDeviceInitialize
function.This function should be called after the
WDFDEVICE
object has been created to allow the NFC CX driver to complete its initialization of the device instance.// Let NFC CX finish initializing the device instance. status = NfcCxDeviceInitialize(device); if (!NT_SUCCESS(status)) { return status; }
Call
NfcCxSetRfDiscoveryConfig
to specify the NFC technologies and protocols supported by the NFC Controller.// 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; }
End the
DeviceContext::AddDevice
function.return STATUS_SUCCESS; }
Implement the
PrepareHardware
andReleaseHardware
callback functions.These two functions are used to initialize and uninitialize the hardware resources assigned to the NFC Controller's device instance. Their implementation depends on what type of bus the device is connected to (for example, I2C, SPI and 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; }
Call the
NfcCxHardwareEvent
function withHostActionStart
andHostActionStop
to start and stop the NCI state machine at the appropriate times.Some drivers do this during the
D0Entry
andD0Exit
PnP power callbacks. This can vary depending on how your client driver handles power management, though.// 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; }
Implement the
WriteNciPacket
function.This callback is called by the NFC CX when there's an NCI packet to send to the NFC Controller.
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. };
Call the
NfcCxNciReadNotification
function when the NFC Controller has an NCI packet that should be sent to the NFC CX. This is typically done in a hardware event callback.For example:
- A GPIO interrupt event callback. (I2C and SPI)
- A USB continuous reader callback.
Logging
Consider adding logging to the client driver to make it easier to debug. Both ETW tracing and WPP tracing are good options.