Écriture d’un pilote client WiFiCx

Initialisation de l’appareil et de l’adaptateur

Outre les tâches requises par NetAdapterCx pour l’initialisation de l’appareil NetAdapter, un pilote client WiFiCx doit également effectuer les tâches suivantes dans sa fonction de rappel EvtDriverDeviceAdd :

  1. Appelez WifiDeviceInitConfig après avoir appelé NetDeviceInitConfig , mais avant d’appeler WdfDeviceCreate, en référençant le même objet WDFDEVICE_INIT passé par le framework.

  2. Appelez WifiDeviceInitialize pour inscrire des fonctions de rappel spécifiques à l’appareil WiFiCx, à l’aide d’une structure WIFI_DEVICE_CONFIG initialisée et de l’objet WDFDEVICE obtenu à partir de WdfDeviceCreate.

L’exemple suivant montre comment initialiser l’appareil WiFiCx. La gestion des erreurs a été laissée de côté pour plus de clarté.

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

Ce diagramme de flux de messages montre le processus d’initialisation.

Diagramme montrant le processus d’initialisation du pilote client WiFiCx.

Flux de création de l’adaptateur (station) par défaut

Ensuite, le pilote client doit définir toutes les Wi-Fi fonctionnalités d’appareil spécifiques, généralement dans la fonction de rappel EvtDevicePrepareHardware qui suit. Si votre matériel a besoin d’interruptions pour pouvoir interroger les fonctionnalités du microprogramme, vous pouvez le faire dans EvtWdfDeviceD0EntryPostInterruptsEnabled.

Notez que WiFiCx n’appelle plus WDI_TASK_OPEN/WDI_TASK_CLOSE pour indiquer aux clients de charger/décharger le microprogramme, ni d’interroger les fonctionnalités Wi-Fi via la commande WDI_GET_ADAPTER_CAPABILITIES .

Contrairement à d’autres types de pilotes NetAdapterCx, les pilotes WiFiCx ne doivent pas créer l’objet NETADAPTER à partir de la fonction de rappel EvtDriverDeviceAdd . Au lieu de cela, WiFiCx demande aux pilotes de créer le NetAdapter (station) par défaut ultérieurement à l’aide du rappel EvtWifiDeviceCreateAdapter (une fois que le rappel EvtDevicePrepareHardware du client a réussi). En outre, WiFiCx/WDI n’appelle plus la commande WDI_TASK_CREATE_PORT .

Dans sa fonction de rappel EvtWifiDeviceCreateAdapter , le pilote client doit :

  1. Appelez NetAdapterCreate pour créer l’objet NetAdapter.

  2. Appelez WifiAdapterInitialize pour initialiser le contexte WiFiCx et l’associer à cet objet NetAdapter.

  3. Appelez NetAdapterStart pour démarrer l’adaptateur.

Si cela réussit, WiFiCx envoie des commandes d’initialisation pour l’appareil/adaptateur (par exemple, SET_ADAPTER_CONFIGURATION, TASK_SET_RADIO_STATE, etc.).

Pour obtenir un exemple de code d’EvtWifiDeviceCreateAdapter, consultez Rappel d’événement pour la création de l’adaptateur.

Organigramme montrant la création de l’adaptateur de station de pilote client WiFiCx.

Gestion des messages de commande WiFiCx

Les messages de commande WiFiCx sont basés sur les commandes précédentes du modèle WDI pour la plupart des opérations de chemin de contrôle. Ces commandes sont définies dans les OID de tâche WiFiCx, les OID de propriété WiFiCx et lesindications de status WiFiCx. Pour plus d’informations, consultez Structure des messages WiFiCx .

Les commandes sont échangées via un ensemble de fonctions de rappel fournies par le pilote client et les API fournies par WiFiCx :

  • WiFiCx envoie un message de commande au pilote client en appelant sa fonction de rappel EvtWifiDeviceSendCommand .

  • Pour récupérer le message, le pilote client appelle WifiRequestGetInOutBuffer pour obtenir la mémoire tampon d’entrée/sortie et les longueurs de mémoire tampon. Le pilote doit également appeler WifiRequestGetMessageId pour récupérer l’ID de message.

  • Pour terminer la demande, le pilote envoie le M3 pour la commande de façon asynchrone en appelant WifiRequestComplete.

  • Si la commande est une commande set et que la requête d’origine ne contient pas une mémoire tampon suffisamment grande, le client doit appeler WifiRequestSetBytesNeeded pour définir la taille de mémoire tampon requise, puis faire échouer la demande avec status BUFFER_OVERFLOW.

  • Si la commande est une commande de tâche, le pilote client doit envoyer ultérieurement l’indication M4 associée en appelant WifiDeviceReceiveIndication et passer la mémoire tampon d’indication avec un en-tête WDI qui contient le même ID de message que celui contenu dans le M1.

  • Les indications non sollicitées sont également signalées via WifiDeviceReceiveIndication, mais avec le membre TransactionId de WDI_MESSAGE_HEADER défini sur 0.

Organigramme montrant la gestion des messages de commande du pilote WiFiCx.

Prise en charge de Wi-Fi Direct (P2P)

Les sections suivantes décrivent comment les pilotes WiFiCx peuvent prendre en charge Wi-Fi Direct.

