设备和适配器初始化
除了 NetAdapterCx 需要 NetAdapter 设备初始化的任务外,WiFiCx 客户端驱动程序还在其 EvtDriverDeviceAdd 回调函数中执行这些任务:
调用 WifiDeviceInitConfig 在调用 NetDeviceInitConfig 之后,但在调用 WdfDeviceCreate 之前,引用框架传入的同一 WDFDEVICE_INIT 对象。
调用 WifiDeviceInitialize 以使用初始化的WIFI_DEVICE_CONFIG结构和从 WdfDeviceCreate 获取的 WDFDEVICE 对象注册 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);
默认适配器(工作站)创建流
接下来,客户端驱动程序必须设置所有 Wi-Fi 特定设备功能,通常位于后面的 EvtDevicePrepareHardware 回调函数中。 如果硬件需要中断才能查询固件功能,可以在 EvtWdfDeviceD0EntryPostInterruptsEnabled 中执行此作。
WiFiCx 不再调用 WDI_TASK_OPEN 或 WDI_TASK_CLOSE 来指示客户端加载或卸载固件,并且它不会使用 WDI_GET_ADAPTER_CAPABILITIES 命令查询 Wi-Fi 功能。
与其他类型的 NetAdapterCx 驱动程序不同,WiFiCx 驱动程序不会在 EvtDriverDeviceAdd 回调函数中创建 NETADAPTER 对象。 WiFiCx 指示驱动程序在客户端的 EvtDevicePrepareHardware 回调成功后,再通过 EvtWifiDeviceCreateAdapter 回调来创建默认 NetAdapter(station)。 WiFiCx 和 WDI 不再调用 WDI_TASK_CREATE_PORT 命令。
在其 EvtWifiDeviceCreateAdapter 回调函数中,客户端驱动程序必须:
调用 NetAdapterCreate 创建新的 NetAdapter 对象。
调用 WifiAdapterInitialize 初始化 WiFiCx 上下文并将其与此 NetAdapter 对象相关联。
调用 NetAdapterStart 以启动适配器。
如果成功,WiFiCx 将为设备或适配器(例如 SET_ADAPTER_CONFIGURATION 和 TASK_SET_RADIO_STATE)发送初始化命令。
有关EvtWifiDeviceCreateAdapter的代码示例,请参阅适配器创建的事件回调。
处理 WiFiCx 命令消息
对于大多数控制路径操作,WiFiCx 命令消息是基于以前的 WDI 模型命令。 这些命令在 WiFiCx 任务 OID、 WiFiCx 属性 OID 和 WiFiCx 状态指示中定义。 有关详细信息,请参阅 WiFiCx 消息结构 。
命令通过客户端驱动程序提供的一组回调函数和 WiFiCx 提供的 API 进行交换:
WiFiCx 通过调用其 EvtWifiDeviceSendCommand 回调函数向客户端驱动程序发送命令消息。
若要检索消息,客户端驱动程序调用 WifiRequestGetInOutBuffer 以获取输入/输出缓冲区和缓冲区长度。 驱动程序还需要调用 WifiRequestGetMessageId 来检索消息 ID。
若要完成请求,驱动程序通过调用 WifiRequestComplete异步发送命令的 M3。
如果命令是设置命令,并且原始请求没有包含足够大的缓冲区,客户端应调用WifiRequestSetBytesNeeded以设置所需的缓冲区大小,然后以 BUFFER_OVERFLOW 状态使请求失败。
如果命令是任务命令,则客户端驱动程序需要稍后通过调用 WifiDeviceReceiveIndication 发送关联的 M4 指示,并使用 WDI 标头传递指示缓冲区,该标头包含与 M1 中包含的消息 ID 相同的消息 ID。
未经请求的指示也通过 WifiDeviceReceiveIndication 被通知,但 TransactionId 成员在 WDI_MESSAGE_HEADER 中设置为 0。
Wi-Fi Direct(P2P)支持
以下部分介绍了 WiFiCx 驱动程序如何支持 Wi-Fi Direct。
Wi-Fi 直接设备功能
WIFI_WIFIDIRECT_CAPABILITIES 表示以前通过WDI_P2P_CAPABILITIES和WDI_AP_CAPABILITIES TLV 在 WDI 中设置的所有相关功能。 客户端驱动程序调用 WifiDeviceSetWiFiDirectCapabilities ,以在设置设备功能阶段向 WiFiCx 报告 Wi-Fi 直接功能。
WIFI_WIFIDIRECT_CAPABILITIES wfdCapabilities = {};
// Set values
wfdCapabilities.ConcurrentGOCount = 1;
wfdCapabilities.ConcurrentClientCount = 1;
// Report capabilities to WiFiCx
WifiDeviceSetWiFiDirectCapabilities(Device, &wfdCapabilities);
Wi-Fi“WfdDevice”的直接事件回调
对于 Wi-Fi Direct,“WfdDevice”是一个没有数据路径功能的控件对象。 因此,WiFiCx 具有名为 WIFIDIRECTDEVICE 的新 WDFObject。 在其 EvtWifiDeviceCreateWifiDirectDevice 回调函数中,客户端驱动程序:
调用 WifiDirectDeviceCreate 以创建 WIFIDIRECTDEVICE 对象。
调用 WifiDirectDeviceInitialize 以初始化对象。
调用 WifiDirectDeviceGetPortId 来确定端口 ID(在命令消息中使用)。
此示例演示如何创建和初始化 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 确定端口 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 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$
NetAdapter 数据路径更改
设置多个 Tx 队列
默认情况下,NetAdapterCx 将为用于 NetAdapter 的所有数据包创建一个 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);
这会导致翻译器按需创建多达 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 并将 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS的 IdleTimeoutType 成员设置为 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);