Compartilhar via


Desenvolver um driver de cliente WiFiCx

Inicialização de dispositivo e adaptador

Além das tarefas que o NetAdapterCx requer para a inicialização do dispositivo NetAdapter, um driver cliente WiFiCx também executa essas tarefas em sua função de retorno de chamada EvtDriverDeviceAdd :

  1. Chame WifiDeviceInitConfig depois de chamar NetDeviceInitConfig , mas antes de chamar WdfDeviceCreate, referenciando o 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 do WdfDeviceCreate.

O exemplo a seguir demonstra como inicializar o dispositivo WiFiCx. O exemplo omite o tratamento de erros 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);

Captura de tela do processo de inicialização do driver do cliente WiFiCx.

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

Em seguida, o driver cliente deve definir todos os recursos de dispositivo Wi-Fi específicos, normalmente na função de retorno de chamada EvtDevicePrepareHardware a seguir. Se o hardware precisar que interrupções estejam habilitadas para consultar capacidades de firmware, isso pode ser feito em EvtWdfDeviceD0EntryPostInterruptsEnabled.

O WiFiCx não chama mais WDI_TASK_OPEN ou WDI_TASK_CLOSE para instruir os clientes a carregar ou descarregar firmware e não consulta recursos de Wi-Fi usando o comando WDI_GET_ADAPTER_CAPABILITIES .

Ao contrário de outros tipos de drivers NetAdapterCx, os drivers WiFiCx não criam o objeto NETADAPTER na função de retorno de chamada EvtDriverDeviceAdd . Em vez disso, o WiFiCx instrui os drivers a criar o NetAdapter (estação) padrão mais tarde usando o retorno de chamada EvtWifiDeviceCreateAdapter depois que o retorno de chamada EvtDevicePrepareHardware do cliente for bem-sucedido. WiFiCx e WDI não chamam 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 ou adaptador, como SET_ADAPTER_CONFIGURATION e TASK_SET_RADIO_STATE.

Consulte o callback de evento para a criação do adaptador para obter um exemplo de código de EvtWifiDeviceCreateAdapter.

Fluxograma mostrando a criação do adaptador de estação do cliente do driver 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 do WiFiCx. Consulte a 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 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 definido 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 o status BUFFER_OVERFLOW.

  • Se o comando for um comando de tarefa, o driver cliente precisará enviar posteriormente a indicação M4 associada chamando WifiDeviceReceiveIndication e passar o buffer de indicação com um cabeçalho WDI que contenha 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 do Wi-Fi Direct (P2P)

As seções a seguir explicam como os drivers WiFiCx dão suporte 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 cliente chama WifiDeviceSetWiFiDirectCapabilities para reportar as capacidades diretas Wi-Fi para o WiFiCx na fase de definição de capacidades do dispositivo.

WIFI_WIFIDIRECT_CAPABILITIES wfdCapabilities = {};

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

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

Wi-Fi Chamada direta de retorno de evento 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 de 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;
}

Callback de evento para criação de adaptador

Os drivers de cliente criam o adaptador de estação e o adaptador WfdRole usando o mesmo callback 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 de transmissão 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 direta do arquivo INI/INF

As funcionalidades do vWifi foram substituídas pelo NetAdapter. Se você estiver portando do driver baseado em WDI, os arquivos INI/INF devem ter as informações relacionadas ao vWIFI removidas.

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 isn't a physical device.
*IfConnectorPresent     = 1     ; true

; In most cases, 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 configurar filas diferentes para diferentes pares, ele o fará configurando as propriedades apropriadas do DEMUX. Se você adicionar propriedades de demux, a contagem de filas Tx será o produto do número máximo de nós conectados (peers) e do número máximo de tids, mais 1 (para transmissão ou multicast).

Várias filas para QOS

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

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

Isso faz com que o tradutor crie até oito filas Tx sob demanda, dependendo do valor NBL WlanTagHeader::WMMInfo.

O driver cliente consulta a prioridade que o framework usa para essa fila a partir do EvtPacketQueueStart:

auto const priority = WifiTxQueueGetDemuxWmmInfo(queue);

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

Várias filas para pares

Antes de usar um objeto NETADAPTER_INIT * para criar um NETADAPTER, o driver 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 cliente consulta o endereço par que a estrutura usa para essa fila do EvtPacketQueueStart:

auto const peerAddress = WifiTxQueueGetDemuxPeerAddress(queue);

Todos os pacotes colocados nessa fila entre EvtStart e EvtStop têm a prioridade determinada.

A estrutura abre filas somente para endereços pares que o driver adiciona usando as seguintes APIs:

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

WifiAdapterRemovePeer: Informa ao WiFiCx que um dispositivo foi desconectado. A estrutura interrompe a fila associada.

Tempo de vida do par

Alterações na política de energia

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

Para dar suporte ao funcionamento em inatividade do dispositivo quando o sistema estiver em seu estado de trabalho (S0), o driver chama WdfDeviceAssignS0IdleSettings e define o membro IdleTimeoutType de WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS para 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);