开发 WiFiCx 客户端驱动程序

设备和适配器初始化

除了 NetAdapterCx 需要 NetAdapter 设备初始化的任务外,WiFiCx 客户端驱动程序还在其 EvtDriverDeviceAdd 回调函数中执行这些任务:

  1. 调用 WifiDeviceInitConfig 在调用 NetDeviceInitConfig 之后,但在调用 WdfDeviceCreate 之前,引用框架传入的同一 WDFDEVICE_INIT 对象。

  2. 调用 WifiDeviceInitialize 以使用初始化的WIFI_DEVICE_CONFIG结构和从 WdfDeviceCreate 获取的 WDFDEVICE 对象注册 WiFiCx 设备特定的回调函数。

以下示例演示如何初始化 WiFiCx 设备。 为了清楚起见,该示例省略了错误处理。

status = NetDeviceInitConfig(deviceInit);
status = WifiDeviceInitConfig(deviceInit);

// Set up other callbacks such as Pnp and Power policy

status = WdfDeviceCreate(&deviceInit, &deviceAttributes, &wdfDevice);
WIFI_DEVICE_CONFIG wifiDeviceConfig;
WIFI_DEVICE_CONFIG_INIT(&wifiDeviceConfig,
                        WDI_VERSION_LATEST,
                        EvtWifiDeviceSendCommand,
                        EvtWifiDeviceCreateAdapter,
                        EvtWifiDeviceCreateWifiDirectDevice); 

status = WifiDeviceInitialize(wdfDevice, &wifiDeviceConfig);
...
// Get the TLV version that WiFiCx uses to initialize the client's TLV parser/generator
auto peerVersion = WifiDeviceGetOsWdiVersion(wdfDevice);

WiFiCx 客户端驱动程序初始化过程的屏幕截图。

默认适配器(工作站)创建流

接下来,客户端驱动程序必须设置所有 Wi-Fi 特定设备功能,通常位于后面的 EvtDevicePrepareHardware 回调函数中。 如果硬件需要中断才能查询固件功能,可以在 EvtWdfDeviceD0EntryPostInterruptsEnabled 中执行此作。

WiFiCx 不再调用 WDI_TASK_OPENWDI_TASK_CLOSE 来指示客户端加载或卸载固件,并且它不会使用 WDI_GET_ADAPTER_CAPABILITIES 命令查询 Wi-Fi 功能。

与其他类型的 NetAdapterCx 驱动程序不同,WiFiCx 驱动程序不会在 EvtDriverDeviceAdd 回调函数中创建 NETADAPTER 对象。 WiFiCx 指示驱动程序在客户端的 EvtDevicePrepareHardware 回调成功后,再通过 EvtWifiDeviceCreateAdapter 回调来创建默认 NetAdapter(station)。 WiFiCx 和 WDI 不再调用 WDI_TASK_CREATE_PORT 命令。

在其 EvtWifiDeviceCreateAdapter 回调函数中,客户端驱动程序必须:

  1. 调用 NetAdapterCreate 创建新的 NetAdapter 对象。

  2. 调用 WifiAdapterInitialize 初始化 WiFiCx 上下文并将其与此 NetAdapter 对象相关联。

  3. 调用 NetAdapterStart 以启动适配器。

如果成功,WiFiCx 将为设备或适配器(例如 SET_ADAPTER_CONFIGURATIONTASK_SET_RADIO_STATE)发送初始化命令。

有关EvtWifiDeviceCreateAdapter的代码示例,请参阅适配器创建的事件回调

描述 WiFiCx 客户端驱动程序工作站适配器的创建流程图。

处理 WiFiCx 命令消息

对于大多数控制路径操作,WiFiCx 命令消息是基于以前的 WDI 模型命令。 这些命令在 WiFiCx 任务 OIDWiFiCx 属性 OIDWiFiCx 状态指示中定义。 有关详细信息,请参阅 WiFiCx 消息结构

