WiFiCx クライアント ドライバーの作成

デバイスとアダプターの初期化

NetAdapterCx が NetAdapter デバイスの初期化に必要とするタスクに加えて、WiFiCx クライアント ドライバーは、EvtDriverDeviceAdd コールバック関数で次のタスクも実行する必要があります。

  1. NetDeviceInitConfig を呼び出した後、WdfDeviceCreate を呼び出す前に WifiDeviceInitConfig を呼び出し、フレームワークによって渡されたのと同じWDFDEVICE_INIT オブジェクトを参照します。

  2. 初期化されたWIFI_DEVICE_CONFIG構造体と WdfDeviceCreate から取得した WDFDEVICE オブジェクトを使用して、WifiDeviceInitialize を呼び出して 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);

このメッセージ フロー図は、初期化プロセスを示しています。

Diagram showing the WiFiCx client driver initialization process.

既定のアダプター (ステーション) 作成フロー

次に、クライアント ドライバーは、通常、次の EvtDevicePrepareHardware コールバック関数で、すべての Wi-Fi 固有のデバイス機能を設定する必要があります。 ファームウェア機能を照会するためにハードウェアで割り込みを有効にする必要がある場合は、EvtWdfDeviceD0EntryPostInterruptsEnabled でこれを行うことができます。

現在、WiFiCx は、WDI_TASK_OPEN/WDI_TASK_CLOSE を呼び出して、ファームウェアの読み込み/アンロードをクライアントに指示したり、WDI_GET_ADAPTER_CAPABILITIES コマンドを使用して Wi-Fi 機能を照会したりしません。

他の種類の NetAdapterCx ドライバーと異なり、WiFiCx ドライバーは、EvtDriverDeviceAdd コールバック関数内から NETADAPTER オブジェクトを作成することができません。 代わりに、WiFiCx は、EvtWifiDeviceCreateAdapter コールバック (クライアントの EvtDevicePrepareHardware コールバックが成功した後) を使用して、既定の 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 のコード例については、「アダプター作成のためのイベント コールバック」を参照してください。

Flow chart showing WiFiCx client driver station adapter creation.

WiFiCx コマンド メッセージの処理

WiFiCx コマンド メッセージは、ほとんどの制御パス操作の前の WDI モデル コマンドに基づきます。 これらのコマンドは、WiFiCx タスク OIDWiFiCx プロパティ OID、および WiFiCx 状態表示で定義されます。 詳細については、「WiFiCxメッセージ構造 」を参照してください 。

コマンドは、クライアント ドライバーによって提供される一連のコールバック関数と、WiFiCx によって提供される API を介して交換されます。

  • WiFiCx は、その EvtWifiDeviceSendCommand コールバック関数を呼び出すことによって、クライアント ドライバーにコマンド メッセージを送信します。

  • メッセージを取得するために、クライアント ドライバーは WifiRequestGetInOutBuffer を呼び出して、入力/出力バッファーとバッファー長を取得します。 また、ドライバーは WifiRequestGetMessageId を呼び出してメッセージ ID を取得する必要もあります。

  • 要求を完了するために、ドライバーは WifiRequestComplete を呼び出すことによって、コマンドの M3 を非同期的に送信します。

  • コマンドが set コマンドで、元の要求に十分な大きさのバッファーが含まれていない場合、クライアントは WifiRequestSetBytesNeeded を呼び出して必要なバッファー サイズを設定し、状態BUFFER_OVERFLOW で要求を失敗させる必要があります。

  • コマンドがタスク コマンドの場合、クライアント ドライバーは後で WifiDeviceReceiveIndication を呼び出して関連付けられている M4 インジケーターを送信し、M1 に含まれているのと同じメッセージ ID を含む WDI ヘッダーを持つインジケーター バッファーを渡す必要があります。

  • 未承諾の表示も WifiDeviceReceiveIndication 経由で通知されますが、WDI_MESSAGE_HEADERTransactionId メンバーは 0 に設定されます。

Flow chart showing WiFiCx driver command message handling.

Wi-Fi Direct (P2P) のサポート

次のセクションでは、WiFiCx ドライバーで Wi-Fi Direct をサポートする方法について説明します。

Wi-Fi Direct デバイスの機能

WIFI_WIFIDIRECT_CAPABILITIESは、WDI_P2P_CAPABILITIES と WDI_AP_CAPABILITIES TLV を介して WDI で以前に設定された関連するすべての機能を表します。 クライアント ドライバーは WifiDeviceSetWiFiDirectCapabilities を呼び出して、設定されたデバイス機能フェーズで Wi-Fi Direct 機能を WiFiCx に報告します。

WIFI_WIFIDIRECT_CAPABILITIES wfdCapabilities = {};

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

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

"WfdDevice" の Wi-Fi 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;
}

アダプター作成のためのイベント コールバック

クライアント ドライバーは、EvtWifiDeviceCreateAdapter と同じイベント コールバックを使用してステーション アダプターと WfdRole アダプターを作成します。

  • WifiAdapterGetType を呼び出して、アダプターの種類を確認します。
  • ドライバーがアダプターを作成する前に、NETADAPTER_INIT オブジェクトからアダプターの種類を照会する必要がある場合は、WifiAdapterInitGetType を呼び出 します。
  • WifiAdapterGetPortId を呼び出すと、(メッセージ コマンドで使用される) ポート ID が特定されます。
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 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 を対象とするすべてのパケットに対して 1 つの 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 を追加する必要があります。

...
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 に通知します。 これにより、フレームワークは関連付けられているキューを停止します。

Peer lifetime

電源ポリシーの変更

電源管理の場合、クライアント ドライバーは、他の種類の NetAdapterCx クライアント ドライバーと同様に NETPOWERSETTINGS オブジェクトを使用する必要があります。

システムが動作中 (S0) 状態のときにデバイスのアイドル状態をサポートするには、ドライバーが WdfDeviceAssignS0Idle を呼び出し、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);