共用方式為


撰寫WiFiCx用戶端驅動程式

裝置和配接器初始化

除了 NetAdapterCx NetAdapter 裝置初始化所需的工作之外,WiFiCx 用戶端驅動程式也必須在其 EvtDriverDeviceAdd 回呼函式中執行下列工作:

  1. 在呼叫 NetDeviceInitConfig 之後,但在呼叫 WdfDeviceCreate之前,呼叫 WifiDeviceInitConfig,參考框架傳入的相同 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 會指示驅動程式稍後使用 EvtWifiDeviceCreateAdapter 回呼來建立預設的 NetAdapter(在客戶端的 EvtDevicePrepareHardware 回呼成功之後)。 此外,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 屬性 OID,以及 WiFiCx 狀態指示。 如需詳細資訊,請參閱 WiFiCx訊息結構

命令會透過用戶端驅動程式提供的一組回呼函式交換,以及WiFiCx提供的API:

  • WiFiCx 會叫用其 EvtWifiDeviceSendCommand 回呼函式,將命令訊息傳送至客戶端驅動程式。

  • 若要擷取訊息,用戶端驅動程式會呼叫 WifiRequestGetInOutBuffer,以取得輸入/輸出緩衝區和緩衝區長度。 驅動程式也需要呼叫 WifiRequestGetMessageId 以擷取訊息標識碼。

  • 若要完成請求,驅動程式會呼叫 WifiRequestComplete,以異步方式傳送用於該命令的 M3。

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

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

  • 未經請求的通知也會透過 WifiDeviceReceiveIndication接收,但會將 WDI_MESSAGE_HEADERTransactionId 成員設定為 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 支援

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);