命令通过客户端驱动程序提供的一组回调函数和 WiFiCx 提供的 API 进行交换:

  • WiFiCx 通过调用其 EvtWifiDeviceSendCommand 回调函数向客户端驱动程序发送命令消息。

  • 若要检索消息,客户端驱动程序调用 WifiRequestGetInOutBuffer 以获取输入/输出缓冲区和缓冲区长度。 驱动程序还需要调用 WifiRequestGetMessageId 来检索消息 ID。

  • 若要完成请求,驱动程序通过调用 WifiRequestComplete异步发送命令的 M3。

  • 如果命令是设置命令,并且原始请求没有包含足够大的缓冲区,客户端应调用WifiRequestSetBytesNeeded以设置所需的缓冲区大小,然后以 BUFFER_OVERFLOW 状态使请求失败。

  • 如果命令是任务命令,则客户端驱动程序需要稍后通过调用 WifiDeviceReceiveIndication 发送关联的 M4 指示,并使用 WDI 标头传递指示缓冲区,该标头包含与 M1 中包含的消息 ID 相同的消息 ID。

  • 未经请求的指示也通过 WifiDeviceReceiveIndication 被通知,但 TransactionId 成员在 WDI_MESSAGE_HEADER 中设置为 0

显示 WiFiCx 驱动程序命令消息处理的流程图。

Wi-Fi Direct(P2P)支持

以下部分介绍了 WiFiCx 驱动程序如何支持 Wi-Fi Direct。

Wi-Fi 直接设备功能

WIFI_WIFIDIRECT_CAPABILITIES 表示以前通过WDI_P2P_CAPABILITIES和WDI_AP_CAPABILITIES TLV 在 WDI 中设置的所有相关功能。 客户端驱动程序调用 WifiDeviceSetWiFiDirectCapabilities ,以在设置设备功能阶段向 WiFiCx 报告 Wi-Fi 直接功能。

WIFI_WIFIDIRECT_CAPABILITIES wfdCapabilities = {};

// Set values
wfdCapabilities.ConcurrentGOCount = 1;
wfdCapabilities.ConcurrentClientCount = 1;

// Report capabilities to WiFiCx
WifiDeviceSetWiFiDirectCapabilities(Device, &wfdCapabilities);

Wi-Fi“WfdDevice”的直接事件回调

对于 Wi-Fi Direct,“WfdDevice”是一个没有数据路径功能的控件对象。 因此,WiFiCx 具有名为 WIFIDIRECTDEVICE 的新 WDFObject。 在其 EvtWifiDeviceCreateWifiDirectDevice 回调函数中,客户端驱动程序:

此示例演示如何创建和初始化 WIFIDIRECTDEVICE 对象。

NTSTATUS
EvtWifiDeviceCreateWifiDirectDevice(
    WDFDEVICE  Device,
    WIFIDIRECT_DEVICE_INIT * WfdDeviceInit
)
{
    WDF_OBJECT_ATTRIBUTES wfdDeviceAttributes;
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wfdDeviceAttributes, WIFI_WFDDEVICE_CONTEXT);
    wfdDeviceAttributes.EvtCleanupCallback = EvtWifiDirectDeviceContextCleanup;

    WIFIDIRECTDEVICE wfdDevice;
    NTSTATUS ntStatus = WifiDirectDeviceCreate(WfdDeviceInit, &wfdDeviceAttributes, &wfdDevice);
    if (!NT_SUCCESS(ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DEVICE, "%!FUNC!: WifiDirectDeviceCreate failed, status=0x%x\n", ntStatus);
        return ntStatus;
    }

    ntStatus = WifiDirectDeviceInitialize(wfdDevice);

    if (!NT_SUCCESS(ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DEVICE, "%!FUNC!: WifiDirectDeviceInitialize failed with %!STATUS!\n", ntStatus);
        return ntStatus;
    }

    ntStatus = ClientDriverInitWifiDirectDeviceContext(
        Device,
        wfdDevice,
        WifiDirectDeviceGetPortId(wfdDevice));
    if (!NT_SUCCESS(ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DEVICE, "%!FUNC!: ClientDriverInitWifiDirectDeviceContext failed with %!STATUS!\n", ntStatus);
        return ntStatus;
    }

    return ntStatus;
}

用于创建适配器的事件回调

客户端驱动程序使用相同的事件回调创建工作站适配器和 WfdRole 适配器: EvtWifiDeviceCreateAdapter

