feat: intel wi-fi (AX211) driver with network scanning
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 46
|
||||
#define MONTAUK_BUILD_NUMBER 53
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user