Escrever um driver de cliente WiFiCx

Inicialização de dispositivo e adaptador

Além das tarefas exigidas pelo NetAdapterCx para a inicialização do dispositivo NetAdapter, um driver de cliente WiFiCx também deve executar as seguintes tarefas em sua função de retorno de chamada EvtDriverDeviceAdd :

  1. Chame WifiDeviceInitConfig depois de chamar NetDeviceInitConfig , mas antes de chamar WdfDeviceCreate, fazendo referência ao mesmo objeto WDFDEVICE_INIT passado pela estrutura.

  2. Chame WifiDeviceInitialize para registrar funções de retorno de chamada específicas do dispositivo WiFiCx, usando uma estrutura de WIFI_DEVICE_CONFIG inicializada e o objeto WDFDEVICE obtido de WdfDeviceCreate.

O exemplo a seguir demonstra como inicializar o dispositivo WiFiCx. O tratamento de erros foi deixado de fora para maior clareza.

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

Este diagrama de fluxo de mensagem mostra o processo de inicialização.

Diagrama mostrando o processo de inicialização do driver do cliente WiFiCx.

Fluxo de criação do adaptador padrão (estação)

Em seguida, o driver do cliente deve definir todas as Wi-Fi funcionalidades específicas do dispositivo, normalmente na função de retorno de chamada EvtDevicePrepareHardware a seguir. Se o hardware precisar de interrupções para ser habilitado para consultar recursos de firmware, isso poderá ser feito em EvtWdfDeviceD0EntryPostInterruptsEnabled.

Observe que o WiFiCx não chama mais WDI_TASK_OPEN/WDI_TASK_CLOSE para instruir os clientes a carregar/descarregar firmware nem consultará recursos de Wi-Fi por meio do comando WDI_GET_ADAPTER_CAPABILITIES .

Ao contrário de outros tipos de drivers NetAdapterCx, os drivers WiFiCx não devem criar o objeto NETADAPTER de dentro da função de retorno de chamada EvtDriverDeviceAdd . Em vez disso, o WiFiCx instruirá os drivers a criar o NetAdapter padrão (estação) posteriormente usando o retorno de chamada EvtWifiDeviceCreateAdapter (depois que o retorno de chamada EvtDevicePrepareHardware do cliente for bem-sucedido). Além disso, o WiFiCx/WDI não chama mais o comando WDI_TASK_CREATE_PORT .

Em sua função de retorno de chamada EvtWifiDeviceCreateAdapter , o driver cliente deve:

  1. Chame NetAdapterCreate para criar o novo objeto NetAdapter.

  2. Chame WifiAdapterInitialize para inicializar o contexto WiFiCx e associá-lo a esse objeto NetAdapter.

  3. Chame NetAdapterStart para iniciar o adaptador.

Se isso for bem-sucedido, o WiFiCx enviará comandos de inicialização para o dispositivo/adaptador (por exemplo, SET_ADAPTER_CONFIGURATION, TASK_SET_RADIO_STATE etc.).

Para obter um exemplo de código de EvtWifiDeviceCreateAdapter, consulte Retorno de chamada de evento para criação do adaptador.

Fluxograma mostrando a criação do adaptador de estação do driver do cliente WiFiCx.

Manipulando mensagens de comando WiFiCx

As mensagens de comando WiFiCx são baseadas nos comandos de modelo WDI anteriores para a maioria das operações de caminho de controle. Esses comandos são definidos em OIDs de tarefa WiFiCx, OIDs de propriedade WiFiCx e indicações de status WiFiCx. Consulte Estrutura de mensagens WiFiCx para obter mais informações.

Os comandos são trocados por meio de um conjunto de funções de retorno de chamada fornecidas pelo driver cliente e pelas APIs fornecidas pelo WiFiCx:

  • O WiFiCx envia uma mensagem de comando para o driver cliente invocando sua função de retorno de chamada EvtWifiDeviceSendCommand .

  • Para recuperar a mensagem, o driver do cliente chama WifiRequestGetInOutBuffer para obter o buffer de entrada/saída e os comprimentos do buffer. O driver também precisa chamar WifiRequestGetMessageId para recuperar a ID da mensagem.

  • Para concluir a solicitação, o driver envia o M3 para o comando de forma assíncrona chamando WifiRequestComplete.

  • Se o comando for um comando set e a solicitação original não contiver um buffer grande o suficiente, o cliente deverá chamar WifiRequestSetBytesNeeded para definir o tamanho do buffer necessário e, em seguida, falhar a solicitação com status BUFFER_OVERFLOW.

  • Se o comando for um comando de tarefa, o driver do cliente precisará enviar posteriormente a indicação M4 associada chamando WifiDeviceReceiveIndication e passar o buffer de indicação com um cabeçalho WDI que contém a mesma ID de mensagem contida no M1.

  • As indicações não solicitadas também são notificadas por meio de WifiDeviceReceiveIndication, mas com o membro TransactionId de WDI_MESSAGE_HEADER definido como 0.

Fluxograma mostrando o tratamento de mensagens de comando do driver WiFiCx.