NTSTATUS
EvtWifiDeviceCreateAdapter(
    WDFDEVICE Device,
    NETADAPTER_INIT* AdapterInit
)
{
    NET_ADAPTER_DATAPATH_CALLBACKS datapathCallbacks;
    NET_ADAPTER_DATAPATH_CALLBACKS_INIT(&datapathCallbacks,
        EvtAdapterCreateTxQueue,
        EvtAdapterCreateRxQueue);

    NetAdapterInitSetDatapathCallbacks(AdapterInit, &datapathCallbacks);

    WDF_OBJECT_ATTRIBUTES adapterAttributes;
    WDF_OBJECT_ATTRIBUTES_INIT(&adapterAttributes);
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&adapterAttributes, WIFI_NETADAPTER_CONTEXT);
    adapterAttributes.EvtCleanupCallback = EvtAdapterContextCleanup;

    NETADAPTER netAdapter;
    NTSTATUS ntStatus = NetAdapterCreate(AdapterInit, &adapterAttributes, &netAdapter);
    if (!NT_SUCCESS(ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DEVICE, "%!FUNC!: NetAdapterCreate failed, status=0x%x\n", ntStatus);
        return ntStatus;
    }

    ntStatus = WifiAdapterInitialize(netAdapter);

    if (!NT_SUCCESS(ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DEVICE, "%!FUNC!: WifiAdapterInitialize failed with %!STATUS!\n", ntStatus);
        return ntStatus;
    }

    ntStatus = ClientDriverInitDataAdapterContext(
        Device,
        netAdapter,
        WifiAdapterGetType(netAdapter) == WIFI_ADAPTER_EXTENSIBLE_STATION ? EXTSTA_PORT : EXT_P2P_ROLE_PORT,
        WifiAdapterGetPortId(netAdapter));

    if (!NT_SUCCESS(ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DEVICE, "%!FUNC!: ClientDriverInitDataAdapterContext failed with %!STATUS!\n", ntStatus);
        return ntStatus;
    }

    ntStatus = ClientDriverNetAdapterStart(netAdapter);
    if (!NT_SUCCESS(ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_DEVICE, "%!FUNC!: ClientDriverNetAdapterStart failed with %!STATUS!\n", ntStatus);
        return ntStatus;
    }

    return ntStatus;
}

Tx 队列中的 Wi-Fi ExemptionAction 支持

ExemptionAction 是一个新的 NetAdapter 数据包扩展,指示该数据包是否应免除客户端执行的任何加密操作。 有关详细信息,请阅读 usExemptionActionType 的文档。

#include <net/wifi/exemptionaction.h>

typedef struct _WIFI_TXQUEUE_CONTEXT
{
    WIFI_NETADAPTER_CONTEXT* NetAdapterContext;
    LONG NotificationEnabled;
    NET_RING_COLLECTION const* Rings;
    NET_EXTENSION VaExtension;
    NET_EXTENSION LaExtension;
    NET_EXTENSION ExemptionActionExtension;
    CLIENTDRIVER_TCB* PacketContext;
} WIFI_TXQUEUE_CONTEXT, * PWIFI_TXQUEUE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(WIFI_TXQUEUE_CONTEXT, WifiGetTxQueueContext);

