编写 WiFiCx 客户端驱动程序

设备和适配器初始化

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

  1. 在调用 NetDeviceInitConfig 之后调用 WifiDeviceInitConfig ,但在调用 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_OPEN/WDI_TASK_CLOSE 来指示客户端加载/卸载固件,也不会通过 WDI_GET_ADAPTER_CAPABILITIES 命令查询 Wi-Fi 功能。

与其他类型的 NetAdapterCx 驱动程序不同,WiFiCx 驱动程序不得从 EvtDriverDeviceAdd 回调函数中创建 NETADAPTER 对象。 相反,WiFiCx 会指示驱动程序在客户端的 EvtDevicePrepareHardware 回调成功) 后,使用 EvtWifiDeviceCreateAdapter 回调 (创建默认的 NetAdapter (工作站) 。 此外,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。

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

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

  • 未经请求的指示也通过 WifiDeviceReceiveIndication 通知,但 WDI_MESSAGE_HEADERTransactionId 成员设置为 0

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

Wi-Fi直接 (P2P) 支持

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

Wi-Fi Direct 设备功能

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”的 Direct 事件回调

对于 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直接 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 is not a physical device.
*IfConnectorPresent     = 1     ; true

; In most cases, you can 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 已断开对等互连。 这会导致框架停止关联的队列。

对等生存期

电源策略更改

对于电源管理,客户端驱动程序应 像其他类型的 NetAdapterCx 客户端驱动程序一样使用 NETPOWERSETTINGS 对象。

若要在系统处于工作 (S0) 状态时支持设备闲置,驱动程序必须调用 WdfDeviceAssignS0IdleSettings 并将 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGSIdleTimeoutType 成员设置为 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);