Написание драйвера клиента WiFiCx

Инициализация устройства и адаптера

Помимо задач, необходимых NetAdapterCx для инициализации устройства NetAdapter, драйвер клиента WiFiCx также должен выполнять следующие задачи в функции обратного вызова EvtDriverDeviceAdd :

  1. Вызовите WifiDeviceInitConfig после вызова NetDeviceInitConfig, но перед вызовом WdfDeviceCreate, ссылаясь на тот же объект WDFDEVICE_INIT, переданный платформой.

  2. Вызовите WifiDeviceInitialize , чтобы зарегистрировать функции обратного вызова для конкретного устройства WiFiCx, используя инициализированную структуру WIFI_DEVICE_CONFIG и объект WDFDEVICE, полученный из WdfDeviceCreate.

В следующем примере показано, как инициализировать устройство 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 , чтобы указать клиентам загружать или выгружать встроенное ПО, и не будет запрашивать возможности Wi-Fi с помощью команды WDI_GET_ADAPTER_CAPABILITIES .

В отличие от других типов драйверов NetAdapterCx, драйверы WiFiCx не должны создавать объект NETADAPTER из функции обратного вызова EvtDriverDeviceAdd . Вместо этого WiFiCx предписывает драйверам создать netAdapter (станцию) по умолчанию позже с помощью обратного вызова EvtWifiDeviceCreateAdapter (после успешного обратного вызова EvtDevicePrepareHardware клиента). Кроме того, WiFiCx/WDI больше не вызывает команду WDI_TASK_CREATE_PORT .

В функции обратного вызова EvtWifiDeviceCreateAdapter драйвер клиента должен:

  1. Вызовите NetAdapterCreate , чтобы создать новый объект NetAdapter.

  2. Вызовите WifiAdapterInitialize , чтобы инициализировать контекст WiFiCx и связать его с этим объектом NetAdapter.

  3. Вызовите NetAdapterStart , чтобы запустить адаптер.

В этом случае WiFiCx отправит команды инициализации для устройства или адаптера (например, SET_ADAPTER_CONFIGURATION, TASK_SET_RADIO_STATE и т. д.).

Пример кода EvtWifiDeviceCreateAdapter см. в разделе Обратный вызов события для создания адаптера.

Блок-схема, показывающая создание адаптера драйвера драйвера клиента WiFiCx.

Обработка командных сообщений WiFiCx

Сообщения команд WiFiCx основаны на предыдущих командах модели WDI для большинства операций пути управления. Эти команды определяются в идентификаторах задач WiFiCx, идентификаторах свойств WiFiCx и индикаторах состояния WiFiCx. Дополнительные сведения см. в статье Структура сообщений WiFiCx .

Команды обмениваются с помощью набора функций обратного вызова, предоставляемых драйвером клиента и ИНТЕРФЕЙСами API, предоставляемыми WiFiCx:

  • WiFiCx отправляет командное сообщение драйверу клиента, вызывая функцию обратного вызова EvtWifiDeviceSendCommand .

  • Чтобы получить сообщение, драйвер клиента вызывает WifiRequestGetInOutBuffer , чтобы получить буфер ввода-вывода и длину буфера. Драйвер также должен вызвать WifiRequestGetMessageId , чтобы получить идентификатор сообщения.

  • Чтобы выполнить запрос, драйвер отправляет M3 для команды асинхронно, вызывая WifiRequestComplete.

  • Если команда является командой set и исходный запрос не содержал достаточно большого буфера, клиент должен вызвать WifiRequestSetBytesNeeded , чтобы задать необходимый размер буфера, а затем завершить запрос с состоянием BUFFER_OVERFLOW.

  • Если команда является командой задачи, драйвер клиента должен позже отправить соответствующее указание M4, вызвав WifiDeviceReceiveIndication и передать буфер индикации с заголовком WDI, который содержит тот же идентификатор сообщения, что и в M1.

  • Нежелательные признаки также уведомляются через WifiDeviceReceiveIndication, но для элемента TransactionIdWDI_MESSAGE_HEADER задано значение 0.

Блок-схема, показывающая обработку сообщений команды драйвера WiFiCx.

Поддержка Wi-Fi Direct (P2P)

В следующих разделах описывается, как драйверы WiFiCx могут поддерживать Wi-Fi Direct.

возможности Wi-Fi устройств direct

WIFI_WIFIDIRECT_CAPABILITIES представляет все соответствующие возможности, которые ранее были заданы в WDI с помощью WDI_P2P_CAPABILITIES и WDI_AP_CAPABILITIES TLV. Драйвер клиента вызывает 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 Direct для WfdDevice

Для Wi-Fi Direct WfdDevice — это управляющий объект без возможностей пути к данным. Таким образом, WiFiCx имеет новый объект WDFObject с именем WIFIDIRECTDEVICE. В функции обратного вызова EvtWifiDeviceCreateWifiDirectDevice клиентские драйверы:

  • Вызовите WifiDirectDeviceCreate , чтобы создать объект WIFIDIRECTDEVICE.
  • Вызовите WifiDirectDeviceInitialize , чтобы инициализировать объект .
  • Вызовите WifiDirectDeviceGetPortId , чтобы определить идентификатор порта (который используется в сообщениях команд).

В этом примере показано, как создать и инициализировать объект 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.

  • Вызовите WifiAdapterGetType , чтобы определить тип адаптера.
  • Если драйверу необходимо запросить тип адаптера из объекта NETADAPTER_INIT перед созданием адаптера, вызовите WifiAdapterInitGetType.
  • ВызовИте WifiAdapterGetPortId и определите идентификатор порта (используется в командах сообщения).
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;
}

поддержка Wi-Fi ExemptionAction в очередях Tx

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 создает одну очередь Tx для всех пакетов, предназначенных для NetAdapter.

Если драйверу требуется поддержка нескольких очередей Tx для QOS или необходимо настроить разные очереди для разных одноранговых узлов, это можно сделать, настроив соответствующие свойства DEMUX. При добавлении свойств demux число очередей Tx — это результат максимального числа одноранговых узлов и максимального числа приливов плюс 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);

Все пакеты, помещенные в эту очередь между EvtStart и EvtStop, будут иметь заданный приоритет.

Несколько очередей для одноранговых узлов

Перед использованием объекта 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);

Все пакеты, помещенные в эту очередь между EvtStart и EvtStop, будут использоваться для этого однорангового узла.

Очереди открываются только для одноранговых адресов, добавленных драйвером с помощью следующих API:

WifiAdapterAddPeer: сообщает WiFiCx, что одноранговый узел подключен с заданным адресом. WiFiCx будет использовать этот адрес с демультлексированием одноранговых узлов путем связывания очереди с адресом однорангового узла. Максимальное число одноранговых узлов, которое может добавить драйвер, не должно превышать значение диапазона, указанное при добавлении сведений о демультиплексировании Tx.

WifiAdapterRemovePeer: сообщает WiFiCx, что одноранговый узел был отключен. Это приводит к тому, что платформа остановит связанную очередь.

Время существования однорангового узла

Изменения в политике питания

Для управления питанием клиентские драйверы должны использовать объект NETPOWERSETTINGS , как и другие типы клиентских драйверов NetAdapterCx.

Для поддержки простоя устройства, когда система находится в рабочем (S0) состоянии, драйвер должен вызвать WdfDeviceAssignS0IdleSettings и задать для элемента IdleTimeoutTypeWDF_DEVICE_POWER_POLICY_IDLE_SETTINGSзначение 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);