NTSTATUS
EvtAdapterCreateTxQueue(
    _In_ NETADAPTER NetAdapter,
    _Inout_ NETTXQUEUE_INIT* TxQueueInit
)
{
    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "-->%!FUNC!\n");

    NTSTATUS status = STATUS_SUCCESS;
    PWIFI_TXQUEUE_CONTEXT txQueueContext = NULL;
    PWIFI_NETADAPTER_CONTEXT netAdapterContext = WifiGetNetAdapterContext(NetAdapter);
    WDF_OBJECT_ATTRIBUTES txAttributes;

    WDF_OBJECT_ATTRIBUTES_INIT(&txAttributes);
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&txAttributes, WIFI_TXQUEUE_CONTEXT);

    txAttributes.EvtDestroyCallback = EvtTxQueueDestroy;

    NET_PACKET_QUEUE_CONFIG queueConfig;
    NET_PACKET_QUEUE_CONFIG_INIT(&queueConfig,
        EvtTxQueueAdvance,
        EvtTxQueueSetNotificationEnabled,
        EvtTxQueueCancel);
    queueConfig.EvtStart = EvtTxQueueStart;
    NETPACKETQUEUE txQueue;
    status =
        NetTxQueueCreate(TxQueueInit,
            &txAttributes,
            &queueConfig,
            &txQueue);

    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "NetTxQueueCreate failed, Adapter=0x%p status=0x%x\n", NetAdapter, status);
        goto Exit;
    }

    txQueueContext = WifiGetTxQueueContext(txQueue);

    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT, "NetTxQueueCreate succeeded, Adapter=0x%p, TxQueue=0x%p\n", NetAdapter, txQueue);

    txQueueContext->NetAdapterContext = netAdapterContext;
    txQueueContext->Rings = NetTxQueueGetRingCollection(txQueue);
    netAdapterContext->TxQueue = txQueue;

    NET_EXTENSION_QUERY extensionQuery;
    NET_EXTENSION_QUERY_INIT(
        &extensionQuery,
        NET_FRAGMENT_EXTENSION_VIRTUAL_ADDRESS_NAME,
        NET_FRAGMENT_EXTENSION_VIRTUAL_ADDRESS_VERSION_1,
        NetExtensionTypeFragment);

    NetTxQueueGetExtension(
        txQueue,
        &extensionQuery,
        &txQueueContext->VaExtension);

    if (!txQueueContext->VaExtension.Enabled)
    {
        TraceEvents(
            TRACE_LEVEL_ERROR,
            DBG_INIT,
            "%!FUNC!: Required virtual address extension is missing.");

        status = STATUS_UNSUCCESSFUL;
        goto Exit;
    }

    NET_EXTENSION_QUERY_INIT(
        &extensionQuery,
        NET_FRAGMENT_EXTENSION_LOGICAL_ADDRESS_NAME,
        NET_FRAGMENT_EXTENSION_LOGICAL_ADDRESS_VERSION_1,
        NetExtensionTypeFragment);

    NetTxQueueGetExtension(
        txQueue,
        &extensionQuery,
        &txQueueContext->LaExtension);

    if (!txQueueContext->LaExtension.Enabled)
    {
        TraceEvents(
            TRACE_LEVEL_ERROR,
            DBG_INIT,
            "%!FUNC!: Required logical address extension is missing.");

        status = STATUS_UNSUCCESSFUL;
        goto Exit;
    }

     NET_EXTENSION_QUERY_INIT(
        &extensionQuery,
        NET_PACKET_EXTENSION_WIFI_EXEMPTION_ACTION_NAME,
        NET_PACKET_EXTENSION_WIFI_EXEMPTION_ACTION_VERSION_1,
        NetExtensionTypePacket);

    NetTxQueueGetExtension(
        txQueue,
        &extensionQuery,
        &txQueueContext->ExemptionActionExtension);

    if (!txQueueContext->ExemptionActionExtension.Enabled)
    {
        TraceEvents(
            TRACE_LEVEL_ERROR,
            DBG_INIT,
            "%!FUNC!: Required Exemption Action extension is missing.");

        status = STATUS_UNSUCCESSFUL;
        goto Exit;
    }

    status = InitializeTCBs(txQueue, txQueueContext);

    if (status != STATUS_SUCCESS)
    {
        goto Exit;
    }

Exit:
    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_INIT, "<--%!FUNC! with 0x%x\n", status);

    return status;
}

static
void
BuildTcbForPacket(
    _In_ WIFI_TXQUEUE_CONTEXT const * TxQueueContext,
    _Inout_ CLIENTDRIVER_TCB * Tcb,
    _In_ UINT32 PacketIndex,
    _In_ NET_RING_COLLECTION const * Rings
)
{
    auto const pr = NetRingCollectionGetPacketRing(Rings);
    auto const fr = NetRingCollectionGetFragmentRing(Rings);

    auto const packet = NetRingGetPacketAtIndex(pr, PacketIndex);

    auto const & vaExtension = TxQueueContext->VaExtension;
    auto const & laExtension = TxQueueContext->LaExtension;
    auto const & exemptionActionExtension = TxQueueContext->ExemptionActionExtension;



    auto const packageExemptionAction = WifiExtensionGetExemptionAction(&exemptionActionExtension, PacketIndex);
    Tcb->EncInfo.ExemptionActionType = packageExemptionAction->ExemptionAction;

}

Wi-Fi Direct INI/INF 文件更改

vWifi 功能已被 NetAdapter 替换。 如果要从基于 WDI 的驱动程序进行移植,INI/INF 应删除 vWIFI 相关信息。

Characteristics = 0x84
BusType         = 5
*IfType         = 71; IF_TYPE_IEEE80211
*MediaType      = 16; NdisMediumNative802_11
*PhysicalMediaType = 9; NdisPhysicalMediumNative802_11
NumberOfNetworkInterfaces   = 5; For WIFI DIRECT DEVICE AND ROLE ADAPTER

