撰寫WiFiCx用戶端驅動程式
裝置和配接器初始化
除了 NetAdapterCx NetAdapter 裝置初始化所需的工作之外,WiFiCx 用戶端驅動程式也必須在其 EvtDriverDeviceAdd 回呼函式中執行下列工作:
在呼叫 NetDeviceInitConfig 之後,但在呼叫 WdfDeviceCreate之前,呼叫 WifiDeviceInitConfig,參考框架傳入的相同 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 會指示驅動程式稍後使用 EvtWifiDeviceCreateAdapter 回呼來建立預設的 NetAdapter(在客戶端的 EvtDevicePrepareHardware 回呼成功之後)。 此外,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 以擷取訊息標識碼。
若要完成請求,驅動程式會呼叫 WifiRequestComplete,以異步方式傳送用於該命令的 M3。
如果命令是 set 命令,且原始要求未包含足夠大的緩衝區,客戶端應該呼叫 WifiRequestSetBytesNeeded 來設定所需的緩衝區大小,然後將要求失敗並顯示 BUFFER_OVERFLOW 狀態。
如果命令是工作命令,用戶端驅動程式稍後必須呼叫 WifiDeviceReceiveIndication 傳送相關聯的 M4 指示,並使用包含 M1 中相同訊息識別符的 WDI 標頭傳遞指示緩衝區。
未經請求的通知也會透過 WifiDeviceReceiveIndication接收,但會將 WDI_MESSAGE_HEADER 的 TransactionId 成員設定為 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,以判斷埠標識碼(用於命令訊息中)。
此範例示範如何建立和初始化 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 判斷埠標識碼(用於訊息命令)。
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 支援
ExemptAction 是新的 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 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);
這會導致翻譯工具視 NBL WlanTagHeader::WMMInfo 值而定,依需求建立最多 8 個 Tx 佇列。
用戶端驅動程序應該從 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);