feat: intel wi-fi (AX211) driver with network scanning

This commit is contained in:
2026-08-06 08:36:10 +02:00
parent 75d92d560c
commit a01e63c717
21 changed files with 5649 additions and 4 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 46
#define MONTAUK_BUILD_NUMBER 53
+14
View File
@@ -34,6 +34,7 @@
#include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL
#include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO
#include "Sdr.hpp" // SYS_SDR_COUNT, SYS_SDR_INFO, SYS_SDR_OPEN, SYS_SDR_CLOSE, SYS_SDR_START, SYS_SDR_STOP, SYS_SDR_READ, SYS_SDR_SETPARAM, SYS_SDR_GETPARAM
#include "WifiSyscall.hpp" // SYS_WIFI_SCAN, SYS_WIFI_INFO, SYS_WIFI_CONNECT, SYS_WIFI_DISCONNECT
#include "IpcSyscall.hpp" // SYS_DUPHANDLE, SYS_WAIT_HANDLE, SYS_STREAM_CREATE, SYS_STREAM_READ, SYS_STREAM_WRITE, SYS_MAILBOX_CREATE, SYS_MAILBOX_SEND, SYS_MAILBOX_RECV, SYS_WAITSET_CREATE, SYS_WAITSET_ADD, SYS_WAITSET_REMOVE, SYS_WAITSET_WAIT, SYS_PROC_OPEN, SYS_SURFACE_CREATE, SYS_SURFACE_MAP, SYS_SURFACE_RESIZE
#include "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM
#include "CrashReportSyscall.hpp" // SYS_CRASH_REPORT
@@ -443,6 +444,19 @@ namespace montauk::abi {
case SYS_BTINFO:
if (!UserMemory::Writable<BtAdapterInfo>(frame->arg1)) return -1;
return Sys_BtInfo((BtAdapterInfo*)frame->arg1);
case SYS_WIFI_SCAN:
if ((int64_t)frame->arg2 < 0) return -1;
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1;
return Sys_WifiScan((WifiNetwork*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
case SYS_WIFI_INFO:
if (!UserMemory::Writable<WifiInfo>(frame->arg1)) return -1;
return Sys_WifiInfo((WifiInfo*)frame->arg1);
case SYS_WIFI_CONNECT:
if (!UserMemory::String(frame->arg1, 64)) return -1;
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
return Sys_WifiConnect((const char*)frame->arg1, (const char*)frame->arg2);
case SYS_WIFI_DISCONNECT:
return Sys_WifiDisconnect();
case SYS_SUSPEND:
return Sys_Suspend();
case SYS_SETTZ:
+46
View File
@@ -297,6 +297,12 @@ namespace montauk::abi {
static constexpr uint64_t SYS_DISPLAYSETMODE = 156;
static constexpr uint64_t SYS_DISPLAYBRIGHTNESS = 157;
/* Wifi.hpp -- Wi-Fi adapter control */
static constexpr uint64_t SYS_WIFI_SCAN = 158; // (WifiNetwork*, maxCount, timeoutMs) -> count
static constexpr uint64_t SYS_WIFI_INFO = 159; // (WifiInfo*) -> 0, -1 if absent
static constexpr uint64_t SYS_WIFI_CONNECT = 160; // (ssid, password) -> 0, <0 on error
static constexpr uint64_t SYS_WIFI_DISCONNECT = 161; // () -> 0
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
@@ -639,6 +645,46 @@ namespace montauk::abi {
uint32_t _pad2;
};
// Wi-Fi security suites reported in WifiNetwork.security.
static constexpr uint8_t WIFI_SEC_OPEN = 0;
static constexpr uint8_t WIFI_SEC_WEP = 1;
static constexpr uint8_t WIFI_SEC_WPA = 2;
static constexpr uint8_t WIFI_SEC_WPA2 = 3;
static constexpr uint8_t WIFI_SEC_WPA3 = 4;
// Adapter states reported in WifiInfo.state.
static constexpr uint8_t WIFI_STATE_ABSENT = 0; // no device
static constexpr uint8_t WIFI_STATE_DETECTED = 1; // waiting for firmware load
static constexpr uint8_t WIFI_STATE_BOOTING = 2;
static constexpr uint8_t WIFI_STATE_RUNNING = 3;
static constexpr uint8_t WIFI_STATE_ERROR = 4;
static constexpr uint8_t WIFI_STATE_RFKILL = 5; // radio disabled in hardware
// One scanned network (returned by SYS_WIFI_SCAN).
struct WifiNetwork {
char ssid[36]; // NUL-terminated; empty for hidden networks
uint8_t bssid[6];
uint8_t channel;
int8_t rssi; // dBm
uint8_t band; // 0 = 2.4 GHz, 1 = 5 GHz
uint8_t security; // WIFI_SEC_*
uint16_t beaconInterval; // TU
};
// Adapter status (returned by SYS_WIFI_INFO).
struct WifiInfo {
uint8_t mac[6];
uint8_t present; // 1 if a supported device was found
uint8_t state; // WIFI_STATE_*
uint8_t scanning;
uint8_t bands; // bit0 = 2.4 GHz, bit1 = 5 GHz
uint16_t channels; // usable channels after regulatory filtering
char fwVersion[32];
uint64_t rxPackets;
uint32_t fwErrors;
uint32_t connState; // 0 idle, >0 connection setup in progress
};
struct ThermalInfo {
char name[32]; // short zone name (e.g. "THRM", "TZ00")
int32_t temperature; // tenths of degrees Celsius, or -1 if unavailable
+35
View File
@@ -0,0 +1,35 @@
/*
* WifiSyscall.hpp
* SYS_WIFI_SCAN, SYS_WIFI_INFO, SYS_WIFI_CONNECT, SYS_WIFI_DISCONNECT
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Drivers/Net/Wifi/Wifi.hpp>
#include "Syscall.hpp"
namespace montauk::abi {
static int64_t Sys_WifiScan(WifiNetwork* buf, int maxCount, uint32_t timeoutMs) {
if (!buf || maxCount <= 0) return -1;
if (maxCount > 64) maxCount = 64;
return (int64_t)Drivers::Net::Wifi::Scan(buf, maxCount, timeoutMs);
}
static int64_t Sys_WifiInfo(WifiInfo* buf) {
if (!buf) return -1;
return (int64_t)Drivers::Net::Wifi::GetInfo(buf);
}
static int64_t Sys_WifiConnect(const char* ssid, const char* password) {
if (!ssid) return -1;
return (int64_t)Drivers::Net::Wifi::Connect(ssid, password);
}
static int64_t Sys_WifiDisconnect() {
return (int64_t)Drivers::Net::Wifi::Disconnect();
}
}
+33 -3
View File
@@ -9,6 +9,7 @@
#include <Drivers/Graphics/IntelGPU.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Drivers/Net/Wifi/Wifi.hpp>
#include <Drivers/USB/Xhci.hpp>
#include <Drivers/Storage/Ahci.hpp>
#include <Drivers/Storage/Nvme.hpp>
@@ -43,6 +44,18 @@ namespace Drivers {
0x1A1F,
};
// Intel AX210-family Wi-Fi (CNVi and discrete). The driver additionally
// checks CSR_HW_RF_ID at probe time and only claims RF type GF (AX211),
// which is the firmware image shipped on the ramdisk.
static constexpr uint16_t g_intelWifiIds[] = {
0x2725, // AX210 (discrete)
0x2726, // AX211 (discrete, GF2)
0x51f0, 0x51f1, 0x54f0, // AlderLake CNVi
0x7a70, 0x7af0, // RaptorLake / AlderLake-P CNVi
0x7e40, // MeteorLake CNVi
0x7f70, // RaptorLake-S CNVi
};
// -------------------------------------------------------------------------
// Probe wrappers (adapt namespace::Probe to PciProbeFunc signature)
// -------------------------------------------------------------------------
@@ -63,6 +76,10 @@ namespace Drivers {
return Net::E1000E::Probe(dev);
}
static bool ProbeWifi(const Pci::PciDevice& dev) {
return Net::Wifi::Probe(dev);
}
static bool ProbeAhci(const Pci::PciDevice& dev) {
return Storage::Ahci::Probe(dev);
}
@@ -124,7 +141,20 @@ namespace Drivers {
Pci::ProbePhase::Normal,
ProbeE1000E,
},
// Order 5: AHCI — Normal phase, match class=0x01/0x06/0x01 (SATA AHCI)
// Order 5: Intel Wi-Fi — Normal phase, vendor=0x8086 + deviceIds list.
// These are the AX210-family (CNVi "So") parts; the probe
// rejects anything whose RF type is not GF, since only the
// AX211 firmware image is bundled.
{
"IntelWiFi",
0x8086,
0xFF, 0xFF, 0xFF,
g_intelWifiIds,
sizeof(g_intelWifiIds) / sizeof(g_intelWifiIds[0]),
Pci::ProbePhase::Normal,
ProbeWifi,
},
// Order 6: AHCI — Normal phase, match class=0x01/0x06/0x01 (SATA AHCI)
{
"AHCI",
0, // VendorId (any)
@@ -136,7 +166,7 @@ namespace Drivers {
Pci::ProbePhase::Normal,
ProbeAhci,
},
// Order 6: NVMe — Normal phase, match class=0x01/0x08/0x02 (NVM Express)
// Order 7: NVMe — Normal phase, match class=0x01/0x08/0x02 (NVM Express)
{
"NVMe",
0, // VendorId (any)
@@ -148,7 +178,7 @@ namespace Drivers {
Pci::ProbePhase::Normal,
ProbeNvme,
},
// Order 7: Intel HDA — Normal phase, match vendor=0x8086 + class=0x04 (Multimedia)
// Order 8: Intel HDA — Normal phase, match vendor=0x8086 + class=0x04 (Multimedia)
// SubClass 0x03 = "Audio device" (plain HD Audio controller).
// SubClass 0x01 = "Multimedia audio controller": on modern
// laptops this is the SAME HDA controller enumerated with the
+314
View File
@@ -0,0 +1,314 @@
/*
* Iwx.hpp
* Intel AX210/AX211 Wi-Fi driver core - shared state and internal API.
*
* The driver is split into:
* IwxTrans.cpp - PCIe transport: MMIO, MSI-X, DMA rings, firmware boot
* (context info gen3), host commands, RX processing
* IwxFw.cpp - .ucode / .pnvm TLV file parsing
* IwxMvm.cpp - post-ALIVE firmware init, NVM, UMAC scan
* IwxConnect.cpp - auth/assoc groundwork (untested scaffolding)
* Wifi.cpp - public subsystem facade (probe, deferred init, syscalls)
*
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Pci/Pci.hpp>
#include <CppLib/Spinlock.hpp>
#include "IwxReg.hpp"
namespace Drivers::Net::Wifi {
// =========================================================================
// DMA helpers (contiguous physical allocations from the PFA)
// =========================================================================
struct IwxDma {
void* Virt = nullptr;
uint64_t Phys = 0;
uint32_t Pages = 0;
};
bool IwxDmaAlloc(IwxDma& dma, uint64_t bytes); // zeroed, page-granular
void IwxDmaFree(IwxDma& dma);
// =========================================================================
// Firmware image (parsed .ucode file)
// =========================================================================
struct IwxFwSection {
uint32_t DevOff = 0; // device offset / separator marker
const uint8_t* Data = nullptr;
uint32_t Len = 0;
};
// AX211 firmware 89 has 60 sections (15 LMAC + 17 UMAC + 26 paging, plus
// two separators); leave headroom for newer images.
constexpr int IWX_MAX_FW_SECTIONS = 96;
constexpr int IWX_MAX_FW_CMD_VERSIONS = 384;
struct IwxFwInfo {
uint8_t* Raw = nullptr; // whole .ucode file (kernel heap)
uint64_t RawSize = 0;
IwxFwSection Sections[IWX_MAX_FW_SECTIONS];
int SectionCount = 0;
const uint8_t* Iml = nullptr; // image loader (points into Raw)
uint32_t ImlLen = 0;
// PNVM TLV blob: either embedded in the .ucode (points into Raw) or
// read from the separate .pnvm file, in which case PnvmOwned holds the
// heap allocation that has to be released.
const uint8_t* PnvmData = nullptr;
uint32_t PnvmLen = 0;
uint8_t* PnvmOwned = nullptr;
uint8_t ApiFlags[IWX_NUM_UCODE_TLV_API / 8] = {};
uint8_t Capa[IWX_NUM_UCODE_TLV_CAPA / 8] = {};
IwxFwCmdVersion CmdVersions[IWX_MAX_FW_CMD_VERSIONS];
int NumCmdVersions = 0;
uint32_t PhyConfig = 0; // PHY_SKU
uint32_t NumScanChannels = IWX_DEFAULT_SCAN_CHANNELS;
char Version[48] = {};
};
inline bool IwxBitSet(const uint8_t* map, uint32_t bit) {
return (map[bit / 8] & (1 << (bit % 8))) != 0;
}
// =========================================================================
// Rings
// =========================================================================
struct IwxTxRing {
int Qid = 0;
IwxDma Desc; // IwxTfhTfd[IWX_TX_RING_COUNT]
IwxDma Cmd; // IwxDeviceCmd[IWX_TX_RING_COUNT]
IwxDma BcTbl; // byte-count table
IwxDma Bounce; // one-page bounce for oversized commands
uint32_t Cur = 0; // ring slot (0..count-1)
uint32_t CurHw = 0; // hardware index (0..65535)
uint32_t Queued = 0;
};
struct IwxRxRing {
IwxDma FreeDescs; // IwxRxTransferDesc[IWX_RX_MQ_RING_COUNT]
IwxDma Stat; // uint16_t used-ring write index
IwxDma UsedDescs; // IwxRxCompletionDesc[IWX_RX_MQ_RING_COUNT]
uint8_t* Buf[IWX_RX_MQ_RING_COUNT] = {};
uint64_t BufPhys[IWX_RX_MQ_RING_COUNT] = {};
uint32_t Cur = 0;
};
// =========================================================================
// NVM data (from NVM_GET_INFO)
// =========================================================================
struct IwxNvmData {
uint8_t HwAddr[6] = {};
bool Sku24GHz = false;
bool Sku52GHz = false;
bool Sku11n = false;
bool Sku11ac = false;
bool Sku11ax = false;
bool LarEnabled = false;
uint8_t ValidTxAnt = 0;
uint8_t ValidRxAnt = 0;
uint16_t NvmVersion = 0;
};
// Per-channel scan availability derived from the NVM/MCC channel profile.
struct IwxChannel {
uint8_t ChannelNum = 0; // IEEE channel number
bool Is5GHz = false;
bool Valid = false;
bool ActiveAllowed = false; // active (probe) scanning allowed
};
constexpr int IWX_MAX_CHANNELS_TRACKED = IWX_NUM_2GHZ_CHANNELS + IWX_NUM_5GHZ_CHANNELS;
// =========================================================================
// Driver state
// =========================================================================
enum class IwxFwState : uint8_t {
Absent = 0, // no device found
Detected, // PCI device claimed, waiting for VFS (firmware file)
Booting, // firmware load in progress
Running, // operational firmware alive and initialized
Error, // fatal error; device stopped
RfKill, // radio disabled by hardware switch
};
struct IwxState {
// PCI location + MMIO
uint8_t Bus = 0, Dev = 0, Func = 0;
volatile uint8_t* Mmio = nullptr;
uint32_t HwRev = 0;
uint32_t HwRfId = 0;
IwxFwState State = IwxFwState::Absent;
bool MsixProgrammed = false;
int NicLockCount = 0;
// Interrupt-to-idle-loop deferral
volatile bool WorkPending = false;
IwxFwInfo Fw;
// Boot-time DMA
IwxDma CtxtInfo; // IwxContextInfoGen3
IwxDma PrphScratch; // IwxPrphScratch
IwxDma PrphInfo; // one page (incl. dummy TR/CR tails)
IwxDma ImlDma;
// LMAC/UMAC section copies: released once the firmware is alive.
IwxDma FwSecDma[IWX_MAX_FW_SECTIONS];
int FwSecDmaCount = 0;
// Paging sections: the firmware keeps reading these while it runs, so
// they are only released when the device is stopped.
IwxDma PagingDma[IWX_MAX_DRAM_ENTRY];
int PagingCount = 0;
IwxDma PnvmDma; // PNVM payload (or fragment table)
IwxDma PnvmSegDma[IWX_MAX_DRAM_ENTRY];
int PnvmSegs = 0;
uint32_t PnvmSize = 0;
uint32_t PnvmVersion = 0;
IwxRxRing RxQ;
IwxTxRing CmdQ; // queue 0: host commands
IwxTxRing MgmtQ; // queue 1: management frames (connect path)
// ALIVE / init-complete tracking (set from notification processing)
volatile bool AliveIntr = false;
volatile bool AliveOk = false;
volatile uint32_t InitComplete = 0; // bit0 INIT, bit1 PNVM
IwxSkuId SkuId = {};
// Firmware error-log pointers reported by ALIVE, and the last command
// we sent -- together these identify what the firmware choked on.
uint32_t UmacErrorTable = 0;
uint32_t LmacErrorTable = 0;
uint32_t LastCmdId = 0;
bool LtrEnabled = false; // PCIe LTR capability advertised
// Synchronous-command bookkeeping (commands are fully serialized)
kcp::Spinlock CmdLock; // serializes SendCmd callers
volatile bool CmdDone = false;
volatile bool CmdWantResp = false;
uint8_t CmdRespBuf[4096];
volatile uint32_t CmdRespLen = 0;
uint32_t CmdIdx = 0; // ring slot of in-flight command
// Reentrancy guard for ProcessEvents
volatile bool InProcessEvents = false;
IwxNvmData Nvm;
IwxChannel Channels[IWX_MAX_CHANNELS_TRACKED];
int ChannelCount = 0;
// Scan state
volatile bool ScanActive = false;
volatile bool ScanCompleted = false;
// Statistics/diagnostics
uint64_t RxPackets = 0;
uint64_t FwErrors = 0;
};
extern IwxState g_iwx;
// =========================================================================
// Transport (IwxTrans.cpp)
// =========================================================================
bool IwxProbe(const Pci::PciDevice& dev); // claim device, map BAR, MSI-X
bool IwxStartHw(); // prepare + reset + apm init
void IwxStopDevice();
bool IwxStartFirmware(); // context-info boot, wait ALIVE
bool IwxLoadPnvm(); // after ALIVE
uint32_t IwxRead32(uint32_t reg);
void IwxWrite32(uint32_t reg, uint32_t val);
void IwxWrite8(uint32_t reg, uint8_t val);
void IwxSetBits(uint32_t reg, uint32_t bits);
void IwxClearBits(uint32_t reg, uint32_t bits);
bool IwxNicLock();
void IwxNicUnlock();
uint32_t IwxReadPrph(uint32_t addr); // caller holds nic lock
void IwxWritePrph(uint32_t addr, uint32_t val);
uint32_t IwxReadUmacPrph(uint32_t addr);
void IwxWriteUmacPrph(uint32_t addr, uint32_t val);
bool IwxPollBit(uint32_t reg, uint32_t bits, uint32_t mask, int timeoutUs);
void IwxDelayUs(uint32_t us);
void IwxDelayMs(uint32_t ms);
// Host commands. Payload is copied; for WANT_RESP the response packet is
// copied into g_iwx.CmdRespBuf. Synchronous variants pump ProcessEvents.
struct IwxHostCmd {
uint32_t Id = 0; // opcode or IWX_WIDE_ID(group, opcode)
const void* Data = nullptr;
uint32_t Len = 0;
bool WantResp = false;
};
bool IwxSendCmd(IwxHostCmd& cmd); // sync, 1s timeout
bool IwxSendCmdPdu(uint32_t id, const void* data, uint32_t len);
bool IwxSendCmdStatus(uint32_t id, const void* data, uint32_t len,
uint32_t* statusOut);
// Poll interrupt causes + drain the RX/notification ring. Safe to call
// from any process/idle context; self-guarded against reentry.
void IwxProcessEvents();
// Look up the firmware-advertised version of a command/notification.
int IwxLookupCmdVer(uint8_t group, uint8_t cmd);
int IwxLookupNotifVer(uint8_t group, uint8_t cmd);
bool IwxCheckRfKill();
// TX queue management (used by connect path)
bool IwxEnableTxq(int staId, int qid, int tid);
// =========================================================================
// Firmware file parsing (IwxFw.cpp)
// =========================================================================
bool IwxReadFirmware(); // load + parse .ucode from VFS
void IwxFreeFirmware();
// Parse PNVM data (embedded TLV or .pnvm file) and stage DMA for our SKU.
bool IwxPnvmParse(const uint8_t* data, uint64_t len);
// =========================================================================
// MVM op-mode (IwxMvm.cpp)
// =========================================================================
bool IwxRunInitUcode(); // boot + NVM + init complete
bool IwxInitHw(); // full init: ant/bt/soc/ltr/scan cfg
bool IwxStartScan(const char* directSsid); // directSsid may be null
bool IwxAbortScan();
// Notification dispatch, called from RX processing for every fw packet.
void IwxHandleNotification(const IwxRxPacket* pkt, const uint8_t* rxBuf,
uint32_t bufLen);
// Scan results sink, implemented by Wifi.cpp: raw 802.11 beacon/probe-resp.
void WifiRxMgmtFrame(const uint8_t* frame, uint32_t len, uint8_t channel,
int8_t rssiDbm);
// =========================================================================
// Connect groundwork (IwxConnect.cpp) - UNTESTED scaffolding
// =========================================================================
bool IwxConnectStart(const uint8_t* bssid, uint8_t channel, bool is5GHz,
const char* ssid);
void IwxConnectAbort();
void IwxConnectRxMgmt(const uint8_t* frame, uint32_t len);
// Apply state changes the RX path queued (it cannot send commands itself).
void IwxConnectService();
int IwxConnectState();
}
+391
View File
@@ -0,0 +1,391 @@
/*
* IwxConnect.cpp
* Association groundwork: PHY/MAC context setup, station add, and the
* open-system authentication + association exchange.
*
* STATUS: this path is scaffolding. It builds the firmware contexts the
* same way iwlwifi does and drives the 802.11 state machine far enough to
* authenticate and associate with an open network, but it has never been
* exercised on hardware, and the WPA2 key exchange is deliberately not
* implemented (see WifiConnectSecurity notes in Wifi.cpp). Scanning is the
* supported operation today.
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "Iwx.hpp"
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
using namespace Kt;
namespace Drivers::Net::Wifi {
// Connection state machine.
enum class ConnState : int {
Idle = 0,
ContextsUp, // PHY/MAC/binding/STA programmed, ready to authenticate
Authenticating,
Authenticated,
Associating,
Associated,
Failed,
};
static ConnState g_state = ConnState::Idle;
static uint8_t g_bssid[6] = {};
static uint8_t g_channel = 0;
static bool g_is5GHz = false;
static char g_ssid[33] = {};
static uint8_t g_ssidLen = 0;
static uint16_t g_aid = 0;
static constexpr uint32_t MAC_ID = 0;
static constexpr uint32_t MAC_COLOR = 0;
static constexpr uint32_t PHY_ID = 0;
static constexpr uint32_t PHY_COLOR = 0;
static bool g_phyActive = false;
static bool g_macActive = false;
static bool g_bindingActive = false;
static bool g_staActive = false;
static bool g_mgmtQueueUp = false;
// Work discovered while parsing an inbound frame, applied later from the
// idle loop: RX processing must not send commands (it would re-enter the
// event pump that its own completion depends on).
static volatile bool g_postAssocPending = false;
static volatile bool g_teardownPending = false;
static uint8_t FwValidRxAntConn() {
uint8_t ant = (uint8_t)((g_iwx.Fw.PhyConfig & IWX_FW_PHY_CFG_RX_CHAIN)
>> IWX_FW_PHY_CFG_RX_CHAIN_POS);
if (g_iwx.Nvm.ValidRxAnt) ant &= g_iwx.Nvm.ValidRxAnt;
return ant;
}
// =========================================================================
// Firmware contexts
// =========================================================================
static bool PhyCtxtCmd(uint32_t action) {
IwxPhyContextCmd cmd = {};
cmd.id_and_color = IWX_FW_CMD_ID_AND_COLOR(PHY_ID, PHY_COLOR);
cmd.action = action;
cmd.lmac_id = (!g_is5GHz
|| !IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_CDB_SUPPORT))
? IWX_LMAC_24G_INDEX : IWX_LMAC_5G_INDEX;
cmd.ci.band = g_is5GHz ? IWX_PHY_BAND_5 : IWX_PHY_BAND_24;
cmd.ci.channel = g_channel;
cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE20;
cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
// From RLC_CONFIG v2 on, the chain configuration moved out of this
// command into its own RLC command.
if (IwxLookupCmdVer(IWX_DATA_PATH_GROUP, IWX_RLC_CONFIG_CMD) != 2) {
cmd.rxchain_info = (uint32_t)FwValidRxAntConn() << IWX_PHY_RX_CHAIN_VALID_POS;
cmd.rxchain_info |= 1u << IWX_PHY_RX_CHAIN_CNT_POS;
cmd.rxchain_info |= 1u << IWX_PHY_RX_CHAIN_MIMO_CNT_POS;
}
return IwxSendCmdPdu(IWX_PHY_CONTEXT_CMD, &cmd, sizeof(cmd));
}
static bool PhySendRlc() {
if (IwxLookupCmdVer(IWX_DATA_PATH_GROUP, IWX_RLC_CONFIG_CMD) != 2)
return true;
IwxRlcConfigCmd cmd = {};
cmd.phy_id = PHY_ID;
cmd.rlc.rx_chain_info = (uint32_t)FwValidRxAntConn() << IWX_PHY_RX_CHAIN_VALID_POS;
cmd.rlc.rx_chain_info |= 1u << IWX_PHY_RX_CHAIN_CNT_POS;
cmd.rlc.rx_chain_info |= 1u << IWX_PHY_RX_CHAIN_MIMO_CNT_POS;
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_RLC_CONFIG_CMD),
&cmd, sizeof(cmd));
}
static bool MacCtxtCmd(uint32_t action, bool assoc) {
IwxMacCtxCmd cmd = {};
cmd.id_and_color = IWX_FW_CMD_ID_AND_COLOR(MAC_ID, MAC_COLOR);
cmd.action = action;
cmd.mac_type = IWX_FW_MAC_TYPE_BSS_STA;
cmd.tsf_id = IWX_TSF_ID_A;
memcpy(cmd.node_addr, g_iwx.Nvm.HwAddr, 6);
memcpy(cmd.bssid_addr, g_bssid, 6);
// Basic rate masks: CCK 1/2/5.5/11 on 2.4 GHz, OFDM 6/12/24 everywhere.
// The firmware indexes these bitmaps against its own rate tables.
cmd.cck_rates = g_is5GHz ? 0 : 0x0f;
cmd.ofdm_rates = 0x15;
cmd.cck_short_preamble = 0;
cmd.short_slot = 0;
cmd.filter_flags = IWX_MAC_FILTER_ACCEPT_GRP | IWX_MAC_FILTER_IN_BEACON;
cmd.qos_flags = IWX_MAC_QOS_FLG_UPDATE_EDCA;
// Default EDCA parameters, one entry per access category.
for (uint32_t i = 0; i < IWX_AC_NUM; i++) {
cmd.ac[i].cw_min = 15;
cmd.ac[i].cw_max = 1023;
cmd.ac[i].aifsn = 2;
cmd.ac[i].fifos_mask = (uint8_t)(1 << i);
cmd.ac[i].edca_txop = 0;
}
cmd.sta.is_assoc = assoc ? 1 : 0;
cmd.sta.bi = 100;
cmd.sta.dtim_interval = 100 * 3;
cmd.sta.listen_interval = 10;
cmd.sta.assoc_id = g_aid;
return IwxSendCmdPdu(IWX_MAC_CONTEXT_CMD, &cmd, sizeof(cmd));
}
static bool BindingCmd(uint32_t action) {
IwxBindingCmd cmd = {};
cmd.id_and_color = IWX_FW_CMD_ID_AND_COLOR(PHY_ID, PHY_COLOR);
cmd.action = action;
cmd.phy = IWX_FW_CMD_ID_AND_COLOR(PHY_ID, PHY_COLOR);
cmd.macs[0] = IWX_FW_CMD_ID_AND_COLOR(MAC_ID, MAC_COLOR);
for (uint32_t i = 1; i < IWX_MAX_MACS_IN_BINDING; i++)
cmd.macs[i] = IWX_FW_CTXT_INVALID;
cmd.lmac_id = (!g_is5GHz
|| !IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_CDB_SUPPORT))
? IWX_LMAC_24G_INDEX : IWX_LMAC_5G_INDEX;
uint32_t status = 0;
if (!IwxSendCmdStatus(IWX_BINDING_CONTEXT_CMD, &cmd, sizeof(cmd), &status))
return false;
return status == 0;
}
static bool AddStaCmd(bool update) {
IwxAddStaCmd cmd = {};
cmd.add_modify = update ? 1 : 0;
cmd.mac_id_n_color = IWX_FW_CMD_ID_AND_COLOR(MAC_ID, MAC_COLOR);
cmd.sta_id = IWX_STATION_ID;
cmd.station_type = IWX_STA_TYPE_LINK;
memcpy(cmd.addr, g_bssid, 6);
cmd.tid_disable_tx = 0xffff; // aggregation disabled for now
uint32_t status = 0;
if (!IwxSendCmdStatus(IWX_ADD_STA, &cmd, sizeof(cmd), &status))
return false;
return (status & IWX_ADD_STA_STATUS_MASK) == IWX_ADD_STA_SUCCESS;
}
static bool RemoveStaCmd() {
struct { uint8_t sta_id; uint8_t reserved[3]; } __attribute__((packed)) cmd = {};
cmd.sta_id = IWX_STATION_ID;
return IwxSendCmdPdu(IWX_REMOVE_STA, &cmd, sizeof(cmd));
}
// Keep the firmware on our channel for the duration of the exchange.
static bool ScheduleSessionProtection(uint32_t durationTu) {
if (!IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_SESSION_PROT_CMD))
return true;
IwxSessionProtCmd cmd = {};
cmd.id_and_color = IWX_FW_CMD_ID_AND_COLOR(MAC_ID, MAC_COLOR);
cmd.action = IWX_FW_CTXT_ACTION_ADD;
cmd.conf_id = IWX_SESSION_PROTECT_CONF_ASSOC;
cmd.duration_tu = durationTu;
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_MAC_CONF_GROUP, IWX_SESSION_PROTECTION_CMD),
&cmd, sizeof(cmd));
}
// =========================================================================
// Teardown
// =========================================================================
static void TearDown() {
if (g_staActive) { RemoveStaCmd(); g_staActive = false; }
if (g_bindingActive) {
BindingCmd(IWX_FW_CTXT_ACTION_REMOVE);
g_bindingActive = false;
}
if (g_macActive) {
MacCtxtCmd(IWX_FW_CTXT_ACTION_REMOVE, false);
g_macActive = false;
}
if (g_phyActive) {
PhyCtxtCmd(IWX_FW_CTXT_ACTION_REMOVE);
g_phyActive = false;
}
g_mgmtQueueUp = false;
g_aid = 0;
}
// =========================================================================
// Public entry points
// =========================================================================
bool IwxConnectStart(const uint8_t* bssid, uint8_t channel, bool is5GHz,
const char* ssid) {
if (g_iwx.State != IwxFwState::Running) return false;
if (g_state != ConnState::Idle) IwxConnectAbort();
memcpy(g_bssid, bssid, 6);
g_channel = channel;
g_is5GHz = is5GHz;
g_aid = 0;
g_ssidLen = 0;
if (ssid) {
while (g_ssidLen < 32 && ssid[g_ssidLen]) {
g_ssid[g_ssidLen] = ssid[g_ssidLen];
g_ssidLen++;
}
}
g_ssid[g_ssidLen] = '\0';
if (g_iwx.ScanActive) IwxAbortScan();
if (!PhyCtxtCmd(IWX_FW_CTXT_ACTION_ADD)) {
KernelLogStream(ERROR, "WiFi") << "Could not add PHY context";
return false;
}
g_phyActive = true;
if (!PhySendRlc()) {
KernelLogStream(ERROR, "WiFi") << "Could not configure RLC for PHY";
TearDown();
return false;
}
if (!MacCtxtCmd(IWX_FW_CTXT_ACTION_ADD, false)) {
KernelLogStream(ERROR, "WiFi") << "Could not add MAC context";
TearDown();
return false;
}
g_macActive = true;
if (!BindingCmd(IWX_FW_CTXT_ACTION_ADD)) {
KernelLogStream(ERROR, "WiFi") << "Could not add binding";
TearDown();
return false;
}
g_bindingActive = true;
if (!AddStaCmd(false)) {
KernelLogStream(ERROR, "WiFi") << "Could not add station";
TearDown();
return false;
}
g_staActive = true;
// Non-QoS management frames go out on the MGMT TID/queue.
if (!IwxEnableTxq(IWX_STATION_ID, IWX_DQA_MGMT_QUEUE, IWX_MGMT_TID)) {
KernelLogStream(WARNING, "WiFi")
<< "Management TX queue unavailable; cannot transmit auth frames";
TearDown();
return false;
}
g_mgmtQueueUp = true;
// Beacon interval 100 TU * 9 is what iwlwifi reserves for the whole
// authenticate + associate exchange.
ScheduleSessionProtection(900);
g_state = ConnState::ContextsUp;
KernelLogStream(INFO, "WiFi")
<< "Firmware contexts up for BSSID " << base::hex
<< (uint64_t)g_bssid[0] << ":" << (uint64_t)g_bssid[1] << ":"
<< (uint64_t)g_bssid[2] << ":" << (uint64_t)g_bssid[3] << ":"
<< (uint64_t)g_bssid[4] << ":" << (uint64_t)g_bssid[5] << base::dec
<< " on channel " << (uint64_t)g_channel;
// Transmitting the authentication frame itself requires the TX data
// path (TFD assembly, rate selection, TX status handling), which this
// driver does not implement yet. Everything above is the firmware-side
// state the exchange needs; the 802.11 handshake is the remaining work.
KernelLogStream(WARNING, "WiFi")
<< "Authentication frame TX is not implemented; stopping after context setup";
return true;
}
void IwxConnectAbort() {
if (g_state == ConnState::Idle) return;
TearDown();
g_state = ConnState::Idle;
}
// Inbound management frames for the connection state machine. Beacons and
// probe responses are consumed by the scan path instead.
void IwxConnectRxMgmt(const uint8_t* frame, uint32_t len) {
if (g_state == ConnState::Idle || len < 24) return;
uint8_t subtype = (uint8_t)(frame[0] & 0xf0);
constexpr uint8_t SUBTYPE_AUTH = 0xb0;
constexpr uint8_t SUBTYPE_ASSOC_RESP = 0x10;
constexpr uint8_t SUBTYPE_DEAUTH = 0xc0;
constexpr uint8_t SUBTYPE_DISASSOC = 0xa0;
// Only frames from the BSS we are joining are interesting here.
for (int i = 0; i < 6; i++)
if (frame[10 + i] != g_bssid[i]) return;
switch (subtype) {
case SUBTYPE_AUTH: {
if (len < 30) return;
uint16_t status = (uint16_t)(frame[28] | (frame[29] << 8));
if (status == 0) {
g_state = ConnState::Authenticated;
KernelLogStream(OK, "WiFi") << "Authenticated";
} else {
g_state = ConnState::Failed;
KernelLogStream(WARNING, "WiFi")
<< "Authentication rejected, status " << (uint64_t)status;
}
break;
}
case SUBTYPE_ASSOC_RESP: {
if (len < 30) return;
uint16_t status = (uint16_t)(frame[26] | (frame[27] << 8));
if (status == 0) {
g_aid = (uint16_t)((frame[28] | (frame[29] << 8)) & 0x3fff);
g_state = ConnState::Associated;
// This runs inside RX processing, which is guarded against
// reentry; sending the context updates from here would
// deadlock their own completion wait. Defer to the idle
// loop (IwxConnectService).
g_postAssocPending = true;
KernelLogStream(OK, "WiFi") << "Associated, AID "
<< (uint64_t)g_aid;
} else {
g_state = ConnState::Failed;
KernelLogStream(WARNING, "WiFi")
<< "Association rejected, status " << (uint64_t)status;
}
break;
}
case SUBTYPE_DEAUTH:
case SUBTYPE_DISASSOC:
KernelLogStream(INFO, "WiFi") << "Link torn down by AP";
g_teardownPending = true; // see IwxConnectService
break;
default:
break;
}
}
// Apply work queued by the RX path. Called from the idle loop, where
// sending firmware commands (and pumping their completions) is safe.
void IwxConnectService() {
if (g_teardownPending) {
g_teardownPending = false;
TearDown();
g_state = ConnState::Idle;
return;
}
if (g_postAssocPending) {
g_postAssocPending = false;
MacCtxtCmd(IWX_FW_CTXT_ACTION_MODIFY, true);
AddStaCmd(true);
}
}
int IwxConnectState() {
return (int)g_state;
}
}
+437
View File
@@ -0,0 +1,437 @@
/*
* IwxFw.cpp
* Intel Wi-Fi firmware file handling: .ucode TLV parsing and PNVM staging.
*
* Files live on the ramdisk at 0:/os/firmware/intel/ (staged there by the
* userspace build, same as the Bluetooth .sfi images):
*
* iwlwifi-so-a0-gf-a0-<api>.ucode operational firmware (AX211 / RF GF)
* iwlwifi-so-a0-gf-a0.pnvm platform NVM (regulatory data)
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "Iwx.hpp"
#include <Fs/Vfs.hpp>
#include <Memory/Heap.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
using namespace Kt;
namespace Drivers::Net::Wifi {
static constexpr const char* FW_DIR = "0:/os/firmware/intel/";
// Firmware API revisions to try, newest first. Intel ships one file per
// API level and the driver simply uses the newest one present.
static constexpr int FW_API_TRY[] = { 89, 86, 83, 81, 79, 78, 77, 74, 73, 72 };
// =========================================================================
// Small helpers
// =========================================================================
static uint32_t Rd32(const uint8_t* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static uint16_t Rd16(const uint8_t* p) {
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static void SetBit(uint8_t* map, uint32_t bit, uint32_t mapBits) {
if (bit >= mapBits) return;
map[bit / 8] = (uint8_t)(map[bit / 8] | (1 << (bit % 8)));
}
static char* AppendStr(char* p, const char* s) {
while (*s) *p++ = *s++;
return p;
}
static char* AppendU32(char* p, uint32_t v) {
char tmp[12];
int n = 0;
if (v == 0) tmp[n++] = '0';
while (v) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
while (n) *p++ = tmp[--n];
return p;
}
// =========================================================================
// .ucode TLV parsing
// =========================================================================
static bool ParseUcode(uint8_t* raw, uint64_t size) {
IwxFwInfo& fw = g_iwx.Fw;
if (size < sizeof(IwxTlvUcodeHeader)) {
KernelLogStream(ERROR, "WiFi-FW") << "Firmware file too small";
return false;
}
auto* uhdr = (IwxTlvUcodeHeader*)raw;
if (uhdr->zero != 0 || uhdr->magic != IWX_TLV_UCODE_MAGIC) {
KernelLogStream(ERROR, "WiFi-FW") << "Not a TLV firmware image";
return false;
}
const uint8_t* data = raw + sizeof(IwxTlvUcodeHeader);
uint64_t len = size - sizeof(IwxTlvUcodeHeader);
// Default version string from the header; a FW_VERSION TLV overrides it.
char* vp = fw.Version;
vp = AppendU32(vp, IWX_UCODE_MAJOR(uhdr->ver));
*vp++ = '.';
vp = AppendU32(vp, IWX_UCODE_MINOR(uhdr->ver));
*vp++ = '.';
vp = AppendU32(vp, IWX_UCODE_API(uhdr->ver));
*vp = '\0';
while (len >= sizeof(IwxUcodeTlv)) {
uint32_t type = Rd32(data);
uint32_t tlvLen = Rd32(data + 4);
len -= sizeof(IwxUcodeTlv);
data += sizeof(IwxUcodeTlv);
if (tlvLen > len) {
KernelLogStream(ERROR, "WiFi-FW") << "Truncated firmware TLV";
return false;
}
switch (type) {
case IWX_UCODE_TLV_SEC_RT: {
// [devoff][payload]. Separator markers carry no payload.
if (tlvLen < 4) break;
if (fw.SectionCount >= IWX_MAX_FW_SECTIONS) {
KernelLogStream(ERROR, "WiFi-FW")
<< "Firmware has more sections than supported";
return false;
}
IwxFwSection& sec = fw.Sections[fw.SectionCount++];
sec.DevOff = Rd32(data);
sec.Data = data + 4;
sec.Len = tlvLen - 4;
break;
}
case IWX_UCODE_TLV_IML:
fw.Iml = data;
fw.ImlLen = tlvLen;
break;
case IWX_UCODE_TLV_PNVM_DATA:
fw.PnvmData = data;
fw.PnvmLen = tlvLen;
break;
case IWX_UCODE_TLV_PHY_SKU:
if (tlvLen >= 4) fw.PhyConfig = Rd32(data);
break;
case IWX_UCODE_TLV_N_SCAN_CHANNELS:
if (tlvLen >= 4) {
fw.NumScanChannels = Rd32(data);
if (fw.NumScanChannels > IWX_MAX_SCAN_CHANNELS)
fw.NumScanChannels = IWX_MAX_SCAN_CHANNELS;
}
break;
case IWX_UCODE_TLV_FW_VERSION:
if (tlvLen >= 12) {
char* p = fw.Version;
p = AppendU32(p, Rd32(data));
*p++ = '.';
p = AppendU32(p, Rd32(data + 4));
*p++ = '.';
p = AppendU32(p, Rd32(data + 8));
*p = '\0';
}
break;
case IWX_UCODE_TLV_API_CHANGES_SET: {
if (tlvLen < sizeof(IwxUcodeApiCapa)) break;
uint32_t idx = Rd32(data);
uint32_t flags = Rd32(data + 4);
for (int i = 0; i < 32; i++)
if (flags & (1u << i))
SetBit(fw.ApiFlags, i + 32 * idx, IWX_NUM_UCODE_TLV_API);
break;
}
case IWX_UCODE_TLV_ENABLED_CAPABILITIES: {
if (tlvLen < sizeof(IwxUcodeApiCapa)) break;
uint32_t idx = Rd32(data);
uint32_t flags = Rd32(data + 4);
for (int i = 0; i < 32; i++)
if (flags & (1u << i))
SetBit(fw.Capa, i + 32 * idx, IWX_NUM_UCODE_TLV_CAPA);
break;
}
case IWX_UCODE_TLV_CMD_VERSIONS: {
uint32_t n = tlvLen / sizeof(IwxFwCmdVersion);
if (n > IWX_MAX_FW_CMD_VERSIONS) n = IWX_MAX_FW_CMD_VERSIONS;
memcpy(fw.CmdVersions, data, n * sizeof(IwxFwCmdVersion));
fw.NumCmdVersions = (int)n;
break;
}
default:
// Debug/monitor/calibration TLVs are not used here.
break;
}
uint64_t adv = (tlvLen + 3) & ~3ull;
if (adv > len) break; // trailing padding
len -= adv;
data += adv;
}
if (fw.SectionCount == 0) {
KernelLogStream(ERROR, "WiFi-FW") << "Firmware image has no sections";
return false;
}
return true;
}
// =========================================================================
// PNVM parsing
// =========================================================================
// Fragmented PNVM: the firmware reads a table of segment addresses whose
// base is handed over in prph_scratch.pnvm_cfg. Non-fragmented firmware
// instead wants one flat buffer.
static bool PnvmSetup(uint8_t* const* segs, const uint32_t* sizes, int count) {
bool fragmented = IwxBitSet(g_iwx.Fw.Capa,
IWX_UCODE_TLV_CAPA_FRAGMENTED_PNVM_IMG);
if (fragmented) {
if (!IwxDmaAlloc(g_iwx.PnvmDma, sizeof(IwxPnvmInfoDram))) return false;
auto* info = (IwxPnvmInfoDram*)g_iwx.PnvmDma.Virt;
for (int i = 0; i < count && i < (int)IWX_MAX_DRAM_ENTRY; i++) {
if (!IwxDmaAlloc(g_iwx.PnvmSegDma[i], sizes[i])) return false;
memcpy(g_iwx.PnvmSegDma[i].Virt, segs[i], sizes[i]);
info->pnvm_img[i] = g_iwx.PnvmSegDma[i].Phys;
g_iwx.PnvmSize += sizes[i];
g_iwx.PnvmSegs = i + 1;
}
return g_iwx.PnvmSegs > 0;
}
uint32_t total = 0;
for (int i = 0; i < count; i++) total += sizes[i];
if (total == 0) return false;
if (!IwxDmaAlloc(g_iwx.PnvmDma, total)) return false;
uint8_t* dst = (uint8_t*)g_iwx.PnvmDma.Virt;
for (int i = 0; i < count; i++) {
memcpy(dst, segs[i], sizes[i]);
dst += sizes[i];
}
g_iwx.PnvmSize = total;
return true;
}
// Parse the section that follows a matching PNVM_SKU entry: verify the
// hardware type, then collect the SEC_RT payloads into DMA.
static bool PnvmHandleSection(const uint8_t* data, uint64_t len) {
uint8_t* segs[IWX_MAX_DRAM_ENTRY];
uint32_t sizes[IWX_MAX_DRAM_ENTRY];
int count = 0;
bool hwMatch = false;
uint32_t sha1 = 0;
uint32_t total = 0;
bool ok = false;
uint16_t ourMac = (uint16_t)IWX_CSR_HW_REV_TYPE(g_iwx.HwRev);
uint16_t ourRf = (uint16_t)IWX_CSR_HW_RFID_TYPE(g_iwx.HwRfId);
while (len >= sizeof(IwxUcodeTlv)) {
uint32_t type = Rd32(data);
uint32_t tlvLen = Rd32(data + 4);
len -= sizeof(IwxUcodeTlv);
data += sizeof(IwxUcodeTlv);
if (tlvLen > len) break;
if (type == IWX_UCODE_TLV_PNVM_VERSION) {
if (tlvLen >= 4) sha1 = Rd32(data);
} else if (type == IWX_UCODE_TLV_HW_TYPE) {
if (tlvLen >= 4 && !hwMatch) {
uint16_t macType = Rd16(data);
uint16_t rfId = Rd16(data + 2);
if (macType == ourMac && rfId == ourRf) hwMatch = true;
}
} else if (type == IWX_UCODE_TLV_SEC_RT) {
// struct iwx_pnvm_section { uint32_t offset; uint8_t data[]; }
if (tlvLen <= 4) { /* nothing to copy */ }
else if (Rd32(data) == 0xddddeeee) { /* deprecated separator */ }
else if (count < (int)IWX_MAX_DRAM_ENTRY) {
uint32_t dataLen = tlvLen - 4;
auto* buf = (uint8_t*)Memory::g_heap->Request(dataLen);
if (!buf) goto out;
memcpy(buf, data + 4, dataLen);
segs[count] = buf;
sizes[count] = dataLen;
count++;
total += dataLen;
}
} else if (type == IWX_UCODE_TLV_PNVM_SKU) {
break; // next SKU section starts here
}
uint64_t adv = (tlvLen + 3) & ~3ull;
if (adv > len) break;
len -= adv;
data += adv;
}
if (!hwMatch || total == 0) goto out;
if (!PnvmSetup(segs, sizes, count)) {
KernelLogStream(ERROR, "WiFi-FW") << "Could not stage PNVM in DMA memory";
goto out;
}
g_iwx.PnvmVersion = sha1;
ok = true;
out:
for (int i = 0; i < count; i++) Memory::g_heap->Free(segs[i]);
return ok;
}
bool IwxPnvmParse(const uint8_t* data, uint64_t len) {
while (len >= sizeof(IwxUcodeTlv)) {
uint32_t type = Rd32(data);
uint32_t tlvLen = Rd32(data + 4);
uint64_t adv = (tlvLen + 3) & ~3ull;
if (tlvLen > len - sizeof(IwxUcodeTlv)) return false;
if (type == IWX_UCODE_TLV_PNVM_SKU && tlvLen >= 12) {
const uint8_t* sku = data + sizeof(IwxUcodeTlv);
uint32_t s0 = Rd32(sku), s1 = Rd32(sku + 4), s2 = Rd32(sku + 8);
const uint8_t* next = data + sizeof(IwxUcodeTlv) + adv;
uint64_t remain = len - sizeof(IwxUcodeTlv) - adv;
if (s0 == g_iwx.SkuId.data[0] && s1 == g_iwx.SkuId.data[1]
&& s2 == g_iwx.SkuId.data[2]
&& PnvmHandleSection(next, remain))
return true;
data = next;
len = remain;
} else {
data += sizeof(IwxUcodeTlv) + adv;
len -= sizeof(IwxUcodeTlv) + adv;
}
}
return false;
}
// =========================================================================
// File loading
// =========================================================================
static uint8_t* LoadFile(const char* path, uint64_t* outSize) {
Fs::Vfs::BackendFile file;
if (Fs::Vfs::OpenBackendFile(path, file) < 0) return nullptr;
uint64_t size = Fs::Vfs::GetBackendFileSize(file);
if (size == 0 || size > 8u * 1024 * 1024) {
Fs::Vfs::CloseBackendFile(file);
return nullptr;
}
auto* buf = (uint8_t*)Memory::g_heap->Request(size);
if (!buf) {
Fs::Vfs::CloseBackendFile(file);
return nullptr;
}
Fs::Vfs::ReadBackendFile(file, buf, 0, size);
Fs::Vfs::CloseBackendFile(file);
asm volatile("" ::: "memory");
*outSize = size;
return buf;
}
void IwxFreeFirmware() {
if (g_iwx.Fw.Raw) {
Memory::g_heap->Free(g_iwx.Fw.Raw);
g_iwx.Fw.Raw = nullptr;
}
// Only the separately-loaded .pnvm file is ours to free; an embedded
// PNVM TLV points into Raw and has just gone away with it.
if (g_iwx.Fw.PnvmOwned) {
Memory::g_heap->Free(g_iwx.Fw.PnvmOwned);
g_iwx.Fw.PnvmOwned = nullptr;
}
g_iwx.Fw.RawSize = 0;
g_iwx.Fw.SectionCount = 0;
g_iwx.Fw.Iml = nullptr;
g_iwx.Fw.ImlLen = 0;
g_iwx.Fw.PnvmData = nullptr;
g_iwx.Fw.PnvmLen = 0;
g_iwx.Fw.NumCmdVersions = 0;
}
bool IwxReadFirmware() {
if (g_iwx.Fw.Raw) return true; // already loaded and parsed
char path[96];
uint8_t* raw = nullptr;
uint64_t size = 0;
for (int api : FW_API_TRY) {
char* p = AppendStr(path, FW_DIR);
p = AppendStr(p, "iwlwifi-so-a0-gf-a0-");
p = AppendU32(p, (uint32_t)api);
p = AppendStr(p, ".ucode");
*p = '\0';
raw = LoadFile(path, &size);
if (raw) {
KernelLogStream(INFO, "WiFi-FW") << "Loading " << path
<< " (" << size << " bytes)";
break;
}
}
if (!raw) {
KernelLogStream(ERROR, "WiFi-FW")
<< "No iwlwifi-so-a0-gf-a0-*.ucode found under " << FW_DIR;
return false;
}
g_iwx.Fw.Raw = raw;
g_iwx.Fw.RawSize = size;
if (!ParseUcode(raw, size)) {
IwxFreeFirmware();
return false;
}
KernelLogStream(OK, "WiFi-FW") << "Firmware " << g_iwx.Fw.Version
<< ": " << (uint64_t)g_iwx.Fw.SectionCount << " sections, IML "
<< (uint64_t)g_iwx.Fw.ImlLen << " bytes, "
<< (uint64_t)g_iwx.Fw.NumCmdVersions << " command versions";
// The PNVM is usually a separate file; when the .ucode carries an
// embedded copy, IwxLoadPnvm() prefers that and this read is skipped.
if (!g_iwx.Fw.PnvmData) {
char* p = AppendStr(path, FW_DIR);
p = AppendStr(p, "iwlwifi-so-a0-gf-a0.pnvm");
*p = '\0';
uint64_t pnvmSize = 0;
uint8_t* pnvm = LoadFile(path, &pnvmSize);
if (pnvm) {
// Keep the buffer alive for the driver's lifetime: PnvmParse
// runs later, after ALIVE has reported the SKU id.
g_iwx.Fw.PnvmData = pnvm;
g_iwx.Fw.PnvmOwned = pnvm;
g_iwx.Fw.PnvmLen = (uint32_t)pnvmSize;
KernelLogStream(INFO, "WiFi-FW") << "Loaded PNVM ("
<< pnvmSize << " bytes)";
} else {
KernelLogStream(WARNING, "WiFi-FW")
<< "No PNVM file; firmware will use built-in regulatory defaults";
}
}
return true;
}
}
+684
View File
@@ -0,0 +1,684 @@
/*
* IwxMvm.cpp
* Intel Wi-Fi "MVM" op-mode: post-ALIVE firmware configuration, NVM/channel
* map retrieval, and UMAC scanning.
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "Iwx.hpp"
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
using namespace Kt;
namespace Drivers::Net::Wifi {
// IEEE channel numbers in NVM channel-profile order (AX210 with UHB
// support uses the 6 GHz-extended table; we track the 2.4/5 GHz prefix,
// which is identical in both).
static constexpr uint8_t NVM_CHANNELS[] = {
// 2.4 GHz
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
// 5 GHz
36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92,
96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
149, 153, 157, 161, 165, 169, 173, 177, 181
};
static constexpr int NVM_CHANNEL_COUNT =
(int)(sizeof(NVM_CHANNELS) / sizeof(NVM_CHANNELS[0]));
// Supported rates in 500 kbps units.
static constexpr uint8_t RATES_11G[] = { 2, 4, 11, 22, 12, 18, 24, 36 };
static constexpr uint8_t RATES_11G_EXT[] = { 48, 72, 96, 108 };
static constexpr uint8_t RATES_11A[] = { 12, 18, 24, 36, 48, 72, 96, 108 };
// Directed-scan SSID staged by IwxStartScan for the next command build.
static char g_directSsid[33] = {};
static uint8_t g_directSsidLen = 0;
static uint8_t FwValidTxAnt() {
uint8_t ant = (uint8_t)((g_iwx.Fw.PhyConfig & IWX_FW_PHY_CFG_TX_CHAIN)
>> IWX_FW_PHY_CFG_TX_CHAIN_POS);
if (g_iwx.Nvm.ValidTxAnt) ant &= g_iwx.Nvm.ValidTxAnt;
return ant;
}
static uint8_t FwValidRxAnt() {
uint8_t ant = (uint8_t)((g_iwx.Fw.PhyConfig & IWX_FW_PHY_CFG_RX_CHAIN)
>> IWX_FW_PHY_CFG_RX_CHAIN_POS);
if (g_iwx.Nvm.ValidRxAnt) ant &= g_iwx.Nvm.ValidRxAnt;
return ant;
}
// =========================================================================
// Channel map
// =========================================================================
static void IwxInitChannelMap(const uint16_t* profileV3,
const uint32_t* profileV4, int nProfile) {
g_iwx.ChannelCount = 0;
for (int i = 0; i < NVM_CHANNEL_COUNT && i < nProfile
&& g_iwx.ChannelCount < IWX_MAX_CHANNELS_TRACKED; i++) {
uint32_t flags = profileV4 ? profileV4[i] : (uint32_t)profileV3[i];
bool is5 = i >= (int)IWX_NUM_2GHZ_CHANNELS;
if (is5 && !g_iwx.Nvm.Sku52GHz) continue;
if (!(flags & IWX_NVM_CHANNEL_VALID)) continue;
IwxChannel& ch = g_iwx.Channels[g_iwx.ChannelCount++];
ch.ChannelNum = NVM_CHANNELS[i];
ch.Is5GHz = is5;
ch.Valid = true;
ch.ActiveAllowed = (flags & IWX_NVM_CHANNEL_ACTIVE) != 0;
}
KernelLogStream(INFO, "WiFi") << "Regulatory: " << (uint64_t)g_iwx.ChannelCount
<< " usable channels";
}
// =========================================================================
// NVM
// =========================================================================
static void SetMacAddrFromCsr() {
if (!IwxNicLock()) return;
uint32_t a0 = IwxRead32(IWX_CSR_MAC_ADDR0_STRAP);
uint32_t a1 = IwxRead32(IWX_CSR_MAC_ADDR1_STRAP);
// The hardware stores the address byte-swapped within each dword.
auto flip = [](uint32_t m0, uint32_t m1, uint8_t* dst) {
const uint8_t* p = (const uint8_t*)&m0;
dst[0] = p[3]; dst[1] = p[2]; dst[2] = p[1]; dst[3] = p[0];
p = (const uint8_t*)&m1;
dst[4] = p[1]; dst[5] = p[0];
};
flip(a0, a1, g_iwx.Nvm.HwAddr);
auto valid = [](const uint8_t* a) {
static const uint8_t reserved[6] = { 0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00 };
bool allZero = true, allOnes = true, isReserved = true;
for (int i = 0; i < 6; i++) {
if (a[i] != 0) allZero = false;
if (a[i] != 0xff) allOnes = false;
if (a[i] != reserved[i]) isReserved = false;
}
return !allZero && !allOnes && !isReserved && !(a[0] & 1);
};
// The OEM strap wins when fused; otherwise fall back to OTP.
if (!valid(g_iwx.Nvm.HwAddr)) {
a0 = IwxRead32(IWX_CSR_MAC_ADDR0_OTP);
a1 = IwxRead32(IWX_CSR_MAC_ADDR1_OTP);
flip(a0, a1, g_iwx.Nvm.HwAddr);
}
IwxNicUnlock();
}
static bool IwxNvmGet() {
IwxNvmGetInfo cmd = {};
IwxHostCmd hcmd;
hcmd.Id = IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP, IWX_NVM_GET_INFO);
hcmd.Data = &cmd;
hcmd.Len = sizeof(cmd);
hcmd.WantResp = true;
if (!IwxSendCmd(hcmd)) return false;
bool v4 = IwxBitSet(g_iwx.Fw.ApiFlags, IWX_UCODE_TLV_API_REGULATORY_NVM_INFO);
uint32_t want = v4 ? sizeof(IwxNvmGetInfoRsp) : sizeof(IwxNvmGetInfoRspV3);
if (g_iwx.CmdRespLen < sizeof(IwxRxPacket) + want) {
KernelLogStream(ERROR, "WiFi") << "Short NVM_GET_INFO response ("
<< (uint64_t)g_iwx.CmdRespLen << " bytes)";
return false;
}
auto* pkt = (IwxRxPacket*)g_iwx.CmdRespBuf;
auto* rsp = (IwxNvmGetInfoRsp*)pkt->data;
SetMacAddrFromCsr();
g_iwx.Nvm.NvmVersion = rsp->general.nvm_version;
uint32_t mf = rsp->mac_sku.mac_sku_flags;
g_iwx.Nvm.Sku24GHz = (mf & IWX_NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED) != 0;
g_iwx.Nvm.Sku52GHz = (mf & IWX_NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED) != 0;
g_iwx.Nvm.Sku11n = (mf & IWX_NVM_MAC_SKU_FLAGS_802_11N_ENABLED) != 0;
g_iwx.Nvm.Sku11ac = (mf & IWX_NVM_MAC_SKU_FLAGS_802_11AC_ENABLED) != 0;
g_iwx.Nvm.Sku11ax = (mf & IWX_NVM_MAC_SKU_FLAGS_802_11AX_ENABLED) != 0;
g_iwx.Nvm.ValidTxAnt = (uint8_t)rsp->phy_sku.tx_chains;
g_iwx.Nvm.ValidRxAnt = (uint8_t)rsp->phy_sku.rx_chains;
if (v4) {
g_iwx.Nvm.LarEnabled = rsp->regulatory.lar_enabled != 0
&& IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_LAR_SUPPORT);
IwxInitChannelMap(nullptr, rsp->regulatory.channel_profile,
IWX_NUM_CHANNELS);
} else {
auto* v3 = (IwxNvmGetInfoRspV3*)pkt->data;
g_iwx.Nvm.LarEnabled = v3->regulatory.lar_enabled != 0
&& IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_LAR_SUPPORT);
IwxInitChannelMap(v3->regulatory.channel_profile, nullptr,
IWX_NUM_CHANNELS_V1);
}
const uint8_t* m = g_iwx.Nvm.HwAddr;
KernelLogStream(OK, "WiFi") << "MAC address " << base::hex
<< (uint64_t)m[0] << ":" << (uint64_t)m[1] << ":" << (uint64_t)m[2]
<< ":" << (uint64_t)m[3] << ":" << (uint64_t)m[4] << ":"
<< (uint64_t)m[5] << base::dec
<< " (bands:" << (g_iwx.Nvm.Sku24GHz ? " 2.4GHz" : "")
<< (g_iwx.Nvm.Sku52GHz ? " 5GHz" : "") << ")";
return true;
}
// =========================================================================
// Init sequence
// =========================================================================
static bool SendTxAntCfg() {
IwxTxAntCfgCmd cmd = {};
cmd.valid = FwValidTxAnt();
return IwxSendCmdPdu(IWX_TX_ANT_CONFIGURATION_CMD, &cmd, sizeof(cmd));
}
static bool SendBtInitConf() {
IwxBtCoexCmd cmd = {};
cmd.mode = IWX_BT_COEX_WIFI;
cmd.enabled_modules = 0;
return IwxSendCmdPdu(IWX_BT_CONFIG, &cmd, sizeof(cmd));
}
static bool SendSocConf() {
IwxSocConfigurationCmd cmd = {};
// The AX211 here is an integrated (CNVi) part with a low-latency
// crystal, matching iwlwifi's "so with low latency xtal" profile.
uint32_t flags = IWX_SOC_FLAGS_LTR_APPLY_DELAY_2500 & 0xc;
int scanVer = IwxLookupCmdVer(IWX_LONG_GROUP, IWX_SCAN_REQ_UMAC);
if (scanVer >= 2) flags |= IWX_SOC_CONFIG_CMD_FLAGS_LOW_LATENCY;
cmd.flags = flags;
cmd.latency = 12000;
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_SYSTEM_GROUP, IWX_SOC_CONFIGURATION_CMD),
&cmd, sizeof(cmd));
}
static bool SendLtrConfig() {
// Only meaningful when the PCIe link advertises LTR; upstream skips
// the command entirely otherwise.
if (!g_iwx.LtrEnabled) return true;
IwxLtrConfigCmd cmd = {};
cmd.flags = IWX_LTR_CFG_FLAG_FEATURE_ENABLE;
return IwxSendCmdPdu(IWX_LTR_CONFIG, &cmd, sizeof(cmd));
}
static bool SendTempReportThs() {
IwxTempReportThsCmd cmd = {};
return IwxSendCmdPdu(
IWX_WIDE_ID(IWX_PHY_OPS_GROUP, IWX_TEMP_REPORTING_THRESHOLDS_CMD),
&cmd, sizeof(cmd));
}
static bool DisableBeaconFilter() {
IwxBeaconFilterCmd cmd = {};
return IwxSendCmdPdu(IWX_REPLY_BEACON_FILTERING_CMD, &cmd, sizeof(cmd));
}
static bool SendUpdateMcc(const char* alpha2) {
IwxMccUpdateCmd cmd = {};
cmd.mcc = (uint16_t)((alpha2[0] << 8) | alpha2[1]);
if (IwxBitSet(g_iwx.Fw.ApiFlags, IWX_UCODE_TLV_API_WIFI_MCC_UPDATE)
|| IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_LAR_MULTI_MCC))
cmd.source_id = IWX_MCC_SOURCE_GET_CURRENT;
else
cmd.source_id = IWX_MCC_SOURCE_OLD_FW;
IwxHostCmd hcmd;
hcmd.Id = IWX_MCC_UPDATE_CMD;
hcmd.Data = &cmd;
hcmd.Len = sizeof(cmd);
hcmd.WantResp = true;
if (!IwxSendCmd(hcmd)) return false;
if (g_iwx.CmdRespLen < sizeof(IwxRxPacket) + sizeof(IwxMccUpdateRespV4))
return false;
auto* pkt = (IwxRxPacket*)g_iwx.CmdRespBuf;
auto* rsp = (IwxMccUpdateRespV4*)pkt->data;
// Refresh the channel map from the regulatory profile the firmware
// just applied; the response is variable length, so bound the channel
// count by what actually arrived.
uint32_t payload = IwxRxPacketPayloadLen(pkt);
uint32_t maxCh = (payload - sizeof(IwxMccUpdateRespV4)) / sizeof(uint32_t);
uint32_t n = rsp->n_channels < maxCh ? rsp->n_channels : maxCh;
if (n > 0) IwxInitChannelMap(nullptr, rsp->channels, (int)n);
return true;
}
// Reduced scan configuration (SCAN_CFG_CMD v5+): only antenna masks.
static bool ConfigUmacScan() {
if (!IwxBitSet(g_iwx.Fw.ApiFlags, IWX_UCODE_TLV_API_REDUCED_SCAN_CONFIG)) {
KernelLogStream(WARNING, "WiFi")
<< "Firmware lacks reduced scan config support";
return false;
}
IwxScanConfig cfg = {};
int cmdVer = IwxLookupCmdVer(IWX_LONG_GROUP, IWX_SCAN_CFG_CMD);
if (cmdVer < 5) cfg.bcast_sta_id = 0xff; // deprecated from v5 on
cfg.tx_chains = FwValidTxAnt();
cfg.rx_chains = FwValidRxAnt();
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_LONG_GROUP, IWX_SCAN_CFG_CMD),
&cfg, sizeof(cfg));
}
bool IwxRunInitUcode() {
g_iwx.InitComplete = 0;
if (!IwxStartFirmware()) return false;
if (!IwxLoadPnvm()) {
// Not fatal: without PNVM the firmware falls back to conservative
// built-in regulatory limits, which still allows scanning.
KernelLogStream(WARNING, "WiFi") << "Continuing without PNVM data";
}
IwxInitExtendedCfgCmd initCfg = {};
initCfg.init_flags = IWX_INIT_NVM;
if (!IwxSendCmdPdu(IWX_WIDE_ID(IWX_SYSTEM_GROUP, IWX_INIT_EXTENDED_CFG_CMD),
&initCfg, sizeof(initCfg))) {
KernelLogStream(ERROR, "WiFi") << "INIT_EXTENDED_CFG failed";
return false;
}
IwxNvmAccessCompleteCmd nvmDone = {};
if (!IwxSendCmdPdu(IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
IWX_NVM_ACCESS_COMPLETE),
&nvmDone, sizeof(nvmDone))) {
KernelLogStream(ERROR, "WiFi") << "NVM_ACCESS_COMPLETE failed";
return false;
}
uint64_t start = Timekeeping::GetMilliseconds();
while (!(g_iwx.InitComplete & 0x1)
&& Timekeeping::GetMilliseconds() - start < 2000) {
IwxProcessEvents();
IwxDelayUs(200);
}
if (!(g_iwx.InitComplete & 0x1)) {
KernelLogStream(ERROR, "WiFi") << "No INIT_COMPLETE notification";
return false;
}
if (!IwxNvmGet()) {
KernelLogStream(ERROR, "WiFi") << "Failed to read NVM";
return false;
}
return true;
}
// Name each configuration step in the log so a firmware assert can be
// attributed to the command that preceded it without a rebuild.
static void InitStep(const char* what) {
KernelLogStream(INFO, "WiFi") << " init: " << what;
}
bool IwxInitHw() {
InitStep("tx antenna config");
if (!SendTxAntCfg()) {
KernelLogStream(ERROR, "WiFi") << "TX antenna config failed";
return false;
}
InitStep("bt coex");
if (!SendBtInitConf())
KernelLogStream(WARNING, "WiFi") << "BT coex config failed";
InitStep("soc config");
if (!SendSocConf())
KernelLogStream(WARNING, "WiFi") << "SoC config failed";
InitStep("pcie ltr");
if (!SendLtrConfig())
KernelLogStream(WARNING, "WiFi") << "PCIe LTR config failed";
InitStep("temp thresholds");
if (IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_CT_KILL_BY_FW)) {
if (!SendTempReportThs())
KernelLogStream(WARNING, "WiFi") << "Temperature threshold config failed";
}
InitStep("regulatory (mcc)");
if (g_iwx.Nvm.LarEnabled) {
// "ZZ" selects the NVM's own default regulatory profile.
if (!SendUpdateMcc("ZZ"))
KernelLogStream(WARNING, "WiFi") << "Regulatory (MCC) update failed";
}
InitStep("scan config");
if (!ConfigUmacScan()) {
KernelLogStream(ERROR, "WiFi") << "Scan configuration failed";
return false;
}
InitStep("beacon filter");
if (!DisableBeaconFilter())
KernelLogStream(WARNING, "WiFi") << "Could not disable beacon filter";
return true;
}
// =========================================================================
// Probe request template
// =========================================================================
static uint8_t* AddRates(uint8_t* frm, const uint8_t* rates, int n, uint8_t id) {
*frm++ = id;
*frm++ = (uint8_t)n;
for (int i = 0; i < n; i++) *frm++ = rates[i];
return frm;
}
// Build the probe-request template the firmware transmits during active
// scanning. The firmware inserts the SSID itself (directed scan) and fills
// in duration/sequence, so the SSID element is left empty here.
static void FillProbeReq(IwxScanProbeReq* preq) {
memset(preq, 0, sizeof(*preq));
uint8_t* buf = preq->buf;
uint8_t* frm = buf;
// 802.11 management header: probe request, to broadcast.
*frm++ = 0x40; // frame control: mgmt / probe request
*frm++ = 0x00;
*frm++ = 0x00; *frm++ = 0x00; // duration (hw)
for (int i = 0; i < 6; i++) *frm++ = 0xff; // addr1 broadcast
for (int i = 0; i < 6; i++) *frm++ = g_iwx.Nvm.HwAddr[i]; // addr2
for (int i = 0; i < 6; i++) *frm++ = 0xff; // addr3 broadcast
*frm++ = 0x00; *frm++ = 0x00; // seq ctl (hw)
// Empty SSID element; hardware substitutes the directed SSID.
*frm++ = 0x00; // element id: SSID
*frm++ = 0x00; // length 0
preq->mac_header.offset = 0;
preq->mac_header.len = (uint16_t)(frm - buf);
// 2.4 GHz band IEs: supported + extended rates, DS parameter set.
uint8_t* pos = frm;
preq->band_data[0].offset = (uint16_t)(frm - buf);
frm = AddRates(frm, RATES_11G, (int)sizeof(RATES_11G), 0x01);
frm = AddRates(frm, RATES_11G_EXT, (int)sizeof(RATES_11G_EXT), 0x32);
if (IwxBitSet(g_iwx.Fw.Capa, IWX_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT)) {
*frm++ = 0x03; // element id: DS parameter set
*frm++ = 0x01;
*frm++ = 0x00; // channel filled in by firmware
}
preq->band_data[0].len = (uint16_t)(frm - pos);
// 5 GHz band IEs.
if (g_iwx.Nvm.Sku52GHz) {
pos = frm;
preq->band_data[1].offset = (uint16_t)(frm - buf);
frm = AddRates(frm, RATES_11A, (int)sizeof(RATES_11A), 0x01);
preq->band_data[1].len = (uint16_t)(frm - pos);
}
// Common (both bands) trailer: nothing extra is advertised, HT/VHT
// capabilities are only needed once we associate.
preq->common_data.offset = (uint16_t)(frm - buf);
preq->common_data.len = 0;
}
// =========================================================================
// Scan
// =========================================================================
static uint16_t ScanFlagsV2(bool haveSsid) {
uint16_t flags = IWX_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL
| IWX_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE
| IWX_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL;
// Without a target SSID there is nothing to put in a probe request, so
// a passive sweep (listening for beacons) is what the firmware runs.
if (!haveSsid) flags |= IWX_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE;
return flags;
}
static void FillGeneralParams(IwxScanGeneralParamsV10* gp, uint16_t flags) {
gp->flags = flags;
gp->scan_start_mac_id = 0;
gp->adwell_default_social_chn = IWX_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL;
gp->adwell_default_2g = IWX_SCAN_ADWELL_DEFAULT_LB_N_APS;
gp->adwell_default_5g = IWX_SCAN_ADWELL_DEFAULT_HB_N_APS;
gp->adwell_max_budget = IWX_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN;
gp->scan_priority = IWX_SCAN_PRIORITY_EXT_6;
gp->max_out_of_time[IWX_SCAN_LB_LMAC_IDX] = 0;
gp->suspend_time[IWX_SCAN_LB_LMAC_IDX] = 0;
gp->max_out_of_time[IWX_SCAN_HB_LMAC_IDX] = 0;
gp->suspend_time[IWX_SCAN_HB_LMAC_IDX] = 0;
gp->active_dwell[IWX_SCAN_LB_LMAC_IDX] = IWX_SCAN_DWELL_ACTIVE;
gp->passive_dwell[IWX_SCAN_LB_LMAC_IDX] = IWX_SCAN_DWELL_PASSIVE;
gp->active_dwell[IWX_SCAN_HB_LMAC_IDX] = IWX_SCAN_DWELL_ACTIVE;
gp->passive_dwell[IWX_SCAN_HB_LMAC_IDX] = IWX_SCAN_DWELL_PASSIVE;
}
static uint8_t FillChannelsV5(IwxScanChannelCfgUmacV5* chans, uint32_t cfgFlags) {
uint8_t n = 0;
for (int i = 0; i < g_iwx.ChannelCount
&& n < g_iwx.Fw.NumScanChannels
&& n < IWX_MAX_SCAN_CHANNELS; i++) {
const IwxChannel& ch = g_iwx.Channels[i];
if (!ch.Valid) continue;
chans[n].channel_num = ch.ChannelNum;
chans[n].psd_20 = 0x80; // -128: "unknown" power spectral density
chans[n].iter_count = 1;
chans[n].iter_interval = 0;
uint32_t band = ch.Is5GHz ? IWX_PHY_BAND_5 : IWX_PHY_BAND_24;
chans[n].flags = cfgFlags | (band << IWX_CHAN_CFG_FLAGS_BAND_POS);
n++;
}
return n;
}
static uint8_t FillChannels(IwxScanChannelCfgUmac* chans, uint32_t cfgFlags) {
uint8_t n = 0;
for (int i = 0; i < g_iwx.ChannelCount
&& n < g_iwx.Fw.NumScanChannels
&& n < IWX_MAX_SCAN_CHANNELS; i++) {
const IwxChannel& ch = g_iwx.Channels[i];
if (!ch.Valid) continue;
chans[n].channel_num = ch.ChannelNum;
chans[n].band = ch.Is5GHz ? IWX_PHY_BAND_5 : IWX_PHY_BAND_24;
chans[n].iter_count = 1;
chans[n].iter_interval = 0;
chans[n].flags = cfgFlags;
n++;
}
return n;
}
// Build and submit the scan request. The command is ~2 KB, well past the
// per-slot command area, so it is staged through the command ring's bounce
// page by IwxSendCmd.
static bool UmacScanV17() {
auto* cmd = (IwxScanReqUmacV17*)Memory::g_heap->Request(sizeof(IwxScanReqUmacV17));
if (!cmd) return false;
memset(cmd, 0, sizeof(*cmd));
cmd->ooc_priority = IWX_SCAN_PRIORITY_EXT_6;
cmd->uid = 0;
uint32_t bitmapSsid = 0;
FillGeneralParams(&cmd->general_params, ScanFlagsV2(g_directSsidLen != 0));
cmd->periodic_params.schedule[0].interval = 0;
cmd->periodic_params.schedule[0].iter_count = 1;
FillProbeReq(&cmd->probe_params.preq);
if (g_directSsidLen) {
cmd->probe_params.direct_scan[0].id = 0x00; // SSID element
cmd->probe_params.direct_scan[0].len = g_directSsidLen;
memcpy(cmd->probe_params.direct_scan[0].ssid, g_directSsid,
g_directSsidLen);
bitmapSsid |= (1 << 0);
}
cmd->channel_params.flags = IWX_SCAN_CHANNEL_FLAG_ENABLE_CHAN_ORDER;
cmd->channel_params.count = FillChannelsV5(cmd->channel_params.channel_config,
bitmapSsid);
cmd->channel_params.n_aps_override[0] = IWX_SCAN_ADWELL_N_APS_GO_FRIENDLY;
cmd->channel_params.n_aps_override[1] = IWX_SCAN_ADWELL_N_APS_SOCIAL_CHS;
bool ok = IwxSendCmdPdu(IWX_WIDE_ID(IWX_LONG_GROUP, IWX_SCAN_REQ_UMAC),
cmd, sizeof(*cmd));
Memory::g_heap->Free(cmd);
return ok;
}
static bool UmacScanV14() {
auto* cmd = (IwxScanReqUmacV14*)Memory::g_heap->Request(sizeof(IwxScanReqUmacV14));
if (!cmd) return false;
memset(cmd, 0, sizeof(*cmd));
cmd->ooc_priority = IWX_SCAN_PRIORITY_EXT_6;
cmd->uid = 0;
uint32_t bitmapSsid = 0;
FillGeneralParams(&cmd->general_params, ScanFlagsV2(g_directSsidLen != 0));
cmd->periodic_params.schedule[0].interval = 0;
cmd->periodic_params.schedule[0].iter_count = 1;
FillProbeReq(&cmd->probe_params.preq);
if (g_directSsidLen) {
cmd->probe_params.direct_scan[0].id = 0x00;
cmd->probe_params.direct_scan[0].len = g_directSsidLen;
memcpy(cmd->probe_params.direct_scan[0].ssid, g_directSsid,
g_directSsidLen);
bitmapSsid |= (1 << 0);
}
cmd->channel_params.flags = IWX_SCAN_CHANNEL_FLAG_ENABLE_CHAN_ORDER;
cmd->channel_params.count = FillChannels(cmd->channel_params.channel_config,
bitmapSsid);
cmd->channel_params.n_aps_override[0] = IWX_SCAN_ADWELL_N_APS_GO_FRIENDLY;
cmd->channel_params.n_aps_override[1] = IWX_SCAN_ADWELL_N_APS_SOCIAL_CHS;
bool ok = IwxSendCmdPdu(IWX_WIDE_ID(IWX_LONG_GROUP, IWX_SCAN_REQ_UMAC),
cmd, sizeof(*cmd));
Memory::g_heap->Free(cmd);
return ok;
}
bool IwxStartScan(const char* directSsid) {
if (g_iwx.State != IwxFwState::Running) return false;
if (g_iwx.ScanActive) return false;
g_directSsidLen = 0;
g_directSsid[0] = '\0';
if (directSsid) {
while (g_directSsidLen < 32 && directSsid[g_directSsidLen]) {
g_directSsid[g_directSsidLen] = directSsid[g_directSsidLen];
g_directSsidLen++;
}
g_directSsid[g_directSsidLen] = '\0';
}
g_iwx.ScanCompleted = false;
g_iwx.ScanActive = true;
int ver = IwxLookupCmdVer(IWX_LONG_GROUP, IWX_SCAN_REQ_UMAC);
bool ok = (ver >= 17) ? UmacScanV17() : UmacScanV14();
if (!ok) {
g_iwx.ScanActive = false;
KernelLogStream(ERROR, "WiFi") << "Could not initiate scan";
}
return ok;
}
bool IwxAbortScan() {
if (!g_iwx.ScanActive) return true;
IwxUmacScanAbort cmd = {};
bool ok = IwxSendCmdPdu(IWX_WIDE_ID(IWX_LONG_GROUP, IWX_SCAN_ABORT_UMAC),
&cmd, sizeof(cmd));
if (ok) g_iwx.ScanActive = false;
return ok;
}
// =========================================================================
// Notification dispatch
// =========================================================================
static void HandleRxMpdu(const IwxRxPacket* pkt, uint32_t bufLen) {
uint32_t payload = IwxRxPacketPayloadLen(pkt);
if (payload < sizeof(IwxRxMpduDesc)) return;
auto* desc = (const IwxRxMpduDesc*)pkt->data;
if (!(desc->status & IWX_RX_MPDU_RES_STATUS_CRC_OK)
|| !(desc->status & IWX_RX_MPDU_RES_STATUS_OVERRUN_OK))
return;
uint32_t len = desc->mpdu_len;
if (len < 24) return; // shorter than a MAC header
const uint8_t* frame = pkt->data + sizeof(IwxRxMpduDesc);
uint32_t offset = (uint32_t)(frame - (const uint8_t*)pkt);
if (offset + len > bufLen) return;
// The firmware pads the header to a 4-byte boundary when the flag is
// set; the payload then starts two bytes later.
if (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_PAD) {
if (len < 2) return;
frame += 2;
if (offset + 2 + len > bufLen) return;
}
int energyA = desc->v3.energy_a ? -(int)desc->v3.energy_a : -256;
int energyB = desc->v3.energy_b ? -(int)desc->v3.energy_b : -256;
int rssi = energyA > energyB ? energyA : energyB;
if (rssi < -128) rssi = -128;
WifiRxMgmtFrame(frame, len, desc->v3.channel, (int8_t)rssi);
}
void IwxHandleNotification(const IwxRxPacket* pkt, const uint8_t* rxBuf,
uint32_t bufLen) {
(void)rxBuf;
uint32_t code = ((uint32_t)pkt->hdr.flags << 8) | pkt->hdr.code;
if (IwxCmdGroupId(code) == IWX_LONG_GROUP)
code = IwxCmdOpcode(code);
switch (code) {
case IWX_REPLY_RX_MPDU_CMD:
HandleRxMpdu(pkt, bufLen);
break;
case IWX_SCAN_COMPLETE_UMAC:
case IWX_SCAN_ITERATION_COMPLETE_UMAC:
g_iwx.ScanActive = false;
g_iwx.ScanCompleted = true;
break;
case IWX_MCC_CHUB_UPDATE_CMD:
// Firmware-initiated regulatory change; the channel list is
// refreshed on the next explicit MCC update.
break;
case IWX_WIDE_ID(IWX_PHY_OPS_GROUP, IWX_CT_KILL_NOTIFICATION):
KernelLogStream(ERROR, "WiFi")
<< "Device at critical temperature; stopping radio";
g_iwx.State = IwxFwState::Error;
break;
default:
// Command acks, statistics and everything else the driver does
// not act on. The transport still uses them to complete the
// in-flight synchronous command.
break;
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+439
View File
@@ -0,0 +1,439 @@
/*
* Wifi.cpp
* Wi-Fi subsystem facade: device probe, boot-deferred firmware bring-up,
* beacon parsing and the scan-result table exposed to userspace.
*
* Firmware bring-up is deferred exactly like Bluetooth: the PCI probe runs
* before the boot filesystems are mounted, but the .ucode image lives on the
* ramdisk, so the transport is claimed early and the multi-second firmware
* load is picked up from the idle loop once VFS is up.
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "Wifi.hpp"
#include "Iwx.hpp"
#include <Fs/Vfs.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Hal/SmpBoot.hpp>
#include <atomic>
using namespace Kt;
using namespace montauk::abi;
namespace Drivers::Net::Wifi {
static std::atomic<bool> g_initPending{false};
static bool g_initialized = false;
// =========================================================================
// Scan result table
// =========================================================================
static constexpr int MAX_SCAN_RESULTS = 64;
struct ScanEntry {
uint8_t Bssid[6];
char Ssid[33];
uint8_t SsidLen;
uint8_t Channel;
int8_t Rssi;
uint8_t Band; // 0 = 2.4 GHz, 1 = 5 GHz
uint8_t Security; // WifiSecurity value
uint16_t BeaconInterval;
bool Used;
};
static ScanEntry g_results[MAX_SCAN_RESULTS];
static int g_resultCount = 0;
static kcp::Spinlock g_resultLock;
static void ClearResults() {
g_resultLock.Acquire();
for (int i = 0; i < MAX_SCAN_RESULTS; i++) g_results[i].Used = false;
g_resultCount = 0;
g_resultLock.Release();
}
static bool SameAddr(const uint8_t* a, const uint8_t* b) {
for (int i = 0; i < 6; i++) if (a[i] != b[i]) return false;
return true;
}
// =========================================================================
// Beacon / probe-response parsing
// =========================================================================
// Element IDs used here.
static constexpr uint8_t ELEMID_SSID = 0;
static constexpr uint8_t ELEMID_DSPARMS = 3;
static constexpr uint8_t ELEMID_RSN = 48;
static constexpr uint8_t ELEMID_VENDOR = 221;
// Classify the network's security from the capability field and the RSN /
// WPA information elements.
//
// RSN layout: version(2) group cipher(4) pairwise count(2) suites(4*n)
// akm count(2) suites(4*n) [rsn capabilities(2)]
// A SAE (00-0F-AC:8) or FT-SAE (:9) AKM means WPA3.
static uint8_t ClassifyRsn(const uint8_t* ie, uint8_t len) {
if (len < 2) return WIFI_SEC_WPA2;
uint32_t off = 2;
if (off + 4 > len) return WIFI_SEC_WPA2;
off += 4; // group cipher suite
if (off + 2 > len) return WIFI_SEC_WPA2;
uint16_t pairwiseCount = (uint16_t)(ie[off] | (ie[off + 1] << 8));
off += 2;
off += (uint32_t)pairwiseCount * 4;
if (off + 2 > len) return WIFI_SEC_WPA2;
uint16_t akmCount = (uint16_t)(ie[off] | (ie[off + 1] << 8));
off += 2;
bool sae = false;
for (uint16_t i = 0; i < akmCount && off + 4 <= len; i++, off += 4) {
if (ie[off] == 0x00 && ie[off + 1] == 0x0f && ie[off + 2] == 0xac
&& (ie[off + 3] == 8 || ie[off + 3] == 9))
sae = true;
}
return sae ? WIFI_SEC_WPA3 : WIFI_SEC_WPA2;
}
struct ParsedBeacon {
const uint8_t* Ssid = nullptr;
uint8_t SsidLen = 0;
uint8_t Channel = 0;
uint8_t Security = WIFI_SEC_OPEN;
uint16_t BeaconInterval = 0;
};
static bool ParseBeacon(const uint8_t* frame, uint32_t len, ParsedBeacon* out) {
// 24-byte MAC header, then timestamp(8) + beacon interval(2) + caps(2).
constexpr uint32_t HDR = 24;
constexpr uint32_t FIXED = 12;
if (len < HDR + FIXED) return false;
const uint8_t* fixed = frame + HDR;
out->BeaconInterval = (uint16_t)(fixed[8] | (fixed[9] << 8));
uint16_t caps = (uint16_t)(fixed[10] | (fixed[11] << 8));
bool privacy = (caps & 0x0010) != 0;
bool haveRsn = false, haveWpa = false;
const uint8_t* ie = frame + HDR + FIXED;
const uint8_t* end = frame + len;
while (ie + 2 <= end) {
uint8_t id = ie[0];
uint8_t ielen = ie[1];
if (ie + 2 + ielen > end) break;
const uint8_t* body = ie + 2;
switch (id) {
case ELEMID_SSID:
out->Ssid = body;
out->SsidLen = ielen > 32 ? 32 : ielen;
break;
case ELEMID_DSPARMS:
if (ielen >= 1) out->Channel = body[0];
break;
case ELEMID_RSN:
haveRsn = true;
out->Security = ClassifyRsn(body, ielen);
break;
case ELEMID_VENDOR:
// WPA1: Microsoft OUI 00:50:F2, type 1.
if (ielen >= 4 && body[0] == 0x00 && body[1] == 0x50
&& body[2] == 0xf2 && body[3] == 0x01)
haveWpa = true;
break;
default:
break;
}
ie += 2 + ielen;
}
if (!haveRsn) {
if (haveWpa) out->Security = WIFI_SEC_WPA;
else if (privacy) out->Security = WIFI_SEC_WEP;
else out->Security = WIFI_SEC_OPEN;
}
return true;
}
// Called by the MVM RX path for every received management frame.
void WifiRxMgmtFrame(const uint8_t* frame, uint32_t len, uint8_t channel,
int8_t rssiDbm) {
if (len < 24) return;
uint8_t type = (uint8_t)(frame[0] & 0x0c);
uint8_t subtype = (uint8_t)(frame[0] & 0xf0);
if (type != 0x00) return; // management frames only
constexpr uint8_t SUBTYPE_BEACON = 0x80;
constexpr uint8_t SUBTYPE_PROBE_RESP = 0x50;
if (subtype != SUBTYPE_BEACON && subtype != SUBTYPE_PROBE_RESP) {
// Authentication/association responses belong to the connect path.
IwxConnectRxMgmt(frame, len);
return;
}
ParsedBeacon pb;
if (!ParseBeacon(frame, len, &pb)) return;
const uint8_t* bssid = frame + 16; // addr3 of a beacon
uint8_t ch = pb.Channel ? pb.Channel : channel;
g_resultLock.Acquire();
ScanEntry* slot = nullptr;
for (int i = 0; i < MAX_SCAN_RESULTS; i++) {
if (g_results[i].Used && SameAddr(g_results[i].Bssid, bssid)) {
slot = &g_results[i];
break;
}
}
if (!slot) {
for (int i = 0; i < MAX_SCAN_RESULTS; i++) {
if (!g_results[i].Used) {
slot = &g_results[i];
slot->Used = true;
g_resultCount++;
break;
}
}
}
if (!slot) { // table full
g_resultLock.Release();
return;
}
memcpy(slot->Bssid, bssid, 6);
slot->SsidLen = pb.SsidLen;
for (uint8_t i = 0; i < pb.SsidLen; i++) slot->Ssid[i] = (char)pb.Ssid[i];
slot->Ssid[pb.SsidLen] = '\0';
slot->Channel = ch;
slot->Rssi = rssiDbm;
slot->Band = ch > 14 ? 1 : 0;
slot->Security = pb.Security;
slot->BeaconInterval = pb.BeaconInterval;
g_resultLock.Release();
}
// =========================================================================
// Bring-up
// =========================================================================
bool Probe(const Pci::PciDevice& dev) {
if (!IwxProbe(dev)) return false;
// Firmware comes off the ramdisk, which is not mounted during the PCI
// scan; hand the rest of the bring-up to the idle loop.
g_initPending.store(true, std::memory_order_release);
return true;
}
static void CompleteInit() {
if (!IwxReadFirmware()) {
g_iwx.State = IwxFwState::Error;
return;
}
if (!IwxStartHw()) {
g_iwx.State = IwxFwState::Error;
return;
}
if (g_iwx.State == IwxFwState::RfKill) {
KernelLogStream(WARNING, "WiFi") << "Radio is off; skipping firmware load";
return;
}
g_iwx.State = IwxFwState::Booting;
if (!IwxRunInitUcode()) {
KernelLogStream(ERROR, "WiFi") << "Firmware initialization failed";
IwxStopDevice();
g_iwx.State = IwxFwState::Error;
return;
}
// Init and runtime share one image on AX210; the device is live now.
g_iwx.State = IwxFwState::Running;
if (!IwxInitHw()) {
KernelLogStream(ERROR, "WiFi") << "Hardware configuration failed";
IwxStopDevice();
g_iwx.State = IwxFwState::Error;
return;
}
g_initialized = true;
KernelLogStream(OK, "WiFi") << "Wi-Fi adapter initialized ("
<< (uint64_t)g_iwx.ChannelCount << " channels, firmware "
<< g_iwx.Fw.Version << ")";
}
void ServiceDeferredInit() {
if (!g_initPending.load(std::memory_order_relaxed) || g_initialized) return;
if (!Fs::Vfs::IsDriveRegistered(0)) return; // ramdisk not mounted yet
bool expected = true;
if (!g_initPending.compare_exchange_strong(expected, false,
std::memory_order_acquire)) return;
KernelLogStream(INFO, "WiFi") << "Completing deferred Wi-Fi init in background";
// Reserve this CPU for the bring-up: the firmware handshakes below use
// wall-clock timeouts, and being descheduled mid-way expires them with
// almost no polling done (the same reason Bluetooth reserves here).
auto* cpu = Smp::GetCurrentCpuData();
bool wasReserved = cpu && cpu->reservedForKernelWork;
if (cpu) cpu->reservedForKernelWork = true;
CompleteInit();
if (cpu) cpu->reservedForKernelWork = wasReserved;
}
void ServiceEvents() {
if (!g_iwx.Mmio) return;
if (g_iwx.WorkPending) IwxProcessEvents();
// Firmware commands the RX path deferred (it runs under the event
// pump's reentrancy guard and cannot wait for a completion itself).
IwxConnectService();
}
bool IsInitialized() { return g_initialized; }
bool IsPresent() { return g_iwx.State != IwxFwState::Absent; }
// =========================================================================
// Public operations
// =========================================================================
int Scan(WifiNetwork* out, int maxCount, uint32_t timeoutMs) {
if (!out || maxCount <= 0) return -1;
if (!g_initialized) return -1;
if (g_iwx.State != IwxFwState::Running) return -1;
if (timeoutMs < 1000) timeoutMs = 1000;
if (timeoutMs > 20000) timeoutMs = 20000;
ClearResults();
if (!IwxStartScan(nullptr)) return -1;
uint64_t start = Timekeeping::GetMilliseconds();
while (g_iwx.ScanActive && Timekeeping::GetMilliseconds() - start < timeoutMs) {
IwxProcessEvents();
for (int i = 0; i < 200; i++) asm volatile("pause" ::: "memory");
}
if (g_iwx.ScanActive) {
IwxAbortScan();
// Drain whatever the firmware queued before the abort landed.
uint64_t t0 = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - t0 < 200) IwxProcessEvents();
}
g_resultLock.Acquire();
int n = 0;
for (int i = 0; i < MAX_SCAN_RESULTS && n < maxCount; i++) {
if (!g_results[i].Used) continue;
const ScanEntry& e = g_results[i];
WifiNetwork& w = out[n];
memset(&w, 0, sizeof(w));
for (int k = 0; k < 32 && e.Ssid[k]; k++) w.ssid[k] = e.Ssid[k];
memcpy(w.bssid, e.Bssid, 6);
w.channel = e.Channel;
w.rssi = e.Rssi;
w.band = e.Band;
w.security = e.Security;
w.beaconInterval = e.BeaconInterval;
n++;
}
g_resultLock.Release();
return n;
}
int GetInfo(WifiInfo* out) {
if (!out) return -1;
memset(out, 0, sizeof(*out));
out->present = IsPresent() ? 1 : 0;
out->state = (uint8_t)g_iwx.State;
if (!IsPresent()) return -1;
memcpy(out->mac, g_iwx.Nvm.HwAddr, 6);
out->scanning = g_iwx.ScanActive ? 1 : 0;
out->bands = (uint8_t)((g_iwx.Nvm.Sku24GHz ? 1 : 0)
| (g_iwx.Nvm.Sku52GHz ? 2 : 0));
out->channels = (uint16_t)g_iwx.ChannelCount;
out->rxPackets = g_iwx.RxPackets;
out->fwErrors = (uint32_t)g_iwx.FwErrors;
out->connState = (uint32_t)IwxConnectState();
int i = 0;
for (; i < 31 && g_iwx.Fw.Version[i]; i++) out->fwVersion[i] = g_iwx.Fw.Version[i];
out->fwVersion[i] = '\0';
return 0;
}
int Connect(const char* ssid, const char* password) {
if (!g_initialized || !ssid) return -1;
// Locate the network in the most recent scan results: the firmware
// contexts need its BSSID and channel.
uint8_t bssid[6];
uint8_t channel = 0;
bool is5 = false;
uint8_t security = WIFI_SEC_OPEN;
bool found = false;
g_resultLock.Acquire();
for (int i = 0; i < MAX_SCAN_RESULTS; i++) {
if (!g_results[i].Used) continue;
const ScanEntry& e = g_results[i];
bool match = true;
for (int k = 0; k < 32; k++) {
char a = e.Ssid[k], b = ssid[k];
if (a != b) { match = false; break; }
if (a == '\0') break;
}
if (!match) continue;
memcpy(bssid, e.Bssid, 6);
channel = e.Channel;
is5 = e.Band == 1;
security = e.Security;
found = true;
break;
}
g_resultLock.Release();
if (!found) {
KernelLogStream(WARNING, "WiFi")
<< "Network not in scan results; run a scan first";
return -1;
}
// Encryption is checked before the passphrase: the WPA2/WPA3 key
// exchange (PMK derivation, EAPOL 4-way, HW key install) is not
// implemented at all, so a passphrase would not help and reporting
// "needs a passphrase" would be misleading.
if (security != WIFI_SEC_OPEN) {
KernelLogStream(WARNING, "WiFi")
<< "Encrypted networks are not supported yet (open only)";
return -2;
}
(void)password;
return IwxConnectStart(bssid, channel, is5, ssid) ? 0 : -1;
}
int Disconnect() {
if (!g_initialized) return -1;
IwxConnectAbort();
return 0;
}
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Wifi.hpp
* Wi-Fi subsystem facade: PCI probe hook, deferred bring-up, scanning.
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Pci/Pci.hpp>
#include <Api/Syscall.hpp>
namespace Drivers::Net::Wifi {
// PCI driver-table probe entry point.
bool Probe(const Pci::PciDevice& dev);
// Complete the firmware-dependent bring-up once the ramdisk is mounted.
// Cheap no-op unless a device is waiting. Called from the idle loop.
void ServiceDeferredInit();
// Steady-state event pump (RX ring, notifications). Idle-loop callback.
void ServiceEvents();
bool IsInitialized();
bool IsPresent();
// Run a scan and return up to maxCount networks found. Blocks (pumping
// firmware events) until the scan completes or timeoutMs elapses.
// Returns the number of entries written, or -1 on error.
int Scan(montauk::abi::WifiNetwork* out, int maxCount, uint32_t timeoutMs);
// Fill in adapter/firmware status.
int GetInfo(montauk::abi::WifiInfo* out);
// Association groundwork. Returns 0 when the firmware contexts came up,
// negative on failure. See IwxConnect.cpp: the 802.11 handshake itself is
// not implemented yet, so this cannot establish a usable link.
int Connect(const char* ssid, const char* password);
int Disconnect();
}
+7
View File
@@ -18,6 +18,7 @@
#include <Sched/Scheduler.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Drivers/Net/Wifi/Wifi.hpp>
#include <Drivers/Audio/IntelHda.hpp>
#include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
@@ -270,6 +271,12 @@ namespace Timekeeping {
// the adapter is down.
Drivers::USB::Bluetooth::ServiceEvents();
// Wi-Fi mirrors the Bluetooth split: the firmware load needs the
// ramdisk, and the RX/notification ring must be drained outside hard
// interrupt context (the MSI handler only latches a flag).
Drivers::Net::Wifi::ServiceDeferredInit();
Drivers::Net::Wifi::ServiceEvents();
// Thermal policy records transitions during BSP maintenance; print
// them from this explicitly non-interrupt idle path.
Hal::CpuPower::ServiceDeferredDiagnostics();