; TODO: Set this to 0 if your device isn't a physical device.
*IfConnectorPresent     = 1     ; true

; In most cases, keep these at their default values.
*ConnectionType         = 1     ; NET_IF_CONNECTION_DEDICATED
*DirectionType          = 0     ; NET_IF_DIRECTION_SENDRECEIVE
*AccessType             = 2     ; NET_IF_ACCESS_BROADCAST
*HardwareLoopback       = 0     ; false

[ndi.NT.Wdf]
KmdfService = %ServiceName%, wdf

[wdf]
KmdfLibraryVersion      = $KMDFVERSION$

NetAdapter 数据路径更改

设置多个 Tx 队列

默认情况下,NetAdapterCx 将为用于 NetAdapter 的所有数据包创建一个 Tx 队列。

如果驱动程序需要为质量服务(QoS)支持多个 Tx 队列,或为不同的对等方设置不同的队列,则可以通过设置相应的 DEMUX 属性来实现。 如果添加 demux 属性,Tx 队列计数是最大对等数与最大 TID 数的乘积,再加上 1(用于广播或多播)。

QOS 的多个队列

在使用 NETADAPTER_INIT * 对象创建 NETADAPTER 之前,客户端驱动程序应向其添加 WMMINFO demux:

...
WIFI_ADAPTER_TX_DEMUX wmmInfoDemux;
WIFI_ADAPTER_TX_WMMINFO_DEMUX_INIT(&wmmInfoDemux);
WifiAdapterInitAddTxDemux(adapterInit, &wmmInfoDemux);

这会导致翻译器按需创建多达 8 个 Tx 队列,具体取决于 NBL WlanTagHeader::WMMInfo 值。

客户端驱动程序从 EvtPacketQueueStart 查询框架用于此队列的优先级:

auto const priority = WifiTxQueueGetDemuxWmmInfo(queue);

EvtStartEvtStop 之间放置到此队列的所有数据包都将具有给定的优先级。

对等互连的多个队列

在使用 NETADAPTER_INIT * 对象创建 NETADAPTER 之前,客户端驱动程序应向其添加PEER_ADDRESS demux:

...
WIFI_ADAPTER_TX_DEMUX peerInfoDemux;
WIFI_ADAPTER_TX_PEER_ADDRESS_DEMUX_INIT(&peerInfoDemux, maxNumOfPeers);
WifiAdapterInitAddTxDemux(adapterInit, &peerInfoDemux);

客户端驱动程序从 EvtPacketQueueStart 查询框架用于此队列的对等地址:

auto const peerAddress = WifiTxQueueGetDemuxPeerAddress(queue);

EvtStartEvtStop 之间的此队列中放置的所有数据包都具有给定的优先级。

框架仅为驱动程序通过以下 API 添加的对等地址打开队列。

WifiAdapterAddPeer:告知 WiFiCx 对等方已连接到给定地址。 WiFiCx 将通过将队列关联到对等地址,利用此地址进行对等多路分解。 驱动程序可以添加的最大对等数不能超过添加 Tx 非复数信息时提供的范围值。

WifiAdapterRemovePeer:告诉 WiFiCx 对等方已断开连接。 框架停止关联的队列。

对等生存期

电源策略更改

对于电源管理,客户端驱动程序使用 NETPOWERSETTINGS 对象 ,例如其他类型的 NetAdapterCx 客户端驱动程序

为了在系统处于工作状态(S0)状态时支持设备闲置,驱动程序调用 WdfDeviceAssignS0IdleSettings 并将 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS的 IdleTimeoutType 成员设置为 SystemManagedIdleTimeoutWithHint

const ULONG WIFI_DEFAULT_IDLE_TIMEOUT_HINT_MS = 3u * 1000u; // 3 seconds
...
WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS  idleSettings;
WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings,IdleCanWakeFromS0);

idleSettings.IdleTimeout = WIFI_DEFAULT_IDLE_TIMEOUT_HINT_MS; // 3 seconds
idleSettings.IdleTimeoutType = SystemManagedIdleTimeoutWithHint;
    status = WdfDeviceAssignS0IdleSettings(DeviceContext->WdfDevice, &idleSettings);