From 75802b81c9be144d89d51055e2621583e1d3987a Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Thu, 2 Jul 2026 00:27:33 +0530 Subject: [PATCH 1/7] netvadapter: rebuild driver to avoid stale catalog Build the sample with /t:Rebuild so an incremental build cannot reuse a stale catalog, which drvcat reported as a signature mismatch at install time. Signed-off-by: Chaitanya Tata --- network/netadaptercx/netvadapter/build.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netadaptercx/netvadapter/build.cmd b/network/netadaptercx/netvadapter/build.cmd index 3f7c8e5da..e0123fe9b 100644 --- a/network/netadaptercx/netvadapter/build.cmd +++ b/network/netadaptercx/netvadapter/build.cmd @@ -21,5 +21,5 @@ set PLAT=%~2 if "%CONFIG%"=="" set CONFIG=Debug if "%PLAT%"=="" set PLAT=x64 -msbuild "%~dp0netvadapter.sln" /t:Build /p:Configuration=%CONFIG% /p:Platform=%PLAT% /p:UMDF_VERSION_MINOR=33 /p:UMDF_MINIMUM_VERSION_REQUIRED=33 /m +msbuild "%~dp0netvadapter.sln" /t:Rebuild /p:Configuration=%CONFIG% /p:Platform=%PLAT% /p:UMDF_VERSION_MINOR=33 /p:UMDF_MINIMUM_VERSION_REQUIRED=33 /m endlocal From 9dcd9a15018cce6af2bc863eb7d77ebf79935d31 Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Thu, 2 Jul 2026 00:27:33 +0530 Subject: [PATCH 2/7] netvadapter: apply property default on missing/out-of-range config NetvAdapterReadConfiguration stored each registry value into the adapter field before validating it, and the range check assigned the fallback to a local that was never written back. A missing or out-of-range value therefore left the raw (or zero) value in the field rather than the default; with a MACLastByte table default of 0 -- which AdapterCreateAddress rejects as < 1 -- a missing MACLastByte would fail adapter start outright. Validate first, then store once, and set the MACLastByte default/min to 1 to match the INF advanced-property definition. Signed-off-by: Chaitanya Tata --- .../netvadapterlibrary/code/configuration.cpp | 54 ++++++------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/network/netadaptercx/netvadapterlibrary/code/configuration.cpp b/network/netadaptercx/netvadapterlibrary/code/configuration.cpp index 7482ef849..71de9f6aa 100644 --- a/network/netadaptercx/netvadapterlibrary/code/configuration.cpp +++ b/network/netadaptercx/netvadapterlibrary/code/configuration.cpp @@ -24,7 +24,7 @@ NETVADAPTER_ADVANCED_PROPERTY NetvSupportedProperties[] = // reg value name - Offset in NetvAdapter - Field size - Default Value - Min - Max // Standard Keywords - { CONSTANT_UNICODE_STRING(L"MACLastByte"), NETV_OFFSET(MACLastByte), NETV_SIZE(MACLastByte), 0, 0, 254 }, + { CONSTANT_UNICODE_STRING(L"MACLastByte"), NETV_OFFSET(MACLastByte), NETV_SIZE(MACLastByte), 1, 1, 254 }, { CONSTANT_UNICODE_STRING(L"LinkProcIndex"), NETV_OFFSET(LinkProcIndex), NETV_SIZE(LinkProcIndex), 1000, 0, 1023 }, { CONSTANT_UNICODE_STRING(L"S0Idle"), NETV_OFFSET(S0Idle), NETV_SIZE(S0Idle), 0, 0, 1 }, { CONSTANT_UNICODE_STRING(L"EnableUsoUro"), NETV_OFFSET(EnableUsoUro), NETV_SIZE(EnableUsoUro), 0, 0, 1 }, @@ -42,57 +42,33 @@ NetvAdapterReadConfiguration( RETURN_IF_NOT_STATUS_SUCCESS( NetDeviceOpenConfiguration(Device, WDF_NO_OBJECT_ATTRIBUTES, &configuration)); - // read all the registry values + // A missing or out-of-range registry value must not fail initialization; + // fall back to the property default instead. Under NT the parameters are + // all read in as DWORDs. for (auto &property : NetvSupportedProperties) { - // Driver should NOT fail the initialization only because it can not - // read the registry - auto pointer = (PUCHAR)Adapter + property.FieldOffset; - - // Get the configuration value for a specific parameter. Under NT the - // parameters are all read in as DWORDs. ULONG value = 0; - status = NetConfigurationQueryUlong( configuration, NET_CONFIGURATION_QUERY_ULONG_NO_FLAGS, &property.RegName, &value); - // Store the value in the adapter structure. - switch (property.FieldSize) + if (! NT_SUCCESS(status) || + value < property.Min || + value > property.Max) { - case 1: - *((PUCHAR)pointer) = (UCHAR)value; - break; - - case 2: - *((PUSHORT)pointer) = (USHORT)value; - break; - - case 4: - *((PULONG)pointer) = (ULONG)value; - break; - - default: - break; + value = property.Default; + status = STATUS_SUCCESS; } - // If the parameter was present, then check its value for validity. - if (NT_SUCCESS(status)) - { - // Check that param value is not too small or too large - - if (value < property.Min || - value > property.Max) - { - value = property.Default; - } - } - else + auto pointer = (PUCHAR)Adapter + property.FieldOffset; + switch (property.FieldSize) { - value = property.Default; - status = STATUS_SUCCESS; + case 1: *((PUCHAR)pointer) = (UCHAR)value; break; + case 2: *((PUSHORT)pointer) = (USHORT)value; break; + case 4: *((PULONG)pointer) = (ULONG)value; break; + default: break; } } From 4c7d9a3ff897ccb4acc2b1e31d5845c1655f55d0 Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Thu, 2 Jul 2026 00:27:55 +0530 Subject: [PATCH 3/7] wificx: drive the connect-to-data-path flow on the sample station Three control-path gaps kept the emulated station from activating its data path on connect: - The adapter inherited MediaConnectStateConnected from NetvAdapter::Initialize, so the WiFiCx framework treated every "netsh wlan connect" as a roam and skipped the association / data-path activation flow. Start the adapter disconnected after AdapterStart. - WS2025 initialises the virtual radio as Software Off and never issues WDI_TASK_CONNECT. WifiIhvSetRadioState now always reports Software=On for this radio-less loopback adapter. - WDI task commands (not properties) need an M3+M4 completion; the default handler sent only M3. Map unhandled WDI_TASK_* commands to their M4 indication so the request completes. Signed-off-by: Chaitanya Tata --- network/wlan/wificx/drivercode/device.cpp | 17 ++++++ network/wlan/wificx/drivercode/wifihal.cpp | 61 ++++++++++++------- .../wlan/wificx/drivercode/wifitransition.cpp | 46 +++++++++++++- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/network/wlan/wificx/drivercode/device.cpp b/network/wlan/wificx/drivercode/device.cpp index 0e04d5625..2b4af6c5c 100644 --- a/network/wlan/wificx/drivercode/device.cpp +++ b/network/wlan/wificx/drivercode/device.cpp @@ -87,6 +87,23 @@ NTSTATUS EvtWifiDeviceCreateAdapter(WDFDEVICE Device, NETADAPTER_INIT* AdapterIn return ntStatus; } + // WiFi adapters start disconnected — the framework manages the media + // state and will send WDI_TASK_CONNECT (not ROAM) for the first + // connection. Without this the inherited SetLinkState(Connected) from + // NetvAdapter::Initialize causes the framework to treat every connect + // as a roam, skipping the association/data-path activation flow. + { + NET_ADAPTER_LINK_STATE linkState; + NET_ADAPTER_LINK_STATE_INIT( + &linkState, + 1'000'000'000ull, + MediaConnectStateDisconnected, + MediaDuplexStateFull, + NetAdapterPauseFunctionTypeUnsupported, + NetAdapterAutoNegotiationFlagNone); + NetAdapterSetLinkState(netAdapter, &linkState); + } + auto wifiNetvDevice = WifiGetIhvDeviceContext(Device); wifiNetvDevice->netAdapters[WifiAdapterGetPortId(netAdapter)] = netAdapter; diff --git a/network/wlan/wificx/drivercode/wifihal.cpp b/network/wlan/wificx/drivercode/wifihal.cpp index e7aaec1f1..792f307ff 100644 --- a/network/wlan/wificx/drivercode/wifihal.cpp +++ b/network/wlan/wificx/drivercode/wifihal.cpp @@ -1,6 +1,7 @@ // Copyright (C) Microsoft Corporation. All rights reserved. #include "precomp.h" +#include "device.h" #include "wifirequest.h" #include "wifiHALtestdata.h" #include "WifiHal.h" @@ -528,25 +529,23 @@ NTSTATUS WifiHAL::WifiIhvReset(const WDI_TASK_DOT11_RESET_PARAMETERS& ResetParam _Use_decl_annotations_ NTSTATUS WifiHAL::WifiIhvSetRadioState(const WDI_SET_RADIO_STATE_PARAMETERS& RadioState, const PWDI_MESSAGE_HEADER pWdiHeader, UINT) { - WFCInfo("Setting OS requested Radio State: SoftwareRadioState=%u\n", RadioState.SoftwareRadioState); - if (RadioState.SoftwareRadioState != m_CurrentRadioState) - { - // Change the radio state - m_CurrentRadioState = RadioState.SoftwareRadioState; - - // Send the radio state indication - WDI_INDICATION_RADIO_STATUS_PARAMETERS RadioStatusParams = {}; - UINT8* pOutput = nullptr; - ULONG cbOutput = 0; + UNREFERENCED_PARAMETER(RadioState); + // Virtual loopback adapter: no physical radio to disable. + // Always report Software=On so the WLAN service allows scanning and + // connecting. Without this the service initialises radio as Software Off + // and never issues WDI_TASK_CONNECT. + m_CurrentRadioState = 1; + + WDI_INDICATION_RADIO_STATUS_PARAMETERS RadioStatusParams = {}; + UINT8* pOutput = nullptr; + ULONG cbOutput = 0; - RadioStatusParams.RadioState.HardwareState = TRUE; - RadioStatusParams.RadioState.SoftwareState = m_CurrentRadioState; - if (GenerateWdiIndicationRadioStatus(&RadioStatusParams, 0, m_TlvContext, &cbOutput, &pOutput) == NDIS_STATUS_SUCCESS) - { - WFCInfo("Indicate OS with Radio State: SoftwareRadioState=%u\n", m_CurrentRadioState); - WifiIhvSendUnsolicitedIndicationToOs(m_Device, pWdiHeader, WDI_INDICATION_RADIO_STATUS, pOutput, cbOutput); - FreeGenerated(pOutput); - } + RadioStatusParams.RadioState.HardwareState = TRUE; + RadioStatusParams.RadioState.SoftwareState = TRUE; + if (GenerateWdiIndicationRadioStatus(&RadioStatusParams, 0, m_TlvContext, &cbOutput, &pOutput) == NDIS_STATUS_SUCCESS) + { + WifiIhvSendUnsolicitedIndicationToOs(m_Device, pWdiHeader, WDI_INDICATION_RADIO_STATUS, pOutput, cbOutput); + FreeGenerated(pOutput); } return STATUS_SUCCESS; @@ -612,9 +611,12 @@ NTSTATUS WifiHAL::WifiIhvConnect(const WDI_TASK_CONNECT_PARAMETERS& ConnectParam reinterpret_cast(&m_ConnectedPeer)); } #endif //NETV_SUPPORT_TX_DEMUXING - WX_RETURN_NTSTATUS_IF_NOT_NT_SUCCESS_MSG(WifiIhvPerformAssociation( - &ConnectParameters.PreferredBSSEntryList, &ConnectParameters.ConnectParameters.AuthenticationAlgorithms, pWdiHeader), - "Failed to perform association"); + NTSTATUS assocStatus = WifiIhvPerformAssociation( + &ConnectParameters.PreferredBSSEntryList, &ConnectParameters.ConnectParameters.AuthenticationAlgorithms, pWdiHeader); + if (!NT_SUCCESS(assocStatus)) + { + return assocStatus; + } // // WPA3-SAE requires the SAE Exchange, so do not complete the Connection request until the SAE exchange is complete @@ -623,10 +625,25 @@ NTSTATUS WifiHAL::WifiIhvConnect(const WDI_TASK_CONNECT_PARAMETERS& ConnectParam { m_LastConnectTransactionId = pWdiHeader->TransactionId; } - else + else { m_LastConnectTransactionId = 0; } + + NETADAPTER netAdapter = WifiGetIhvDeviceContext(m_Device)->netAdapters[pWdiHeader->PortId]; + if (netAdapter != WDF_NO_HANDLE) + { + NET_ADAPTER_LINK_STATE linkState; + NET_ADAPTER_LINK_STATE_INIT( + &linkState, + 1'000'000'000ull, + MediaConnectStateConnected, + MediaDuplexStateFull, + NetAdapterPauseFunctionTypeUnsupported, + NetAdapterAutoNegotiationFlagNone); + NetAdapterSetLinkState(netAdapter, &linkState); + } + return STATUS_SUCCESS; } diff --git a/network/wlan/wificx/drivercode/wifitransition.cpp b/network/wlan/wificx/drivercode/wifitransition.cpp index 2e1b7ef24..64470d3c6 100644 --- a/network/wlan/wificx/drivercode/wifitransition.cpp +++ b/network/wlan/wificx/drivercode/wifitransition.cpp @@ -437,6 +437,37 @@ struct PropertyTraits > {}; +// Map WDI_TASK_* command IDs to their WDI_INDICATION_*_COMPLETE IDs. +// Returns 0 when messageId is not a task (i.e. a property SET/GET). +static UINT16 GetTaskCompletionIndication(UINT16 messageId) +{ + switch (messageId) + { + case WDI_TASK_OPEN: return WDI_INDICATION_OPEN_COMPLETE; + case WDI_TASK_CLOSE: return WDI_INDICATION_CLOSE_COMPLETE; + case WDI_TASK_SCAN: return WDI_INDICATION_SCAN_COMPLETE; + case WDI_TASK_P2P_DISCOVER: return WDI_INDICATION_P2P_DISCOVERY_COMPLETE; + case WDI_TASK_CONNECT: return WDI_INDICATION_CONNECT_COMPLETE; + case WDI_TASK_DOT11_RESET: return WDI_INDICATION_DOT11_RESET_COMPLETE; + case WDI_TASK_DISCONNECT: return WDI_INDICATION_DISCONNECT_COMPLETE; + case WDI_TASK_P2P_SEND_REQUEST_ACTION_FRAME: return WDI_INDICATION_P2P_SEND_REQUEST_ACTION_FRAME_COMPLETE; + case WDI_TASK_P2P_SEND_RESPONSE_ACTION_FRAME: return WDI_INDICATION_P2P_SEND_RESPONSE_ACTION_FRAME_COMPLETE; + case WDI_TASK_SET_RADIO_STATE: return WDI_INDICATION_SET_RADIO_STATE_COMPLETE; + case WDI_TASK_CREATE_PORT: return WDI_INDICATION_CREATE_PORT_COMPLETE; + case WDI_TASK_DELETE_PORT: return WDI_INDICATION_DELETE_PORT_COMPLETE; + case WDI_TASK_START_AP: return WDI_INDICATION_START_AP_COMPLETE; + case WDI_TASK_STOP_AP: return WDI_INDICATION_STOP_AP_COMPLETE; + case WDI_TASK_SEND_AP_ASSOCIATION_RESPONSE: return WDI_INDICATION_SEND_AP_ASSOCIATION_RESPONSE_COMPLETE; + case WDI_TASK_CHANGE_OPERATION_MODE: return WDI_INDICATION_CHANGE_OPERATION_MODE_COMPLETE; + case WDI_TASK_ROAM: return WDI_INDICATION_ROAM_COMPLETE; + case WDI_TASK_SEND_REQUEST_ACTION_FRAME: return WDI_INDICATION_SEND_REQUEST_ACTION_FRAME_COMPLETE; + case WDI_TASK_SEND_RESPONSE_ACTION_FRAME: return WDI_INDICATION_SEND_RESPONSE_ACTION_FRAME_COMPLETE; + case WDI_TASK_IHV: return WDI_INDICATION_IHV_TASK_COMPLETE; + case WDI_TASK_REQUEST_FTM: return WDI_INDICATION_REQUEST_FTM_COMPLETE; + default: return 0; + } +} + // Runtime dispatcher switches on MessageId and invokes the matching compile-time runner. NTSTATUS RunTransitionByMessage(TransitionContext& ctx, UINT16 messageId) { @@ -459,8 +490,19 @@ NTSTATUS RunTransitionByMessage(TransitionContext& ctx, UINT16 messageId) case WDI_DEVICE_SERVICE_COMMAND: return RunPropertyM3(ctx); default: + { UINT bytesWritten = sizeof(WDI_MESSAGE_HEADER); - WifiRequestComplete(ctx.WifiRequest, STATUS_NOT_SUPPORTED, bytesWritten); - return STATUS_NOT_SUPPORTED; + UINT16 indicationId = GetTaskCompletionIndication(messageId); + if (indicationId != 0) + { + WifiIhvNotifyM3Completion(ctx.WifiRequest, STATUS_SUCCESS, bytesWritten); + WifiIhvSendM4IndicationToOs(ctx.Device, indicationId, ctx.Header, STATUS_SUCCESS); + } + else + { + WifiRequestComplete(ctx.WifiRequest, STATUS_SUCCESS, bytesWritten); + } + return STATUS_SUCCESS; + } } } \ No newline at end of file From d7419d09a4bb6c7b47e90b552b1fabab080fe0c8 Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Thu, 2 Jul 2026 00:27:55 +0530 Subject: [PATCH 4/7] wificx: relay forwarded 802.11 frames as an AP would (rx fix) Two sample stations associate in software to the same hard-coded AP BSSID and the ENL link emulates the wireless medium between them. nwifi builds a station-to-peer frame as a "to-DS" frame whose Address1/RA is the AP BSSID -- it expects the AP to relay it on as a "from-DS" frame addressed to the destination station. There is no AP in this setup, so ENL copied the to-DS frame verbatim into the peer's receive ring. The WiFiCx miniport indicated every frame up, but nwifi on the receiving station dropped them all: the receive frame's RA was the BSSID rather than the station's own address and the DS direction was wrong, so ping saw 100% loss even though the ENL forward and the netadapter receive ring worked correctly. Emulate the AP relay in the ENL copy path: rewrite a to-DS data frame in place into the equivalent from-DS frame, rotating the three address fields (RA <- final DA, TA <- BSSID, SA <- original source) and flipping ToDS to FromDS. The address fields are at the same offsets for data and QoS-data frames, and the guard makes this a no-op for 802.3 (netvmini) traffic. Signed-off-by: Chaitanya Tata --- .../netvadapterlibrary/code/enl.cpp | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/network/netadaptercx/netvadapterlibrary/code/enl.cpp b/network/netadaptercx/netvadapterlibrary/code/enl.cpp index 709c5a1ea..6990ac951 100644 --- a/network/netadaptercx/netvadapterlibrary/code/enl.cpp +++ b/network/netadaptercx/netvadapterlibrary/code/enl.cpp @@ -15,6 +15,7 @@ ENL_MLINK NetvEnlMLink[MAX_ADAPTER_COUNT / 2]; + /*++ The iteration routine performs one full pass over all input queues and any internal queues (that might be holding previous items waiting to be @@ -52,6 +53,59 @@ CopyTxPacketDataToBuffer( return bytesCopied; } +// +// Emulate the AP relay for Native 802.11 frames. +// +// Both sample stations associate (in software) to the same hard-coded AP +// BSSID, and the ENL link is the wireless medium. nwifi builds a +// station-to-peer frame as a "to-DS" frame whose Address1/RA is the AP BSSID, +// expecting the (nonexistent) AP to relay it. Copied verbatim into the peer's +// receive ring, that RA is not the peer's address, so nwifi on the peer drops +// it before it reaches TCP/IP. Rewrite the header in place into the "from-DS" +// frame the peer accepts, exactly as a real AP would when forwarding: +// Addr1 (RA) <- old Addr3 (final destination = peer MAC) +// Addr2 (TA) <- old Addr1 (BSSID) +// Addr3 (SA) <- old Addr2 (original sender) +// FrameControl: clear ToDS, set FromDS +// The address offsets are identical for data and QoS-data frames, and the +// guard below makes this a no-op for non-802.11 (802.3 netvmini) traffic. +static +VOID +EnlpRelayWiFiFrame( + _Inout_updates_bytes_(Length) PUCHAR Frame, + _In_ SIZE_T Length) +{ + // FrameControl[2] Duration[2] Addr1[6] Addr2[6] Addr3[6] SeqCtrl[2] ... + SIZE_T const k80211HeaderLength = 24; + if (Length < k80211HeaderLength) + { + return; + } + + // Relay only 802.11 data-type (Type == 10b) frames headed to the DS. + UCHAR const type = Frame[0] & 0x0C; + UCHAR const dsBits = Frame[1] & 0x03; + if (type != 0x08 || dsBits != 0x01) + { + return; + } + + PUCHAR const addr1 = Frame + 4; + PUCHAR const addr2 = Frame + 10; + PUCHAR const addr3 = Frame + 16; + + UCHAR ra[6], ta[6], sa[6]; + RtlCopyMemory(ra, addr3, 6); + RtlCopyMemory(ta, addr1, 6); + RtlCopyMemory(sa, addr2, 6); + + RtlCopyMemory(addr1, ra, 6); + RtlCopyMemory(addr2, ta, 6); + RtlCopyMemory(addr3, sa, 6); + + Frame[1] = (Frame[1] & ~0x01) | 0x02; // ToDS -> FromDS +} + VOID EnlpAffinitizeThread( _In_ ULONG ProcIndex, @@ -364,6 +418,11 @@ EnlpIterationRoutine( &txq->TxQueue->VirtualAddressExtension, static_cast(fragment->Capacity)); + // Relay the frame as an AP would (see function). + EnlpRelayWiFiFrame( + fragmentBuffer, + static_cast(fragment->ValidLength)); + auto rxPacket = NetPacketIteratorGetPacket(&rxPi); rxPacket->FragmentIndex = NetFragmentIteratorGetIndex(&rxFi); rxPacket->FragmentCount = 1; From f5788b393b32aab7795986cd58bc669d7c909a1c Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Thu, 2 Jul 2026 00:27:55 +0530 Subject: [PATCH 5/7] wificx: add EWDK deploy/test scripts with deterministic pairing deploy.ps1 builds, signs, and installs two WiFiCx instances and pairs them on the Emulated Network Link. Pairing writes MACLastByte to each adapter's software (Class) key -- the location NetDeviceOpenConfiguration reads -- after a clean-slate removal of all existing WiFiCx devnodes, so the two instances never collide on the default MAC and no manual step or reboot is required. Uninstall removes every devnode before deleting the package. run.ps1 connects both adapters to the open SSID, assigns static IPs, seeds static neighbor entries (both stations share one host/subnet, so each stack would otherwise treat the peer IP as a local duplicate and never answer its ARP), and pings in both directions. README_EWDK.md documents the full EWDK build, deploy, and test flow. Signed-off-by: Chaitanya Tata --- network/wlan/wificx/README_EWDK.md | 169 ++++++++++ network/wlan/wificx/deploy.ps1 | 493 +++++++++++++++++++++++++++++ network/wlan/wificx/run.ps1 | 259 +++++++++++++++ 3 files changed, 921 insertions(+) create mode 100644 network/wlan/wificx/README_EWDK.md create mode 100644 network/wlan/wificx/deploy.ps1 create mode 100644 network/wlan/wificx/run.ps1 diff --git a/network/wlan/wificx/README_EWDK.md b/network/wlan/wificx/README_EWDK.md new file mode 100644 index 000000000..d90726be5 --- /dev/null +++ b/network/wlan/wificx/README_EWDK.md @@ -0,0 +1,169 @@ +# Building and testing WiFiCx sample on Azure VM (EWDK) + +Step-by-step guide to build, sign, and install the WiFiCx sample driver +using the Enterprise WDK (EWDK) on an Azure VM. No Visual Studio required. + +## Azure VM setup + +Create a VM with: + +| Setting | Value | +|-----------|--------------------------------------------| +| Size | Standard D2s v3 (2 vcpus, 8 GiB memory) | +| Region | Any (tested with South India) | +| Image | Windows Server 2025 Datacenter | + +RDP into the VM and open an elevated Command Prompt for the steps below. + +## Prerequisites + +### 1. Install EWDK + +Download the EWDK ISO from + +and mount it. Launch the build environment: + +```cmd +E:\LaunchBuildEnv.cmd +``` + +### 2. Install NuGet and PowerShell (one-time) + +```cmd +winget install Microsoft.NuGet +winget install Microsoft.PowerShell +``` + +Close and reopen the EWDK terminal so the new tools are on PATH. + +### 3. Clone the repository + +```cmd +git clone --recurse-submodules https://github.com/microsoft/Windows-driver-samples.git +cd Windows-driver-samples +``` + +If you already cloned without `--recurse-submodules`: + +```cmd +git submodule update --init +``` + +### 4. Restore NuGet packages + +```cmd +nuget restore -PackagesDirectory .\packages +``` + +### 5. Enable test signing (requires reboot) + +```cmd +bcdedit /set testsigning on +shutdown /r /t 0 +``` + +After reboot, re-mount the EWDK ISO and launch the build environment again. + +## Build, sign, and install + +From the EWDK terminal, launch PowerShell and run the deploy script: + +```cmd +pwsh +``` + +```powershell +Set-ExecutionPolicy -Scope Process Bypass +.\network\wlan\wificx\deploy.ps1 +``` + +The script will: +1. Build both WiFiCx and NetAdapterCx solutions +2. Create or find a test certificate +3. Run StampInf, Inf2Cat, and signtool +4. Remove any stale WiFiCx devnodes, install two instances, and pair them + deterministically — MACLastByte 1 and 2 — with no manual step or reboot + +### Common options + +```powershell +# Skip rebuild (use existing build output) +.\network\wlan\wificx\deploy.ps1 -SkipBuild + +# Release build +.\network\wlan\wificx\deploy.ps1 -Config Release + +# Install only (already built and signed) +.\network\wlan\wificx\deploy.ps1 -InstallOnly + +# Uninstall all sample drivers +.\network\wlan\wificx\deploy.ps1 -Uninstall +``` + +## Test: connect and ping + +`run.ps1` automates the full data-path test: it discovers the two paired +adapters, creates an open WLAN profile, connects both, assigns static IPs, +seeds static neighbor entries, and pings in both directions. + +```powershell +.\network\wlan\wificx\run.ps1 +``` + +A passing run reports `PASS: Bidirectional ping succeeded`. + +### Manual steps (alternative) + +```cmd +netsh wlan show networks +netsh wlan connect name="WFC_OPEN" interface="Wi-Fi" +netsh wlan connect name="WFC_OPEN" interface="Wi-Fi 2" +``` + +Both adapters share one host and subnet, so seed static neighbor entries +before pinging — otherwise each stack treats the peer IP as a local +duplicate and never answers its ARP: + +```cmd +netsh interface ip set address "Wi-Fi" static 192.168.100.1 255.255.255.0 +netsh interface ip set address "Wi-Fi 2" static 192.168.100.2 255.255.255.0 +netsh interface ipv4 add neighbors "Wi-Fi" 192.168.100.2 22-22-22-22-00-02 +netsh interface ipv4 add neighbors "Wi-Fi 2" 192.168.100.1 22-22-22-22-00-01 +ping 192.168.100.2 -S 192.168.100.1 +``` + +## Architecture + +This is a pure software simulation — no Wi-Fi hardware required. + +``` +WiFiCx Instance 1 (MACLastByte=1) <--ENL--> WiFiCx Instance 2 (MACLastByte=2) + | | + +-- WifiCx control path (scan/connect/auth) +-- WifiCx control path + | | + +-- NetAdapter Tx Queue ---- ENL ---- Rx Queue + + +-- NetAdapter Rx Queue ---- ENL ---- Tx Queue + +``` + +- **Control path**: Hardcoded scan results, simulated WPA2/WPA3-SAE + authentication, fake link quality (RSSI -50, 30 Mbps). +- **Data path**: Two virtual adapters connected by the Emulated Network + Link (ENL), a software packet forwarding engine in + `netadaptercx/netvadapterlibrary/`. +- **AP relay**: Both stations associate (in software) to the same fake AP + BSSID. With no real AP to forward between them, the ENL rewrites each + forwarded 802.11 frame from "to-DS" to "from-DS" — as an AP would — so the + receiving station accepts it. +- **No hardware**: Both drivers are root-enumerated virtual devices. + +## Troubleshooting + +| Problem | Fix | +|---------|-----| +| `wil/resource.h` not found | `git submodule update --init` | +| NuGet packages missing | `nuget restore -PackagesDirectory .\packages` | +| Driver install fails | Verify `bcdedit /enum {current}` shows `testsigning Yes` | +| Inf2Cat fails on netvadapter | The deploy script falls back to `New-FileCatalog` automatically | +| `pnputil` not found | It is built into Windows — should always be on PATH | +| Only one adapter, no `…00-02` | Re-run `deploy.ps1`; it clears stale devnodes and re-pairs | +| Ping fails with "Destination host unreachable" | Seed static neighbor entries (see manual steps); `run.ps1` does this automatically | diff --git a/network/wlan/wificx/deploy.ps1 b/network/wlan/wificx/deploy.ps1 new file mode 100644 index 000000000..126b5361d --- /dev/null +++ b/network/wlan/wificx/deploy.ps1 @@ -0,0 +1,493 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + Build, sign, and install the WiFiCx + NetAdapterCx virtual adapter sample drivers. +.DESCRIPTION + Run from an elevated EWDK PowerShell session. Can be invoked from any directory. + Targets Windows Server 2025 / Windows 11. + + Prerequisites (run once before this script): + git submodule update --init + nuget restore -PackagesDirectory .\packages + bcdedit /set testsigning on # then reboot +.PARAMETER Config + Build configuration: Debug or Release (default: Debug) +.PARAMETER Platform + Build platform: x64 or ARM64 (default: x64) +.PARAMETER CertThumbprint + SHA1 thumbprint of the code-signing certificate. + If omitted, the script finds or creates a self-signed WDKTestCert. +.PARAMETER SkipBuild + Skip building — use existing build output. +.PARAMETER SkipSign + Skip catalog creation and signing. +.PARAMETER InstallOnly + Jump straight to driver installation. +.PARAMETER Uninstall + Remove all sample drivers and exit. +#> +param( + [ValidateSet("Debug","Release")] + [string]$Config = "Debug", + + [ValidateSet("x64","ARM64")] + [string]$Platform = "x64", + + [string]$CertThumbprint = "", + + [switch]$SkipBuild, + [switch]$SkipSign, + [switch]$InstallOnly, + [switch]$Uninstall +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { $PWD.Path } +# Script lives in network/wlan/wificx — repo root is three levels up +$RepoRoot = Resolve-Path "$ScriptDir\..\..\..\" + +$WificxDir = "$RepoRoot\network\wlan\wificx" +$NetvadDir = "$RepoRoot\network\netadaptercx\netvadapter" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan } +function Write-Ok($msg) { Write-Host " OK: $msg" -ForegroundColor Green } +function Write-Warn($msg) { Write-Host " WARNING: $msg" -ForegroundColor Yellow } +function Write-Fail($msg) { Write-Host " FAIL: $msg" -ForegroundColor Red; exit 1 } + +function Invoke-Checked($description, [scriptblock]$cmd) { + Write-Step $description + & $cmd + if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { + Write-Fail "$description (exit code $LASTEXITCODE)" + } + Write-Ok $description +} + +# --------------------------------------------------------------------------- +# WiFiCx devnode helpers (shared by install + uninstall) +# --------------------------------------------------------------------------- +$WificxHwId = 'Root\wificxsampleclientkm' + +# Every ROOT\NET devnode for our hardware ID — both the NIC mains and the +# WiFiDirect companions the framework spawns report this same hardware ID, +# present or phantom. +function Get-WificxDevnodes { + Get-PnpDevice -Class Net -ErrorAction SilentlyContinue | Where-Object { + $_.InstanceId -like 'ROOT\NET\*' -and + ((Get-PnpDeviceProperty -InstanceId $_.InstanceId ` + -KeyName 'DEVPKEY_Device_HardwareIds' -EA SilentlyContinue).Data -contains $WificxHwId) + } +} + +# The per-adapter software (Class) key, e.g. +# HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e972-...}\0017 +# This is where NetAdapterCx (NetDeviceOpenConfiguration -> MACLastByte) reads +# its config, NOT the Enum\\Device Parameters hardware key. +function Get-WificxSoftwareKey($InstanceId) { + $drv = (Get-PnpDeviceProperty -InstanceId $InstanceId ` + -KeyName 'DEVPKEY_Device_Driver' -EA SilentlyContinue).Data + if ($drv) { "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$drv" } +} + +function Get-WificxMacLastByte($InstanceId) { + $k = Get-WificxSoftwareKey $InstanceId + if ($k) { (Get-ItemProperty $k -Name MACLastByte -EA SilentlyContinue).MACLastByte } +} + +function Set-WificxMacLastByte($InstanceId, $Value) { + $k = Get-WificxSoftwareKey $InstanceId + if (-not $k) { return $false } + Set-ItemProperty -Path $k -Name MACLastByte -Value "$Value" -Type String -EA SilentlyContinue + return $true +} + +# Disable/enable forces the framework to re-read MACLastByte and re-run adapter +# start; works even when the device is currently in the FAILED_START state. +function Restart-WificxDevice($InstanceId) { + try { Disable-PnpDevice -InstanceId $InstanceId -Confirm:$false -EA Stop } catch {} + Start-Sleep -Milliseconds 500 + try { Enable-PnpDevice -InstanceId $InstanceId -Confirm:$false -EA Stop } catch {} +} + +function Remove-AllWificxDevnodes { + $nodes = @(Get-WificxDevnodes) + foreach ($d in $nodes) { + pnputil /remove-device "$($d.InstanceId)" /force 2>&1 | Out-Null + } + return $nodes.Count +} + +function Get-WificxMacPair { + @(Get-NetAdapter -IncludeHidden -ErrorAction SilentlyContinue | + Where-Object { $_.MacAddress -in '22-22-22-22-00-01', '22-22-22-22-00-02' } | + Select-Object -ExpandProperty MacAddress -Unique) +} + +# --------------------------------------------------------------------------- +# Uninstall +# --------------------------------------------------------------------------- +if ($Uninstall) { + Write-Step "Removing WiFiCx devnodes (present and phantom)" + $removed = Remove-AllWificxDevnodes + Write-Ok "Removed $removed WiFiCx devnode(s)" + + Write-Step "Scanning for installed sample drivers" + $raw = pnputil /enum-drivers /class net | Out-String + Write-Host $raw + + $oems = [regex]::Matches($raw, + '(?ms)Published Name\s*:\s*(oem\d+\.inf).*?Original Name\s*:\s*(wificx|netvadapter)\w*\.inf') | + ForEach-Object { + [PSCustomObject]@{ Oem = $_.Groups[1].Value; Orig = $_.Groups[2].Value } + } + + if ($oems.Count -eq 0) { Write-Warn "No sample drivers found."; exit 0 } + + foreach ($d in $oems) { + Invoke-Checked "Removing $($d.Oem) ($($d.Orig))" { + pnputil /delete-driver $d.Oem /uninstall /force + } + } + exit 0 +} + +# --------------------------------------------------------------------------- +# Pre-flight +# --------------------------------------------------------------------------- +Write-Step "Pre-flight checks" + +if (-not (Test-Path "$RepoRoot\wil\include\wil\resource.h")) { + Write-Fail "wil submodule missing. Run: git submodule update --init" +} +Write-Ok "wil submodule present" + +if (-not (Test-Path "$RepoRoot\packages\Microsoft.Windows.WDK*")) { + Write-Fail "NuGet packages missing. Run: nuget restore -PackagesDirectory .\packages" +} +Write-Ok "NuGet packages present" + +$ts = bcdedit /enum '{current}' | Select-String "testsigning\s+Yes" +if (-not $ts) { + Write-Warn "Test signing is OFF. Run 'bcdedit /set testsigning on' and reboot." +} else { + Write-Ok "Test signing enabled" +} + +$devcon = Get-Command devcon.exe -ErrorAction SilentlyContinue | + Select-Object -ExpandProperty Source +if (-not $devcon) { + $wdkToolsDirs = @( + "F:\Program Files\Windows Kits\10\Tools", + "E:\Program Files\Windows Kits\10\Tools", + "$env:SystemDrive\Program Files (x86)\Windows Kits\10\Tools" + ) + foreach ($dir in $wdkToolsDirs) { + $candidate = Get-ChildItem "$dir\*\x64\devcon.exe" -ErrorAction SilentlyContinue | + Select-Object -First 1 -ExpandProperty FullName + if ($candidate) { $devcon = $candidate; break } + } +} +if ($devcon) { + Write-Ok "devcon: $devcon" +} else { + Write-Fail "devcon.exe not found. Run from an EWDK terminal or add WDK tools to PATH." +} + +# --------------------------------------------------------------------------- +# Build +# --------------------------------------------------------------------------- +if (-not $SkipBuild -and -not $InstallOnly) { + Invoke-Checked "Building WiFiCx sample ($Config|$Platform)" { + msbuild "$WificxDir\wificxsampleclient.sln" ` + /t:Rebuild /p:Configuration=$Config /p:Platform=$Platform /m + } + + Invoke-Checked "Building NetAdapterCx sample ($Config|$Platform)" { + & "$NetvadDir\build.cmd" $Config $Platform + } +} + +# --------------------------------------------------------------------------- +# Locate build output (solution-level: \\\\) +# --------------------------------------------------------------------------- +$drivers = @( + @{ + Name = "WiFiCx KM" + OutDir = "$WificxDir\$Platform\$Config\wificxsampleclientkm" + Binary = "wificxsampleclientkm.sys" + Inf = "wificxsampleclientkm.inf" + Cat = "wificxsampleclientkm.cat" + HwId = "Root\wificxsampleclientkm" + }, + @{ + Name = "WiFiCx UM" + OutDir = "$WificxDir\$Platform\$Config\wificxsampleclientum" + Binary = "wificxsampleclientum.dll" + Inf = "wificxsampleclientum.inf" + Cat = "wificxsampleclientum.cat" + HwId = $null + }, + @{ + Name = "NetVAd KM" + OutDir = "$NetvadDir\$Platform\$Config\netvadapterkm" + Binary = "netvadapter.sys" + Inf = "netvadapter.inf" + Cat = "netvadapter.cat" + HwId = "root\netvadapter" + }, + @{ + Name = "NetVAd UM" + OutDir = "$NetvadDir\$Platform\$Config\netvadapterum" + Binary = "netvadapterum.dll" + Inf = "netvadapterum.inf" + Cat = "netvadapterum.cat" + HwId = $null + } +) + +Write-Step "Checking build output" +foreach ($d in $drivers) { + $ok = (Test-Path "$($d.OutDir)\$($d.Binary)") -and (Test-Path "$($d.OutDir)\$($d.Inf)") + if ($ok) { Write-Ok "$($d.Name): $($d.OutDir)" } + else { Write-Warn "$($d.Name): build output missing in $($d.OutDir)" } +} + +# --------------------------------------------------------------------------- +# Certificate +# --------------------------------------------------------------------------- +if (-not $SkipSign -and -not $InstallOnly) { + if ($CertThumbprint -eq "") { + Write-Step "Locating code-signing certificate" + $cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | + Where-Object { $_.Subject -eq "CN=WDKTestCert" } | + Select-Object -First 1 + + if ($cert) { + $CertThumbprint = $cert.Thumbprint + Write-Ok "Found WDKTestCert: $CertThumbprint" + } + else { + Write-Step "Creating self-signed test certificate" + $cert = New-SelfSignedCertificate -Type CodeSigningCert ` + -Subject "CN=WDKTestCert" -CertStoreLocation Cert:\CurrentUser\My + $CertThumbprint = $cert.Thumbprint + $cerFile = "$RepoRoot\WDKTestCert.cer" + Export-Certificate -Cert $cert -FilePath $cerFile | Out-Null + certutil -addstore Root $cerFile | Out-Null + certutil -f -addstore TrustedPublisher $cerFile | Out-Null + Write-Ok "Created and trusted: $CertThumbprint" + } + } + + # ------------------------------------------------------------------- + # StampInf — expand $ARCH$ and set DriverVer where the build missed it + # ------------------------------------------------------------------- + $archMap = @{ "x64" = "amd64"; "ARM64" = "arm64" } + $stampArch = $archMap[$Platform] + $ntArch = if ($Platform -eq "x64") { "NTAMD64" } else { "NTARM64" } + + foreach ($d in $drivers) { + $infPath = "$($d.OutDir)\$($d.Inf)" + if (-not (Test-Path $infPath)) { continue } + + $needsFix = Select-String -Path $infPath -Encoding unicode ` + -Pattern '\$ARCH\$' -Quiet + + if ($needsFix) { + Write-Step "StampInf $($d.Name)" + stampinf -f "$infPath" -a $stampArch -d * -v "1.0.0.0" -k "1.33" 2>&1 | Out-Null + + if (Select-String -Path $infPath -Encoding unicode -Pattern '\$ARCH\$' -Quiet) { + $raw = [System.IO.File]::ReadAllText($infPath, [System.Text.Encoding]::Unicode) + $raw = $raw.Replace('NT$ARCH$', $ntArch).Replace('$ARCH$', $ntArch) + [System.IO.File]::WriteAllText($infPath, $raw, [System.Text.Encoding]::Unicode) + } + + $raw = [System.IO.File]::ReadAllText($infPath, [System.Text.Encoding]::Unicode) + $today = (Get-Date).ToString("MM/dd/yyyy") + if ($raw -match 'DriverVer\s*=\s*;') { + $raw = $raw -replace 'DriverVer\s*=\s*;[^\r\n]*', "DriverVer = $today,1.0.0.0" + [System.IO.File]::WriteAllText($infPath, $raw, [System.Text.Encoding]::Unicode) + } + Write-Ok "StampInf $($d.Name)" + } + } + + # ------------------------------------------------------------------- + # Inf2Cat + signtool + # ------------------------------------------------------------------- + foreach ($d in $drivers) { + $infPath = "$($d.OutDir)\$($d.Inf)" + $catPath = "$($d.OutDir)\$($d.Cat)" + if (-not (Test-Path $infPath)) { + Write-Warn "Skipping $($d.Name) - no INF" + continue + } + + Write-Step "Catalog for $($d.Name)" + Inf2Cat /driver:"$($d.OutDir)" /os:10_X64,Server10_X64 2>&1 | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Warn "Inf2Cat failed, falling back to New-FileCatalog" + New-FileCatalog -Path "$($d.OutDir)" -CatalogFilePath $catPath ` + -CatalogVersion 2 | Out-Null + } + if (-not (Test-Path $catPath)) { Write-Fail "No catalog for $($d.Name)" } + Write-Ok "Catalog for $($d.Name)" + + Invoke-Checked "Signing $($d.Name)" { + signtool sign /fd SHA256 /sha1 $CertThumbprint ` + /tr http://timestamp.digicert.com /td SHA256 $catPath + } + } +} + +# --------------------------------------------------------------------------- +# Clean slate: remove every existing WiFiCx devnode (mains, companions, and +# phantom leftovers) so freshly installed instances cannot collide with stale +# nodes still defaulting to MACLastByte=1. +# --------------------------------------------------------------------------- +Write-Step "Clean slate: removing all existing WiFiCx devnodes" +$removed = Remove-AllWificxDevnodes +if ($removed) { + Write-Ok "Removed $removed pre-existing WiFiCx devnode(s)" + Start-Sleep -Seconds 2 +} else { + Write-Ok "No pre-existing WiFiCx devnodes" +} + +# --------------------------------------------------------------------------- +# Install +# --------------------------------------------------------------------------- +Write-Step "Installing drivers" + +$wificxKmOut = ($drivers | Where-Object { $_.Name -eq "WiFiCx KM" }).OutDir + +$needsReboot = $false + +Write-Step "Install WiFiCx KMDF instance 1 (MACLastByte=1)" +& $devcon install "$wificxKmOut\wificxsampleclientkm.inf" Root\wificxsampleclientkm +if ($LASTEXITCODE -eq 1) { $needsReboot = $true; Write-Ok "Installed (reboot needed)" } +elseif ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { Write-Fail "Install failed (exit code $LASTEXITCODE)" } +else { Write-Ok "Install WiFiCx KMDF instance 1" } + +Write-Step "Install WiFiCx KMDF instance 2 (MACLastByte=2)" +& $devcon install "$wificxKmOut\wificxsampleclientkm.inf" Root\wificxsampleclientkm +if ($LASTEXITCODE -eq 1) { $needsReboot = $true; Write-Ok "Installed (reboot needed)" } +elseif ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { Write-Fail "Install failed (exit code $LASTEXITCODE)" } +else { Write-Ok "Install WiFiCx KMDF instance 2" } + +Start-Sleep -Seconds 3 + +# --------------------------------------------------------------------------- +# Pair the two adapters: one must end up at MACLastByte=1 (MAC ...00-01) and +# one at MACLastByte=2 (MAC ...00-02). On a clean slate both mains install +# with the INF default (1) and collide on ...00-01, so one starts and the +# other lands in FAILED_START with no MAC. We promote a second main to +# ...00-02 by writing MACLastByte to its SOFTWARE (Class) key -- the location +# NetAdapterCx (NetDeviceOpenConfiguration) actually reads -- then restart it. +# The previous script wrote Enum\\Device Parameters, which the driver never +# reads, so pairing silently never took effect. +# --------------------------------------------------------------------------- +$have = Get-WificxMacPair +$haveMac1 = $have -contains '22-22-22-22-00-01' +$haveMac2 = $have -contains '22-22-22-22-00-02' + +if ($haveMac1 -and $haveMac2) { + Write-Ok "MAC pair already present (00-01 and 00-02)" +} +else { + $mac1Owner = Get-NetAdapter -IncludeHidden -ErrorAction SilentlyContinue | + Where-Object MacAddress -eq '22-22-22-22-00-01' | + Select-Object -First 1 -ExpandProperty PnpDeviceID + + # Candidate second mains: our devnodes (excluding the 00-01 owner) whose + # software key exposes the MACLastByte keyword -- i.e. the NIC instances. + # A WiFiDirect companion never yields a 00-02 NIC, so if a candidate turns + # out to be one, we revert it and try the next. + $candidates = @(Get-WificxDevnodes | Where-Object { + $_.InstanceId -ne $mac1Owner -and ($null -ne (Get-WificxMacLastByte $_.InstanceId)) + }) + + Write-Step "Promoting a second adapter to MACLastByte=2 (software key)" + foreach ($cand in $candidates) { + $id = $cand.InstanceId + if (-not (Set-WificxMacLastByte $id 2)) { continue } + Write-Host " trying $id ..." + Restart-WificxDevice $id + + $deadline = (Get-Date).AddSeconds(15) + do { + Start-Sleep -Seconds 2 + $haveMac2 = (Get-WificxMacPair) -contains '22-22-22-22-00-02' + } while (-not $haveMac2 -and (Get-Date) -lt $deadline) + + if ($haveMac2) { Write-Ok "Second adapter is now 00-02 ($id)"; break } + + # Not a usable main -- revert so nodes don't fight over 00-02. + Set-WificxMacLastByte $id 1 | Out-Null + Restart-WificxDevice $id + } +} + +# --------------------------------------------------------------------------- +# Verify the real success signal: both 00-01 and 00-02 present. +# --------------------------------------------------------------------------- +$have = Get-WificxMacPair +$pairOk = ($have -contains '22-22-22-22-00-01') -and ($have -contains '22-22-22-22-00-02') +if ($pairOk) { Write-Ok "MAC pair complete (00-01 and 00-02)" } +else { Write-Warn "MAC pair incomplete (have: $($have -join ', '))" } + +# --------------------------------------------------------------------------- +# Remove leftover Error-state companion devnodes, protecting the two real +# mains (the PnpDeviceIDs that own each 00:0X MAC). +# --------------------------------------------------------------------------- +Write-Step "Cleaning up companion Error-state devices" +if ($pairOk) { + $mainIds = @(Get-NetAdapter -IncludeHidden -ErrorAction SilentlyContinue | + Where-Object { $_.MacAddress -in '22-22-22-22-00-01', '22-22-22-22-00-02' } | + Select-Object -ExpandProperty PnpDeviceID) + $companions = @(Get-WificxDevnodes | + Where-Object { $_.Status -eq 'Error' -and $mainIds -notcontains $_.InstanceId }) + if ($companions) { + foreach ($dev in $companions) { + pnputil /remove-device "$($dev.InstanceId)" /force 2>&1 | Out-Null + Write-Ok "Removed companion $($dev.InstanceId)" + } + } else { + Write-Ok "No companion devices to remove" + } +} else { + Write-Ok "Companion cleanup skipped — leaving devices untouched" +} + +# --------------------------------------------------------------------------- +# Verify +# --------------------------------------------------------------------------- +Write-Step "Verifying" +pnputil /enum-drivers /class net +Write-Host "" +ipconfig /all | Select-String -Pattern "adapter|IPv4|Description" -Context 0,1 + +if ($needsReboot) { + Write-Warn "A reboot is required for the second adapter to start." + Write-Warn "Run: shutdown /r /t 0" +} + +Write-Host "`n==> Done!" -ForegroundColor Green +Write-Host @" + Two WiFiCx adapters installed and paired via ENL (MACLastByte 1 & 2). + + Next steps: + 1. Open Settings > Network > Wi-Fi and scan for networks + 2. Connect to one of the fake SSIDs + 3. Assign IPs and ping between the two WiFi adapters: + netsh interface ip set address "Wi-Fi" static 192.168.100.1 255.255.255.0 + netsh interface ip set address "Wi-Fi 2" static 192.168.100.2 255.255.255.0 + ping 192.168.100.2 -S 192.168.100.1 + + To remove: .\deploy.ps1 -Uninstall +"@ diff --git a/network/wlan/wificx/run.ps1 b/network/wlan/wificx/run.ps1 new file mode 100644 index 000000000..0831a6996 --- /dev/null +++ b/network/wlan/wificx/run.ps1 @@ -0,0 +1,259 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + Connect both WiFiCx virtual adapters and verify data path with ping. +.DESCRIPTION + Automates what the GUI does manually: discover WiFiCx interfaces, + ensure MACLastByte pairing, create a WLAN profile, connect both + adapters, assign static IPs, and ping between them. + + Run after deploy.ps1 has installed both WiFiCx adapter instances. +.PARAMETER SSID + SSID to connect to (default: WFC_OPEN). +.PARAMETER Ip1 + Static IP for the first adapter (default: 192.168.100.1). +.PARAMETER Ip2 + Static IP for the second adapter (default: 192.168.100.2). +.PARAMETER Mask + Subnet mask (default: 255.255.255.0). +#> +param( + [string]$SSID = "WFC_OPEN", + [string]$Ip1 = "192.168.100.1", + [string]$Ip2 = "192.168.100.2", + [string]$Mask = "255.255.255.0" +) + +$ErrorActionPreference = "Stop" + +function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan } +function Write-Ok($msg) { Write-Host " OK: $msg" -ForegroundColor Green } +function Write-Warn($msg) { Write-Host " WARNING: $msg" -ForegroundColor Yellow } +function Write-Fail($msg) { Write-Host " FAIL: $msg" -ForegroundColor Red } + +function Dump-Debug { + Write-Host "`n--- Debug dump ---" -ForegroundColor Yellow + Write-Host "`n[netsh wlan show interfaces]" + netsh wlan show interfaces + Write-Host "`n[ipconfig]" + ipconfig + Write-Host "`n[arp -a]" + arp -a + Write-Host "`n[Get-PnpDevice WiFiCx]" + Get-PnpDevice -FriendlyName "*WiFiCx*" -ErrorAction SilentlyContinue | + Format-Table Status, InstanceId, FriendlyName -AutoSize + Write-Host "--- End debug dump ---`n" -ForegroundColor Yellow +} + +# --------------------------------------------------------------------------- +# 1. Discover WiFiCx adapters +# --------------------------------------------------------------------------- +Write-Step "Discovering WiFiCx adapters" + +$wificxDevices = Get-PnpDevice -FriendlyName "*WiFiCx*" -Status OK -ErrorAction SilentlyContinue | + Sort-Object InstanceId + +if ($wificxDevices.Count -lt 2) { + Write-Fail "Need 2 WiFiCx adapters in OK state, found $($wificxDevices.Count). Run deploy.ps1 first." + Dump-Debug + exit 1 +} + +Write-Ok "Found $($wificxDevices.Count) WiFiCx adapters" + +# Pairing is owned by deploy.ps1; run.ps1 must not touch MACLastByte or +# disable/enable here -- doing so re-enumerates the queues and tears down the +# ENL pairing right before the ping. Adapters are matched by MAC below. + +# --------------------------------------------------------------------------- +# 3. Find WLAN interface names +# --------------------------------------------------------------------------- +Write-Step "Mapping WLAN interfaces" + +$wlanOutput = netsh wlan show interfaces +$interfaces = @() +$currentIf = $null + +foreach ($line in $wlanOutput -split "`n") { + if ($line -match '^\s*Name\s*:\s*(.+)$') { + if ($currentIf) { $interfaces += $currentIf } + $currentIf = @{ Name = $Matches[1].Trim(); MAC = ""; State = "" } + } + elseif ($currentIf -and $line -match '^\s*Physical address\s*:\s*(.+)$') { + $currentIf.MAC = $Matches[1].Trim() + } + elseif ($currentIf -and $line -match '^\s*State\s*:\s*(.+)$') { + $currentIf.State = $Matches[1].Trim() + } +} +if ($currentIf) { $interfaces += $currentIf } + +$wificxIfs = $interfaces | Where-Object { $_.MAC -match "22:22:22:22:00:" } + +if ($wificxIfs.Count -lt 2) { + Write-Fail "Found $($wificxIfs.Count) WiFiCx WLAN interfaces (need 2)" + Dump-Debug + exit 1 +} + +$if1 = ($wificxIfs | Where-Object { $_.MAC -match "00:01$" }) +$if2 = ($wificxIfs | Where-Object { $_.MAC -match "00:02$" }) + +if (-not $if1 -or -not $if2) { + Write-Fail "Cannot identify adapter1 (00:01) and adapter2 (00:02)" + Dump-Debug + exit 1 +} + +$name1 = $if1.Name +$name2 = $if2.Name +Write-Ok "Adapter 1: '$name1' ($($if1.MAC))" +Write-Ok "Adapter 2: '$name2' ($($if2.MAC))" + +# --------------------------------------------------------------------------- +# 4. Enable interfaces and ensure Wi-Fi radio is on +# --------------------------------------------------------------------------- +Write-Step "Enabling Wi-Fi interfaces" +netsh interface set interface "$name1" enable 2>&1 | Out-Null +netsh interface set interface "$name2" enable 2>&1 | Out-Null +Start-Sleep -Seconds 2 +Write-Ok "Interfaces enabled" + +# --------------------------------------------------------------------------- +# 5. Create WLAN profile and connect +# --------------------------------------------------------------------------- +Write-Step "Creating WLAN profile for '$SSID'" + +$profileXml = @" + + + $SSID + + + $SSID + + + ESS + manual + + + + open + none + false + + + + +"@ + +$profilePath = "$env:TEMP\wificx_$SSID.xml" +$profileXml | Out-File -Encoding UTF8 -FilePath $profilePath + +netsh wlan add profile filename="$profilePath" interface="$name1" 2>&1 | Out-Null +netsh wlan add profile filename="$profilePath" interface="$name2" 2>&1 | Out-Null +Write-Ok "Profile added to both interfaces" + +Write-Step "Connecting '$name1' to $SSID" +netsh wlan connect name="$SSID" interface="$name1" 2>&1 | Out-Null + +Write-Step "Connecting '$name2' to $SSID" +netsh wlan connect name="$SSID" interface="$name2" 2>&1 | Out-Null + +# Poll until both connected (scan + connect is async and can take 10-20s) +$connTimeout = 30 +$connStart = Get-Date +$connected1 = $false +$connected2 = $false + +Write-Step "Waiting for WLAN connections (timeout ${connTimeout}s)" +do { + Start-Sleep -Seconds 2 + $wlanText = (netsh wlan show interfaces) -join "`n" + $blocks = $wlanText -split "(?=\s+Name\s*:)" + + foreach ($block in $blocks) { + if ($block -match [regex]::Escape($name1) -and $block -match "State\s*:\s*connected") { + if (-not $connected1) { Write-Ok "'$name1' connected to $SSID" } + $connected1 = $true + } + if ($block -match [regex]::Escape($name2) -and $block -match "State\s*:\s*connected") { + if (-not $connected2) { Write-Ok "'$name2' connected to $SSID" } + $connected2 = $true + } + } + + $elapsed = [int]((Get-Date) - $connStart).TotalSeconds + if (-not $connected1 -or -not $connected2) { + Write-Host " [$elapsed s] '$name1'=$connected1 '$name2'=$connected2" + } +} while ((-not $connected1 -or -not $connected2) -and + ((Get-Date) - $connStart).TotalSeconds -lt $connTimeout) + +if (-not $connected1) { Write-Warn "'$name1' did not connect within ${connTimeout}s" } +if (-not $connected2) { Write-Warn "'$name2' did not connect within ${connTimeout}s" } + +if (-not $connected1 -and -not $connected2) { + Write-Fail "Neither adapter connected" + Dump-Debug; exit 1 +} + +# --------------------------------------------------------------------------- +# 5. Assign static IPs +# --------------------------------------------------------------------------- +Write-Step "Assigning static IPs" + +netsh interface ip set address "$name1" static $Ip1 $Mask 2>&1 | Out-Null +Write-Ok "$name1 = $Ip1" + +netsh interface ip set address "$name2" static $Ip2 $Mask 2>&1 | Out-Null +Write-Ok "$name2 = $Ip2" + +Start-Sleep -Seconds 2 + +# --------------------------------------------------------------------------- +# 5b. Seed static ARP neighbors +# --------------------------------------------------------------------------- +# Both adapters share one host and subnet, so each stack treats the other's IP +# as a local duplicate and won't answer its ARP. Seed static neighbor entries +# to bypass ARP and let the ICMP frames traverse the ENL link. +Write-Step "Seeding static ARP neighbors" + +$mac1 = ($if1.MAC -replace ':', '-') # 22-22-22-22-00-01 +$mac2 = ($if2.MAC -replace ':', '-') # 22-22-22-22-00-02 + +netsh interface ipv4 delete neighbors "$name1" $Ip2 2>&1 | Out-Null +netsh interface ipv4 add neighbors "$name1" $Ip2 $mac2 2>&1 | Out-Null +Write-Ok "${name1}: $Ip2 -> $mac2" + +netsh interface ipv4 delete neighbors "$name2" $Ip1 2>&1 | Out-Null +netsh interface ipv4 add neighbors "$name2" $Ip1 $mac1 2>&1 | Out-Null +Write-Ok "${name2}: $Ip1 -> $mac1" + +# --------------------------------------------------------------------------- +# 6. Ping test +# --------------------------------------------------------------------------- +Write-Step "Ping $Ip1 -> $Ip2" +$ping1 = ping $Ip2 -S $Ip1 -n 4 -w 1000 +$ping1 | ForEach-Object { Write-Host " $_" } +$pass1 = ($ping1 | Select-String "Reply from $Ip2" | Measure-Object).Count -gt 0 + +Write-Step "Ping $Ip2 -> $Ip1" +$ping2 = ping $Ip1 -S $Ip2 -n 4 -w 1000 +$ping2 | ForEach-Object { Write-Host " $_" } +$pass2 = ($ping2 | Select-String "Reply from $Ip1" | Measure-Object).Count -gt 0 + +# --------------------------------------------------------------------------- +# 7. Result +# --------------------------------------------------------------------------- +Write-Host "" +if ($pass1 -and $pass2) { + Write-Ok "PASS: Bidirectional ping succeeded between WiFiCx adapters" + exit 0 +} else { + Write-Fail "FAIL: Ping did not succeed" + if (-not $pass1) { Write-Fail " ${Ip1} -> ${Ip2}: no reply" } + if (-not $pass2) { Write-Fail " ${Ip2} -> ${Ip1}: no reply" } + Dump-Debug + exit 1 +} From d245240e204866ea9a4e2f0d926fba3ae0f6f2b3 Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Thu, 2 Jul 2026 00:27:55 +0530 Subject: [PATCH 6/7] wificx: note the ENL access-point relay in the README Document that the ENL also performs the access point's relay role, so a frame one station sends is rewritten into the form the other station accepts. Signed-off-by: Chaitanya Tata --- network/wlan/wificx/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/wlan/wificx/README.md b/network/wlan/wificx/README.md index fb4ca92af..15c277a55 100644 --- a/network/wlan/wificx/README.md +++ b/network/wlan/wificx/README.md @@ -33,7 +33,7 @@ The sample interacts with three OS components: `netvxxxx` files and classes implement the **data path**, managing network buffers and synchronizing with the control path for transfer start/stop operations. ## Data Buffers from Firmware -The control path uses hardcoded data since this sample does not target real hardware. The data path uses **Emulated Network Link (ENL)**, which connects two virtual network adapters directly. Packets sent over one adapter are delivered to the other and vice versa. +The control path uses hardcoded data since this sample does not target real hardware. The data path uses **Emulated Network Link (ENL)**, which connects two virtual network adapters directly. Packets sent over one adapter are delivered to the other and vice versa. Because both instances associate to the same emulated access point, the ENL also performs the access point's relay role, rewriting each forwarded 802.11 frame so the receiving station accepts it. ## Supported Scenarios - [x] Scan access points and report BSS entries to the Windows UI. From a1d6c314e5b7ed15fe8f13968c79a1455d136a6f Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Thu, 2 Jul 2026 00:28:13 +0530 Subject: [PATCH 7/7] wificx: gate datapath debug tracing behind off-by-default macros The DbgPrintEx diagnostics used while bringing up the two-instance data path are useful for future debugging but must not be active in a normal build. Gate them all behind preprocessor switches that are undefined by default: - netvadapterlibrary (enl.cpp, rxqueue.cpp, adapter.cpp): the ENL forward / rx-indication probes and queue-event traces compile only when NETV_DATAPATH_DEBUG is defined. - wificx drivercode: DbgPrintEx calls route through a WFC_TRACE macro (precomp.h) that expands to nothing unless WIFICX_DATAPATH_DEBUG is defined. Keeping the instrumentation in one self-contained, off-by-default commit makes it easy to enable for diagnosis or drop entirely. Signed-off-by: Chaitanya Tata --- .../netvadapterlibrary/code/adapter.cpp | 23 ++++++++++++- .../netvadapterlibrary/code/enl.cpp | 30 +++++++++++++++++ .../netvadapterlibrary/code/rxqueue.cpp | 32 +++++++++++++++++++ network/wlan/wificx/drivercode/device.cpp | 4 +++ network/wlan/wificx/drivercode/precomp.h | 8 +++++ network/wlan/wificx/drivercode/wifihal.cpp | 16 ++++++++++ .../wlan/wificx/drivercode/wifitransition.cpp | 6 ++++ 7 files changed, 118 insertions(+), 1 deletion(-) diff --git a/network/netadaptercx/netvadapterlibrary/code/adapter.cpp b/network/netadaptercx/netvadapterlibrary/code/adapter.cpp index 880f970d8..e16c12d36 100644 --- a/network/netadaptercx/netvadapterlibrary/code/adapter.cpp +++ b/network/netadaptercx/netvadapterlibrary/code/adapter.cpp @@ -307,7 +307,11 @@ EvtSetReceiveFilter( ) { NetvAdapter* adapter = NetvAdapterGetContextFromWDFObject(NetAdapter); - +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + "ENL EvtSetReceiveFilter port=%u filter=0x%x\n", + adapter->EnlPortIndex, (ULONG)NetReceiveFilterGetPacketFilter(Handle)); +#endif adapter->PacketFilter = NetReceiveFilterGetPacketFilter(Handle); adapter->NumMulticastAddresses = (ULONG)NetReceiveFilterGetMulticastAddressCount(Handle); @@ -507,6 +511,11 @@ EvtTxQueueStart( NETPACKETQUEUE Queue ) { +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + "ENL EvtTxQueueStart port=%u\n", + NetvTxQueueGetContext(Queue)->m_adapter.EnlPortIndex); +#endif NetvTxQueueGetContext(Queue)->Start(); } @@ -525,6 +534,13 @@ EvtTxQueueAdvance( NETPACKETQUEUE Queue ) { +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + static LONG txAdvCount = 0; + if (InterlockedIncrement(&txAdvCount) <= 3) + DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + "ENL EvtTxQueueAdvance port=%u\n", + NetvTxQueueGetContext(Queue)->m_adapter.EnlPortIndex); +#endif NetvTxQueueGetContext(Queue)->Advance(); } @@ -553,6 +569,11 @@ EvtRxQueueStart( NETPACKETQUEUE Queue ) { +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + "ENL EvtRxQueueStart port=%u\n", + NetvRxQueueGetContext(Queue)->m_adapter.EnlPortIndex); +#endif NetvRxQueueGetContext(Queue)->Start(); } diff --git a/network/netadaptercx/netvadapterlibrary/code/enl.cpp b/network/netadaptercx/netvadapterlibrary/code/enl.cpp index 6990ac951..4a4bbab68 100644 --- a/network/netadaptercx/netvadapterlibrary/code/enl.cpp +++ b/network/netadaptercx/netvadapterlibrary/code/enl.cpp @@ -15,6 +15,12 @@ ENL_MLINK NetvEnlMLink[MAX_ADAPTER_COUNT / 2]; +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) +// Forward-logging budgets, reset when the link goes idle so each tx burst +// starts fresh. g_rxAdvDbg is defined in rxqueue.cpp. +static LONG g_enlFwdDbg = 0; +extern LONG g_rxAdvDbg; +#endif /*++ The iteration routine performs one full pass over all input queues and @@ -383,10 +389,18 @@ EnlpIterationRoutine( EnlDisarmWake(enlLink); } +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + int dbgRxState = -1; + int dbgHasFrag = -1; + int dbgHasPkt = -1; +#endif if (rxport->RxQueueCount > 0) { //TODO: Currently does 1:1 mapping between Tx and Rx. Need to set up indirection table auto rxq = &rxport->RxQueue[ci]; +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + dbgRxState = rxq->State; +#endif if (rxq->State == Started) { @@ -395,6 +409,10 @@ EnlpIterationRoutine( }; auto rxPi = NetRingGetPostPackets(rxq->Queue->m_rings); +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + dbgHasFrag = NetFragmentIteratorHasAny(&rxFi) ? 1 : 0; + dbgHasPkt = NetPacketIteratorHasAny(&rxPi) ? 1 : 0; +#endif if (NetFragmentIteratorHasAny(&rxFi) && NetPacketIteratorHasAny(&rxPi)) { @@ -512,6 +530,13 @@ EnlpIterationRoutine( } } +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + if (InterlockedIncrement(&g_enlFwdDbg) <= 30) + DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + "ENL fwd tx-port=%zu rx-port=%zu rxQCount=%lu rxState=%d hasFrag=%d hasPkt=%d drop=%d\n", + pi, pi ^ 0x1, (ULONG)rxport->RxQueueCount, + dbgRxState, dbgHasFrag, dbgHasPkt, rxDrop ? 1 : 0); +#endif if (rxDrop) { // TODO - add rxdrop stat @@ -539,6 +564,11 @@ EnlpIterationRoutine( if (emptyTx) { +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + // Link idle: give the next tx burst a fresh logging budget. + InterlockedExchange(&g_enlFwdDbg, 0); + InterlockedExchange(&g_rxAdvDbg, 0); +#endif if (enlLink->Poll == FALSE) { enlpArmAndWait(enlLink); diff --git a/network/netadaptercx/netvadapterlibrary/code/rxqueue.cpp b/network/netadaptercx/netvadapterlibrary/code/rxqueue.cpp index 6eed638c1..539059c06 100644 --- a/network/netadaptercx/netvadapterlibrary/code/rxqueue.cpp +++ b/network/netadaptercx/netvadapterlibrary/code/rxqueue.cpp @@ -5,6 +5,12 @@ #include "rxqueue.h" #include "memory.h" +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) +// Rx-indication logging budget. Non-static so enl.cpp can reset it when the +// link goes idle (see g_enlFwdDbg). +LONG g_rxAdvDbg = 0; +#endif + static void CheckForWakeFrame( @@ -117,11 +123,25 @@ NetvRxQueue::Advance( // Ideally this would run in EvtQueueStart, but at that point the receive buffers are not // attached to the fragment yet +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + bool const ranWake = ! CheckedWakeFrame; +#endif if (! CheckedWakeFrame) { CheckForWakeFrame(this); } +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + ULONG const dbgFBegin = fr->BeginIndex; + ULONG const dbgFEnd = fr->EndIndex; + int const dbgHaveFrag = NetFragmentIteratorHasAny(&fi) ? 1 : 0; + int const dbgHavePkt = NetPacketIteratorHasAny(&pi) ? 1 : 0; + int dbgFirstScratch = -1; + if (dbgHaveFrag) + dbgFirstScratch = (int)NetFragmentIteratorGetFragment(&fi)->Scratch; + ULONG dbgDrained = 0; +#endif + // Move begin index forward for all fragments with Scratch == 1, thus returning them to the OS since we're done processing them. for (; NetFragmentIteratorHasAny(&fi) && NetPacketIteratorHasAny(&pi); NetPacketIteratorAdvance(&pi), NetFragmentIteratorAdvance(&fi)) { @@ -130,11 +150,23 @@ NetvRxQueue::Advance( { break; } +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + dbgDrained++; +#endif } NetFragmentIteratorSet(&fi); NetPacketIteratorSet(&pi); EnlRingDoorBell(EnlQueueHandle, fr->EndIndex); + +#if defined(_KERNEL_MODE) && defined(NETV_DATAPATH_DEBUG) + if ((dbgDrained > 0 || ranWake || dbgFirstScratch == 1) && + InterlockedIncrement(&g_rxAdvDbg) <= 40) + DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + "RX adv port=%u fBegin=%lu fEnd=%lu haveFrag=%d havePkt=%d firstScr=%d drained=%lu wake=%d\n", + (ULONG)m_adapter.EnlPortIndex, dbgFBegin, dbgFEnd, + dbgHaveFrag, dbgHavePkt, dbgFirstScratch, dbgDrained, ranWake ? 1 : 0); +#endif } _Use_decl_annotations_ diff --git a/network/wlan/wificx/drivercode/device.cpp b/network/wlan/wificx/drivercode/device.cpp index 2b4af6c5c..21c06ab29 100644 --- a/network/wlan/wificx/drivercode/device.cpp +++ b/network/wlan/wificx/drivercode/device.cpp @@ -135,6 +135,8 @@ _Use_decl_annotations_ NTSTATUS EvtAdapterCreateTxQueue(NETADAPTER Adapter, NETTXQUEUE_INIT* Init) { + WFC_TRACE( + "WiFiCx EvtAdapterCreateTxQueue called\n"); TraceEntry(); return WifiNetvAdapterGetContext(Adapter)->CreateTxQueue(Init); } @@ -143,6 +145,8 @@ _Use_decl_annotations_ NTSTATUS EvtAdapterCreateRxQueue(NETADAPTER Adapter, NETRXQUEUE_INIT* Init) { + WFC_TRACE( + "WiFiCx EvtAdapterCreateRxQueue called\n"); TraceEntry(); return WifiNetvAdapterGetContext(Adapter)->CreateRxQueue(Init); } diff --git a/network/wlan/wificx/drivercode/precomp.h b/network/wlan/wificx/drivercode/precomp.h index 1b0f808e0..62de018d2 100644 --- a/network/wlan/wificx/drivercode/precomp.h +++ b/network/wlan/wificx/drivercode/precomp.h @@ -27,6 +27,14 @@ // WPP Tracing Headers #include "trace.h" +// Off-by-default datapath trace. Define WIFICX_DATAPATH_DEBUG in the project +// preprocessor definitions to emit DbgPrintEx diagnostics. +#if defined(WIFICX_DATAPATH_DEBUG) && defined(_KERNEL_MODE) +#define WFC_TRACE(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, __VA_ARGS__) +#else +#define WFC_TRACE(...) ((void)0) +#endif + // Minimal placement-new to match operator new(size_t, void*) // TLV generator/parser memory interface has the ULONG_PTR version inline void* operator new(size_t, void* p) noexcept { return p; } diff --git a/network/wlan/wificx/drivercode/wifihal.cpp b/network/wlan/wificx/drivercode/wifihal.cpp index 792f307ff..e0b9247bc 100644 --- a/network/wlan/wificx/drivercode/wifihal.cpp +++ b/network/wlan/wificx/drivercode/wifihal.cpp @@ -544,6 +544,8 @@ NTSTATUS WifiHAL::WifiIhvSetRadioState(const WDI_SET_RADIO_STATE_PARAMETERS& Rad RadioStatusParams.RadioState.SoftwareState = TRUE; if (GenerateWdiIndicationRadioStatus(&RadioStatusParams, 0, m_TlvContext, &cbOutput, &pOutput) == NDIS_STATUS_SUCCESS) { + WFC_TRACE( + "WiFiCx WifiIhvSetRadioState: forcing Software=On\n"); WifiIhvSendUnsolicitedIndicationToOs(m_Device, pWdiHeader, WDI_INDICATION_RADIO_STATUS, pOutput, cbOutput); FreeGenerated(pOutput); } @@ -602,6 +604,9 @@ NTSTATUS WifiHAL::WifiIhvScan(const WDI_SCAN_PARAMETERS& ScanParameters, const P _Use_decl_annotations_ NTSTATUS WifiHAL::WifiIhvConnect(const WDI_TASK_CONNECT_PARAMETERS& ConnectParameters, const PWDI_MESSAGE_HEADER pWdiHeader, UINT) { + WFC_TRACE( + "WiFiCx WifiIhvConnect ENTER PortId=%u BSSCount=%u\n", + pWdiHeader->PortId, ConnectParameters.PreferredBSSEntryList.ElementCount); NT_ASSERT(m_LastConnectEntryId == 0); #ifdef NETV_SUPPORT_TX_DEMUXING if (m_LastConnectEntryId != 0) // Not Disconnected State @@ -613,6 +618,8 @@ NTSTATUS WifiHAL::WifiIhvConnect(const WDI_TASK_CONNECT_PARAMETERS& ConnectParam #endif //NETV_SUPPORT_TX_DEMUXING NTSTATUS assocStatus = WifiIhvPerformAssociation( &ConnectParameters.PreferredBSSEntryList, &ConnectParameters.ConnectParameters.AuthenticationAlgorithms, pWdiHeader); + WFC_TRACE( + "WiFiCx WifiIhvPerformAssociation returned 0x%08X\n", assocStatus); if (!NT_SUCCESS(assocStatus)) { return assocStatus; @@ -630,7 +637,13 @@ NTSTATUS WifiHAL::WifiIhvConnect(const WDI_TASK_CONNECT_PARAMETERS& ConnectParam m_LastConnectTransactionId = 0; } + WFC_TRACE( + "WiFiCx WifiIhvConnect done PortId=%u AuthAlgo=%u EntryId=%u\n", + pWdiHeader->PortId, m_LastAuthAlgo, m_LastConnectEntryId); NETADAPTER netAdapter = WifiGetIhvDeviceContext(m_Device)->netAdapters[pWdiHeader->PortId]; + WFC_TRACE( + "WiFiCx netAdapter[%u]=%p\n", + pWdiHeader->PortId, (void*)netAdapter); if (netAdapter != WDF_NO_HANDLE) { NET_ADAPTER_LINK_STATE linkState; @@ -642,6 +655,9 @@ NTSTATUS WifiHAL::WifiIhvConnect(const WDI_TASK_CONNECT_PARAMETERS& ConnectParam NetAdapterPauseFunctionTypeUnsupported, NetAdapterAutoNegotiationFlagNone); NetAdapterSetLinkState(netAdapter, &linkState); + WFC_TRACE( + "WiFiCx SetLinkState(Connected) PortId=%u\n", + pWdiHeader->PortId); } return STATUS_SUCCESS; diff --git a/network/wlan/wificx/drivercode/wifitransition.cpp b/network/wlan/wificx/drivercode/wifitransition.cpp index 64470d3c6..7e4d6bb05 100644 --- a/network/wlan/wificx/drivercode/wifitransition.cpp +++ b/network/wlan/wificx/drivercode/wifitransition.cpp @@ -495,11 +495,17 @@ NTSTATUS RunTransitionByMessage(TransitionContext& ctx, UINT16 messageId) UINT16 indicationId = GetTaskCompletionIndication(messageId); if (indicationId != 0) { + WFC_TRACE( + "WiFiCx UNHANDLED task MessageId=0x%04X PortId=%u (M3+M4)\n", + messageId, ctx.Header->PortId); WifiIhvNotifyM3Completion(ctx.WifiRequest, STATUS_SUCCESS, bytesWritten); WifiIhvSendM4IndicationToOs(ctx.Device, indicationId, ctx.Header, STATUS_SUCCESS); } else { + WFC_TRACE( + "WiFiCx UNHANDLED property MessageId=0x%04X PortId=%u (M3)\n", + messageId, ctx.Header->PortId); WifiRequestComplete(ctx.WifiRequest, STATUS_SUCCESS, bytesWritten); } return STATUS_SUCCESS;