编写 WiFiCx 客户端驱动程序
设备和适配器初始化
除了 NetAdapterCx 需要完成 NetAdapter 设备初始化的任务外,WiFiCx 客户端驱动程序还必须在其 EvtDriverDeviceAdd 回调函数中执行以下任务:
在调用 NetDeviceInitConfig 之后调用 WifiDeviceInitConfig ,但在调用 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 (工作站) 。 此外,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。
如果命令是 set 命令,并且原始请求不包含足够大的缓冲区,则客户端应调用 WifiRequestSetBytesNeeded 来设置所需的缓冲区大小,然后使请求失败,状态BUFFER_OVERFLOW。
如果命令是任务命令,则客户端驱动程序稍后需要通过调用 WifiDeviceReceiveIndication 发送关联的 M4 指示,并使用 WDI 标头传递指示缓冲区,该标头包含与 M1 中包含的消息 ID 相同的消息 ID。
未经请求的指示也通过 WifiDeviceReceiveIndication 通知,但 WDI_MESSAGE_HEADER的 TransactionId 成员设置为 0。
Wi-Fi直接 (P2P) 支持
以下部分介绍了 WiFiCx 驱动程序如何支持 Wi-Fi Direct。
Wi-Fi Direct 设备功能
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”的 Direct 事件回调
对于 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直接 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 将为用于 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 已断开对等互连。 这会导致框架停止关联的队列。
电源策略更改
对于电源管理,客户端驱动程序应 像其他类型的 NetAdapterCx 客户端驱动程序一样使用 NETPOWERSETTINGS 对象。
若要在系统处于工作 (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);