fonctionnalités d’appareil direct Wi-Fi

WIFI_WIFIDIRECT_CAPABILITIES représente toutes les fonctionnalités pertinentes qui ont été précédemment définies dans WDI via les WDI_P2P_CAPABILITIES et WDI_AP_CAPABILITIES TLVs. Le pilote client appelle WifiDeviceSetWiFiDirectCapabilities pour signaler Wi-Fi fonctionnalités directes à WiFiCx dans la phase définir les fonctionnalités de l’appareil.

WIFI_WIFIDIRECT_CAPABILITIES wfdCapabilities = {};

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

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

Wi-Fi rappel d’événement direct pour « WfdDevice »

Pour Wi-Fi Direct, « WfdDevice » est un objet de contrôle sans fonctionnalités de chemin d’accès aux données. Par conséquent, WiFiCx a un nouvel objet WDFObject nommé WIFIDIRECTDEVICE. Dans leur fonction de rappel EvtWifiDeviceCreateWifiDirectDevice , les pilotes clients :

Cet exemple montre comment créer et initialiser un objet 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;
}

Rappel d’événement pour la création de l’adaptateur

Les pilotes clients créent l’adaptateur de station et l’adaptateur WfdRole à l’aide du même rappel d’événement : EvtWifiDeviceCreateAdapter.

  • Appelez WifiAdapterGetType pour déterminer le type d’adaptateur.
  • Si le pilote doit interroger le type d’adaptateur à partir de l’objet NETADAPTER_INIT avant sa création, appelez WifiAdapterInitGetType.
  • Appeler WifiAdapterGetPortId détermine l’ID de port (utilisé dans les commandes de message).
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 prise en charge dans les files d’attente Tx

ExemptionAction est une nouvelle extension de paquet NetAdapter qui indique si le paquet est censé être exempté des opérations de chiffrement effectuées par le client. Pour plus d’informations, consultez la documentation sur nousExemptionActionType .

#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 modification directe du fichier INI/INF

Les fonctionnalités vWifi ont été remplacées par NetAdapter. Si vous effectuez un portage à partir d’un pilote WDI, INI/INF doit supprimer les informations relatives à 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$

Modification du chemin des données NetAdapter

Configuration de plusieurs files d’attente Tx

Par défaut, NetAdapterCx crée une file d’attente Tx pour tous les paquets destinés à un NetAdapter.

Si un pilote doit prendre en charge plusieurs files d’attente Tx pour QOS ou doit configurer différentes files d’attente pour différents homologues, il peut le faire en configurant les propriétés DEMUX appropriées. Si des propriétés demux sont ajoutées, le nombre de files d’attente Tx est le produit du nombre maximal d’homologues et du nombre maximal de tids, plus 1 (pour la diffusion/multidiffusion).

Plusieurs files d’attente pour QOS

Avant d’utiliser un objet NETADAPTER_INIT * pour créer un NETADAPTER, le pilote client doit y ajouter un demux WMMINFO :

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

Cela amènera le traducteur à créer jusqu’à 8 files d’attente Tx à la demande, en fonction de la valeur NBL WlanTagHeader::WMMInfo.

Le pilote client doit interroger la priorité que le framework utilisera pour cette file d’attente à partir d’EvtPacketQueueStart :

auto const priority = WifiTxQueueGetDemuxWmmInfo(queue);

Tous les paquets placés dans cette file d’attente entre EvtStart et EvtStop auront la priorité donnée.

Plusieurs files d’attente pour les homologues

Avant d’utiliser un objet NETADAPTER_INIT * pour créer un NETADAPTER, le pilote client doit y ajouter PEER_ADDRESS demux :

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

Le pilote client doit interroger l’adresse homologue que le framework utilisera pour cette file d’attente à partir d’EvtPacketQueueStart :

auto const peerAddress = WifiTxQueueGetDemuxPeerAddress(queue);

Tous les paquets placés dans cette file d’attente entre EvtStart et EvtStop seront destinés à cet homologue.

Les files d’attente sont uniquement ouvertes pour les adresses homologues ajoutées par le pilote à l’aide des API suivantes :

WifiAdapterAddPeer : indique à WiFiCx qu’un homologue s’est connecté à l’adresse donnée. WiFiCx utilise cette adresse avec le démultiplexing d’homologue en associant une file d’attente à l’adresse de l’homologue. Le nombre maximal d’homologues que le pilote peut ajouter ne doit pas dépasser la valeur de plage fournie lors de l’ajout d’informations de démultiplexation Tx.

WifiAdapterRemovePeer : indique à WiFiCx qu’un homologue a été déconnecté. Cela entraîne l’arrêt de la file d’attente associée par l’infrastructure.

Durée de vie de l’homologue

Modifications de la stratégie d’alimentation

Pour la gestion de l’alimentation, les pilotes clients doivent utiliser l’objet NETPOWERSETTINGS comme d’autres types de pilotes clients NetAdapterCx.

Pour prendre en charge la marche au ralenti des appareils lorsque le système est dans son état de fonctionnement (S0), le pilote doit appeler WdfDeviceAssignS0IdleSettings et définir le membre IdleTimeoutType de WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS sur 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);