撰寫 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 來擷取訊息識別碼。
若要完成要求,驅動程式會呼叫 WifiRequestComplete以非同步方式傳送命令的 M3。
如果命令是 set 命令,且原始要求未包含夠大的緩衝區,用戶端應該呼叫 WifiRequestSetBytesNeeded 來設定所需的緩衝區大小,然後讓要求失敗並顯示狀態BUFFER_OVERFLOW。
如果命令是工作命令,用戶端驅動程式稍後必須呼叫 WifiDeviceReceiveIndication 來傳送相關聯的 M4 指示,並使用包含 M1 中相同訊息識別碼的 WDI 標頭傳遞指示緩衝區。
未經請求的指示也會透過 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 ,以在設定裝置功能階段將Wi-Fi直接功能回報給 WiFiCx。
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);