撰寫 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 來擷取訊息識別碼。

  • 若要完成要求,驅動程式會呼叫 WifiRequestComplete以非同步方式傳送命令的 M3。

  • 如果命令是 set 命令,且原始要求未包含夠大的緩衝區,用戶端應該呼叫 WifiRequestSetBytesNeeded 來設定所需的緩衝區大小,然後讓要求失敗並顯示狀態BUFFER_OVERFLOW。

  • 如果命令是工作命令,用戶端驅動程式稍後必須呼叫 WifiDeviceReceiveIndication 來傳送相關聯的 M4 指示,並使用包含 M1 中相同訊息識別碼的 WDI 標頭傳遞指示緩衝區。

  • 未經請求的指示也會透過 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 ,以在設定裝置功能階段將Wi-Fi直接功能回報給 WiFiCx。

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 支援

ExemptAction 是新的 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 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);

這會導致翻譯工具視 NBL WlanTagHeader::WMMInfo 值而定,視需求建立最多 8 個 Tx 佇列。

用戶端驅動程式應該從 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_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);