Suporte ao Wi-Fi Direct (P2P)

As seções a seguir descrevem como os drivers WiFiCx podem dar suporte a Wi-Fi Direct.

Wi-Fi recursos de dispositivo Direto

WIFI_WIFIDIRECT_CAPABILITIES representa todos os recursos relevantes que foram definidos anteriormente no WDI por meio das TLVs WDI_P2P_CAPABILITIES e WDI_AP_CAPABILITIES. O driver do cliente chama WifiDeviceSetWiFiDirectCapabilities para relatar Wi-Fi recursos diretos para o WiFiCx na fase de recursos do dispositivo definido.

WIFI_WIFIDIRECT_CAPABILITIES wfdCapabilities = {};

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

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

Wi-Fi retorno de chamada de evento direto para "WfdDevice"

Para Wi-Fi Direct, o "WfdDevice" é um objeto de controle sem recursos de caminho de dados. Portanto, o WiFiCx tem um novo WDFObject chamado WIFIDIRECTDEVICE. Na função de retorno de chamada EvtWifiDeviceCreateWifiDirectDevice , os drivers do cliente:

Este exemplo mostra como criar e inicializar um objeto 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;
}

Retorno de chamada de evento para criação do adaptador

Os drivers cliente criam o adaptador de estação e o adaptador WfdRole usando o mesmo retorno de chamada de evento: 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;
}

Wi-Fi suporte a ExemptionAction em filas Tx

ExemptionAction é uma nova extensão de pacote NetAdapter que indica se o pacote deve ser isento de quaisquer operações de criptografia executadas pelo cliente. Leia a documentação sobre usExemptionActionType para obter detalhes.

#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 alteração de arquivo INI/INF direto

As funcionalidades do vWifi foram substituídas pelo NetAdapter. Se você estiver portando do driver baseado em WDI, o INI/INF deverá remover as informações relacionadas ao 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$

Alteração do caminho de dados do NetAdapter

Configurando várias filas Tx

Por padrão, NetAdapterCx criará uma fila Tx para todos os pacotes destinados a um NetAdapter.

Se um driver precisar dar suporte a várias filas Tx para QOS ou precisar configurar filas diferentes para pares diferentes, ele poderá fazer isso configurando as propriedades apropriadas do DEMUX. Se as propriedades de demux forem adicionadas, a contagem de filas Tx será o produto do número máximo de pares e do número máximo de tids, mais 1 (para difusão/multicast).

Várias filas para QOS

Antes de usar um objeto NETADAPTER_INIT * para criar um NETADAPTER, o driver do cliente deve adicionar o WMMINFO demux a ele:

...
WIFI_ADAPTER_TX_DEMUX wmmInfoDemux;
WIFI_ADAPTER_TX_WMMINFO_DEMUX_INIT(&wmmInfoDemux);
WifiAdapterInitAddTxDemux(adapterInit, &wmmInfoDemux);

Isso fará com que o tradutor crie até 8 filas Tx sob demanda, dependendo do valor WlanTagHeader::WMMInfo da NBL.

O driver do cliente deve consultar a prioridade que a estrutura usará para essa fila de EvtPacketQueueStart:

auto const priority = WifiTxQueueGetDemuxWmmInfo(queue);

Todos os pacotes colocados nessa fila entre EvtStart e EvtStop terão a prioridade especificada.

Várias filas para pares

Antes de usar um objeto NETADAPTER_INIT * para criar um NETADAPTER, o driver do cliente deve adicionar PEER_ADDRESS demux a ele:

...
WIFI_ADAPTER_TX_DEMUX peerInfoDemux;
WIFI_ADAPTER_TX_PEER_ADDRESS_DEMUX_INIT(&peerInfoDemux, maxNumOfPeers);
WifiAdapterInitAddTxDemux(adapterInit, &peerInfoDemux);

O driver do cliente deve consultar o endereço par que a estrutura usará para essa fila de EvtPacketQueueStart:

auto const peerAddress = WifiTxQueueGetDemuxPeerAddress(queue);

Todos os pacotes colocados nessa fila entre EvtStart e EvtStop serão para esse par.

As filas só são abertas para endereços pares que o driver adicionou usando as seguintes APIs:

WifiAdapterAddPeer: informa ao WiFiCx que um par se conectou com o endereço especificado. O WiFiCx usará esse endereço com demultiplexing par associando uma fila ao endereço par. O número máximo de pares que o driver pode adicionar não deve exceder o valor de intervalo fornecido ao adicionar informações de demultiplexing do Tx.

WifiAdapterRemovePeer: informa ao WiFiCx que um par foi desconectado. Isso faz com que a estrutura interrompa a fila associada.

Tempo de vida do par

Alterações na política de energia

Para o gerenciamento de energia, os drivers de cliente devem usar o objeto NETPOWERSETTINGS como outros tipos de drivers de cliente NetAdapterCx.

Para dar suporte à idling de dispositivo quando o sistema estiver em seu estado de trabalho (S0), o driver deve chamar WdfDeviceAssignS0IdleSettings e definir o membro IdleTimeoutType de WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS como 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);