feat: wi-fi - join WPA2/WPA3-PSK networks and carry traffic like ethernet

This commit is contained in:
daniel committed 2026-08-06 19:44:35 +02:00
1 parent a01e63c717
commit bbe1df62fd
40 files changed
+5878 -272

No files matched your search

+149
View File
@@ -0,0 +1,149 @@
/*
* Ieee80211.hpp
* 802.11 frame and RSN constants shared by the MLME and the supplicant.
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::Net::Wifi {
// =========================================================================
// Frame control
// =========================================================================
constexpr uint16_t IEEE80211_FC0_TYPE_MASK = 0x0c;
constexpr uint16_t IEEE80211_FC0_TYPE_MGT = 0x00;
constexpr uint16_t IEEE80211_FC0_TYPE_CTL = 0x04;
constexpr uint16_t IEEE80211_FC0_TYPE_DATA = 0x08;
constexpr uint16_t IEEE80211_FC0_SUBTYPE_MASK = 0xf0;
// Management subtypes (already shifted into the frame-control byte)
constexpr uint8_t IEEE80211_SUBTYPE_ASSOC_REQ = 0x00;
constexpr uint8_t IEEE80211_SUBTYPE_ASSOC_RESP = 0x10;
constexpr uint8_t IEEE80211_SUBTYPE_REASSOC_REQ = 0x20;
constexpr uint8_t IEEE80211_SUBTYPE_PROBE_REQ = 0x40;
constexpr uint8_t IEEE80211_SUBTYPE_PROBE_RESP = 0x50;
constexpr uint8_t IEEE80211_SUBTYPE_BEACON = 0x80;
constexpr uint8_t IEEE80211_SUBTYPE_DISASSOC = 0xa0;
constexpr uint8_t IEEE80211_SUBTYPE_AUTH = 0xb0;
constexpr uint8_t IEEE80211_SUBTYPE_DEAUTH = 0xc0;
constexpr uint8_t IEEE80211_SUBTYPE_ACTION = 0xd0;
// Data subtypes
constexpr uint8_t IEEE80211_SUBTYPE_DATA = 0x00;
constexpr uint8_t IEEE80211_SUBTYPE_QOS_DATA = 0x80;
constexpr uint8_t IEEE80211_SUBTYPE_NULL = 0x40;
// Frame control byte 1
constexpr uint8_t IEEE80211_FC1_TO_DS = 0x01;
constexpr uint8_t IEEE80211_FC1_FROM_DS = 0x02;
constexpr uint8_t IEEE80211_FC1_MORE_FRAG = 0x04;
constexpr uint8_t IEEE80211_FC1_RETRY = 0x08;
constexpr uint8_t IEEE80211_FC1_PWR_MGT = 0x10;
constexpr uint8_t IEEE80211_FC1_MORE_DATA = 0x20;
constexpr uint8_t IEEE80211_FC1_PROTECTED = 0x40;
constexpr uint32_t IEEE80211_HDR_LEN = 24;
constexpr uint32_t IEEE80211_QOS_HDR_LEN = 26;
// Capability bits in beacons / association requests
constexpr uint16_t IEEE80211_CAPINFO_ESS = 0x0001;
constexpr uint16_t IEEE80211_CAPINFO_PRIVACY = 0x0010;
constexpr uint16_t IEEE80211_CAPINFO_SHORT_PREAMBLE = 0x0020;
constexpr uint16_t IEEE80211_CAPINFO_SHORT_SLOT = 0x0400;
// Authentication algorithms
constexpr uint16_t IEEE80211_AUTH_ALG_OPEN = 0;
constexpr uint16_t IEEE80211_AUTH_ALG_SAE = 3;
// Reason / status codes used here
constexpr uint16_t IEEE80211_STATUS_SUCCESS = 0;
constexpr uint16_t IEEE80211_REASON_UNSPECIFIED = 1;
constexpr uint16_t IEEE80211_REASON_DEAUTH_LEAVING = 3;
constexpr uint16_t IEEE80211_REASON_MIC_FAILURE = 14;
constexpr uint16_t IEEE80211_REASON_4WAY_TIMEOUT = 15;
// =========================================================================
// Information elements
// =========================================================================
constexpr uint8_t IEEE80211_ELEMID_SSID = 0;
constexpr uint8_t IEEE80211_ELEMID_RATES = 1;
constexpr uint8_t IEEE80211_ELEMID_DSPARMS = 3;
constexpr uint8_t IEEE80211_ELEMID_XRATES = 50;
constexpr uint8_t IEEE80211_ELEMID_RSN = 48;
constexpr uint8_t IEEE80211_ELEMID_HTCAPS = 45;
constexpr uint8_t IEEE80211_ELEMID_VENDOR = 221;
// RSN cipher / AKM suite selectors (00-0F-AC:<type>)
constexpr uint32_t RSN_OUI = 0x000fac;
constexpr uint8_t RSN_CIPHER_NONE = 0;
constexpr uint8_t RSN_CIPHER_WEP40 = 1;
constexpr uint8_t RSN_CIPHER_TKIP = 2;
constexpr uint8_t RSN_CIPHER_CCMP = 4;
constexpr uint8_t RSN_CIPHER_WEP104 = 5;
constexpr uint8_t RSN_CIPHER_BIP_CMAC = 6;
constexpr uint8_t RSN_CIPHER_GCMP = 8;
constexpr uint8_t RSN_CIPHER_GCMP_256 = 9;
constexpr uint8_t RSN_CIPHER_CCMP_256 = 10;
constexpr uint8_t RSN_AKM_8021X = 1;
constexpr uint8_t RSN_AKM_PSK = 2;
constexpr uint8_t RSN_AKM_FT_PSK = 4;
constexpr uint8_t RSN_AKM_PSK_SHA256 = 6;
constexpr uint8_t RSN_AKM_SAE = 8;
constexpr uint8_t RSN_AKM_FT_SAE = 9;
// RSN capabilities
constexpr uint16_t RSN_CAP_MFPR = 0x0040; // management protection required
constexpr uint16_t RSN_CAP_MFPC = 0x0080; // management protection capable
// =========================================================================
// LLC/SNAP: the 8-byte shim between an 802.11 data payload and an
// Ethernet II EtherType.
// =========================================================================
struct LlcSnapHeader {
uint8_t dsap; // 0xaa
uint8_t ssap; // 0xaa
uint8_t control; // 0x03
uint8_t oui[3]; // 00-00-00 (RFC 1042)
uint16_t etherType; // big endian
} __attribute__((packed));
constexpr uint32_t LLC_SNAP_LEN = 8;
constexpr uint8_t LLC_SNAP_LSAP = 0xaa;
constexpr uint16_t ETHERTYPE_IPV4 = 0x0800;
constexpr uint16_t ETHERTYPE_ARP = 0x0806;
constexpr uint16_t ETHERTYPE_EAPOL = 0x888e;
// =========================================================================
// Byte helpers (802.11 fields are little endian, EAPOL is big endian)
// =========================================================================
inline uint16_t Get16Le(const uint8_t* p) {
return (uint16_t)(p[0] | ((uint16_t)p[1] << 8));
}
inline void Put16Le(uint8_t* p, uint16_t v) {
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8);
}
inline uint16_t Get16Be(const uint8_t* p) {
return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
}
inline void Put16Be(uint8_t* p, uint16_t v) {
p[0] = (uint8_t)(v >> 8); p[1] = (uint8_t)v;
}
inline bool AddrEqual(const uint8_t* a, const uint8_t* b) {
for (int i = 0; i < 6; i++) if (a[i] != b[i]) return false;
return true;
}
inline bool AddrIsBroadcast(const uint8_t* a) {
for (int i = 0; i < 6; i++) if (a[i] != 0xff) return false;
return true;
}
}
+105 -8
View File
@@ -17,6 +17,7 @@
#include <cstdint>
#include <Pci/Pci.hpp>
#include <CppLib/Spinlock.hpp>
#include <atomic>
#include "IwxReg.hpp"
namespace Drivers::Net::Wifi {
@@ -85,6 +86,13 @@ namespace Drivers::Net::Wifi {
// Rings
// =========================================================================
// Frames waiting in a TX queue are staged in single pages, one per
// in-flight frame. The descriptor ring itself has IWX_TX_RING_COUNT
// entries (the size the firmware was told about), but outstanding frames
// are capped at the number of staging pages so a slot can never be reused
// while the hardware is still reading it.
constexpr uint32_t IWX_TX_STAGE_SLOTS = 64;
struct IwxTxRing {
int Qid = 0;
IwxDma Desc; // IwxTfhTfd[IWX_TX_RING_COUNT]
@@ -94,6 +102,13 @@ namespace Drivers::Net::Wifi {
uint32_t Cur = 0; // ring slot (0..count-1)
uint32_t CurHw = 0; // hardware index (0..65535)
uint32_t Queued = 0;
// TX staging (data/management queues only; the command queue stages
// into its per-slot Cmd area instead).
uint8_t* Stage[IWX_TX_STAGE_SLOTS] = {};
uint64_t StagePhys[IWX_TX_STAGE_SLOTS] = {};
uint32_t StageSlots = 0;
bool Active = false; // configured with the firmware
};
struct IwxRxRing {
@@ -181,7 +196,11 @@ namespace Drivers::Net::Wifi {
IwxRxRing RxQ;
IwxTxRing CmdQ; // queue 0: host commands
IwxTxRing MgmtQ; // queue 1: management frames (connect path)
// One queue carries management frames, EAPOL and non-QoS data: the
// firmware maps non-QoS data onto the management TID anyway, and the
// driver never negotiates block-ack sessions that would need per-TID
// queues.
IwxTxRing MgmtQ;
// ALIVE / init-complete tracking (set from notification processing)
volatile bool AliveIntr = false;
@@ -194,18 +213,29 @@ namespace Drivers::Net::Wifi {
uint32_t UmacErrorTable = 0;
uint32_t LmacErrorTable = 0;
uint32_t LastCmdId = 0;
uint32_t LastCmdLen = 0;
uint8_t LastCmdPayload[192] = {}; // dumped on a firmware assert
bool LtrEnabled = false; // PCIe LTR capability advertised
// Synchronous-command bookkeeping (commands are fully serialized)
kcp::Spinlock CmdLock; // serializes SendCmd callers
// A Mutex, deliberately not a Spinlock: kcp::Spinlock disables
// interrupts, and this lock is held across a wall-clock wait for the
// firmware's response. With interrupts off the millisecond counter
// (driven by the APIC timer interrupt) never advances, so the timeout
// could never expire and one unanswered command would hang the machine
// with interrupts disabled. Every caller is process or idle context;
// the hard IRQ only latches WorkPending.
kcp::Mutex 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;
// Reentrancy guard for ProcessEvents. Interrupts stay enabled while
// commands wait, so two cores can genuinely race here; a plain bool
// would let both through.
std::atomic_flag InProcessEvents = ATOMIC_FLAG_INIT;
IwxNvmData Nvm;
IwxChannel Channels[IWX_MAX_CHANNELS_TRACKED];
@@ -215,8 +245,19 @@ namespace Drivers::Net::Wifi {
volatile bool ScanActive = false;
volatile bool ScanCompleted = false;
// Band of the BSS currently being joined; picks the legacy rate used
// for management frames.
bool Is5GHz = false;
// Serializes frame TX (the netif and the MLME both queue frames).
// Process/idle context only, so it keeps interrupts enabled too.
kcp::Mutex TxLock;
// Statistics/diagnostics
uint64_t RxPackets = 0;
uint64_t TxPackets = 0;
uint64_t TxFailures = 0;
uint64_t RxDataPackets = 0;
uint64_t FwErrors = 0;
};
@@ -271,8 +312,28 @@ namespace Drivers::Net::Wifi {
bool IwxCheckRfKill();
// TX queue management (used by connect path)
bool IwxEnableTxq(int staId, int qid, int tid);
// TX queue management (used by the connect path). On the new data-path
// API the firmware picks the queue number and reports it back, so `qid` is
// only a hint; the assigned value lands in ring.Qid.
bool IwxEnableTxq(IwxTxRing& ring, int staId, int qid, int tid);
void IwxDisableTxq(IwxTxRing& ring, int staId, int tid);
// Transmit one 802.11 frame. The header and payload are copied into a
// staging page, wrapped in a TX_CMD and handed to the queue.
// encrypt - let the firmware apply the installed key (data frames
// after the handshake); cleared for auth/assoc/EAPOL
// fixedRate - send at the lowest basic rate instead of letting the
// firmware's rate control pick (required for management
// frames, which have no rate table yet)
bool IwxTxFrame(IwxTxRing& ring, const uint8_t* hdr, uint32_t hdrLen,
const uint8_t* payload, uint32_t payloadLen,
bool encrypt, bool fixedRate);
// Install or remove a hardware key for the connected station.
bool IwxSetKey(const uint8_t* key, uint32_t keyLen, uint8_t keyIdx,
bool pairwise, uint8_t cipher, const uint8_t* rsc);
bool IwxRemoveKey(uint8_t keyIdx, bool pairwise, uint8_t cipher,
uint32_t keyLen);
// =========================================================================
// Firmware file parsing (IwxFw.cpp)
@@ -300,15 +361,51 @@ namespace Drivers::Net::Wifi {
void WifiRxMgmtFrame(const uint8_t* frame, uint32_t len, uint8_t channel,
int8_t rssiDbm);
// TX completion, called from RX processing for a TX_CMD response.
void IwxTxComplete(int qid, int idx, uint32_t status);
// =========================================================================
// Connect groundwork (IwxConnect.cpp) - UNTESTED scaffolding
// Connect / MLME (IwxConnect.cpp)
// =========================================================================
// Association states reported through WifiInfo.connState.
enum class IwxConnStateId : int {
Idle = 0,
ContextsUp,
Authenticating,
Authenticated,
Associating,
Associated, // 802.11 link up; open networks stop here
Handshaking, // WPA 4-way in progress
Connected, // keys installed (or open); data can flow
Failed,
};
// `rsnIe` is the AP's RSN element body (may be null for open networks).
bool IwxConnectStart(const uint8_t* bssid, uint8_t channel, bool is5GHz,
const char* ssid);
const char* ssid, const char* password,
const uint8_t* rsnIe, uint32_t rsnIeLen,
uint16_t beaconInterval, uint8_t dtimPeriod);
void IwxConnectAbort();
// Why the last IwxConnectStart() refused: false means the radio or the
// firmware failed, true means the network's security is unsupported. The
// two need very different advice, so they must not be conflated.
bool IwxConnectRefusedForSecurity();
void IwxConnectRxMgmt(const uint8_t* frame, uint32_t len);
// Inbound 802.11 data frame from the RX path.
void IwxConnectRxData(const uint8_t* frame, uint32_t len);
// Apply state changes the RX path queued (it cannot send commands itself).
void IwxConnectService();
int IwxConnectState();
// Link status for the network stack.
bool IwxLinkUp(); // associated and keyed
const uint8_t* IwxConnectBssid();
const char* IwxConnectSsid();
// Send an Ethernet frame over the air (called by the netif).
bool IwxConnectSendEthernet(const uint8_t* frame, uint32_t len);
// Sink for decapsulated Ethernet frames, implemented by Wifi.cpp.
void WifiRxEthernet(const uint8_t* frame, uint32_t len);
}
File diff suppressed because it is too large. Load diff
+37 -5
View File
@@ -7,6 +7,7 @@
*/
#include "Iwx.hpp"
#include "Ieee80211.hpp"
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -629,12 +630,23 @@ namespace Drivers::Net::Wifi {
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.
// The firmware pads a MAC header that is not a multiple of four bytes
// (in practice: every QoS data header) out to alignment by inserting
// two bytes between the header -- and the crypto IV, if any -- and
// the payload; mpdu_len counts them. Cut them out so the rest of the
// driver sees a contiguous 802.11 frame. Getting this wrong loses
// every QoS data frame while management frames keep working.
if (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_PAD) {
if (len < 2) return;
frame += 2;
if (offset + 2 + len > bufLen) return;
uint32_t hdrLen = 24;
if ((frame[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA
&& (frame[0] & IEEE80211_SUBTYPE_QOS_DATA))
hdrLen += 2;
if (frame[1] & IEEE80211_FC1_PROTECTED)
hdrLen += IWX_CCMP_HDR_LEN;
if (len < hdrLen + 2) return;
memmove((uint8_t*)frame + hdrLen, frame + hdrLen + 2,
len - hdrLen - 2);
len -= 2;
}
int energyA = desc->v3.energy_a ? -(int)desc->v3.energy_a : -256;
@@ -642,6 +654,26 @@ namespace Drivers::Net::Wifi {
int rssi = energyA > energyB ? energyA : energyB;
if (rssi < -128) rssi = -128;
uint8_t type = (uint8_t)(frame[0] & IEEE80211_FC0_TYPE_MASK);
if (type == IEEE80211_FC0_TYPE_DATA) {
// Encrypted frames are decrypted by the firmware, which strips the
// MIC but leaves the CCMP/GCMP header in place; drop anything that
// failed to decrypt rather than handing up ciphertext.
if (frame[1] & IEEE80211_FC1_PROTECTED) {
uint32_t sec = desc->status & IWX_RX_MPDU_STATUS_SEC_MASK;
bool ok = (desc->status & IWX_RX_MPDU_STATUS_DECRYPTED)
&& (desc->status & IWX_RX_MPDU_STATUS_MIC_OK)
&& (sec == IWX_RX_MPDU_STATUS_SEC_CCM
|| sec == IWX_RX_MPDU_STATUS_SEC_GCM);
if (!ok) return;
}
IwxConnectRxData(frame, len);
return;
}
if (type != IEEE80211_FC0_TYPE_MGT) return;
WifiRxMgmtFrame(frame, len, desc->v3.channel, (int8_t)rssi);
}
+377 -7
View File
@@ -567,7 +567,10 @@ inline uint32_t IwxRxPacketPayloadLen(const IwxRxPacket* pkt) {
return IwxRxPacketLen(pkt) - sizeof(IwxCmdHeader);
}
// DQA queue assignment
// DQA queue assignment. The management queue number is only a hint: on the v3
// data-path API the firmware assigns the queue and reports the number back.
// TID 15 carries management frames, EAPOL and non-QoS data -- the firmware maps
// non-QoS data onto this TID regardless.
constexpr int IWX_DQA_CMD_QUEUE = 0;
constexpr int IWX_DQA_MGMT_QUEUE = 1;
constexpr int IWX_MGMT_TID = 15;
@@ -949,6 +952,20 @@ constexpr uint8_t IWX_SCAN_ADWELL_N_APS_SOCIAL_CHS = 2;
constexpr uint32_t IWX_RX_MPDU_RES_STATUS_CRC_OK = 1u << 0;
constexpr uint32_t IWX_RX_MPDU_RES_STATUS_OVERRUN_OK = 1u << 1;
constexpr uint32_t IWX_RX_MPDU_STATUS_KEY_VALID = 1u << 3;
constexpr uint32_t IWX_RX_MPDU_STATUS_ICV_OK = 1u << 5;
constexpr uint32_t IWX_RX_MPDU_STATUS_MIC_OK = 1u << 6;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_MASK = 0x7u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_NONE = 0x0u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_WEP = 0x1u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_CCM = 0x2u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_TKIP = 0x3u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_EXT_ENC = 0x4u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_SEC_GCM = 0x5u << 8;
constexpr uint32_t IWX_RX_MPDU_STATUS_DECRYPTED = 1u << 11;
// The hardware strips the CCMP/GCMP MIC but leaves the 8-byte IV in place.
constexpr uint32_t IWX_CCMP_HDR_LEN = 8;
constexpr uint8_t IWX_RX_MPDU_MFLG2_PAD = 0x20;
constexpr uint8_t IWX_RX_MPDU_MFLG2_AMSDU = 0x40;
@@ -1000,21 +1017,46 @@ constexpr uint32_t IWX_FW_CTXT_ACTION_REMOVE = 3;
constexpr uint32_t IWX_LMAC_24G_INDEX = 0;
constexpr uint32_t IWX_LMAC_5G_INDEX = 1;
// PHY context
struct IwxFwChannelInfoV1 {
// PHY context.
//
// The channel-info sub-structure has two shapes and the firmware picks which
// one it expects via IWX_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS. AX211 firmware 89
// sets that bit, so it wants the v2 form: a 32-bit channel number first, then
// the band. Sending the 4-byte v1 form to a firmware expecting 8 bytes
// shifts lmac_id/rxchain_info and asserts the firmware, which then stops
// answering host commands entirely.
struct IwxFwChannelInfoV1 { // CHANNEL_CONFIG_API_S_VER_1
uint8_t band;
uint8_t channel;
uint8_t width;
uint8_t ctrl_pos;
} __attribute__((packed));
struct IwxFwChannelInfo { // CHANNEL_CONFIG_API_S_VER_2
uint32_t channel;
uint8_t band;
uint8_t width;
uint8_t ctrl_pos;
uint8_t reserved;
} __attribute__((packed));
constexpr uint8_t IWX_PHY_VHT_CHANNEL_MODE20 = 0x0;
constexpr uint8_t IWX_PHY_VHT_CTRL_POS_1_BELOW = 0x0;
constexpr uint32_t IWX_PHY_RX_CHAIN_VALID_POS = 1;
constexpr uint32_t IWX_PHY_RX_CHAIN_CNT_POS = 10;
constexpr uint32_t IWX_PHY_RX_CHAIN_MIMO_CNT_POS = 12;
struct IwxPhyContextCmd { // PHY_CONTEXT_CMD_API_VER_3/4 (non-UHB)
struct IwxPhyContextCmd { // PHY_CONTEXT_CMD_API_S_VER_3/4, UHB
uint32_t id_and_color;
uint32_t action;
IwxFwChannelInfo ci;
uint32_t lmac_id;
uint32_t rxchain_info; // reserved from VER_4 on
uint32_t dsp_cfg_flags;
uint32_t reserved;
} __attribute__((packed));
struct IwxPhyContextCmdV1Chan { // same command, pre-UHB channel info
uint32_t id_and_color;
uint32_t action;
IwxFwChannelInfoV1 ci;
@@ -1024,11 +1066,15 @@ struct IwxPhyContextCmd { // PHY_CONTEXT_CMD_API_VER_3/4 (non-UHB)
uint32_t reserved;
} __attribute__((packed));
static_assert(sizeof(IwxPhyContextCmd) == 32, "PHY context (UHB) must be 32 bytes");
static_assert(sizeof(IwxPhyContextCmdV1Chan) == 28, "PHY context (legacy) must be 28 bytes");
// MAC context
constexpr uint32_t IWX_FW_MAC_TYPE_BSS_STA = 5;
constexpr uint32_t IWX_TSF_ID_A = 0;
constexpr uint32_t IWX_AC_NUM = 4;
constexpr uint32_t IWX_MAC_QOS_FLG_UPDATE_EDCA = 1u << 0;
constexpr uint32_t IWX_MAC_QOS_FLG_TGN = 1u << 1;
constexpr uint32_t IWX_MAC_FILTER_IN_CONTROL_AND_MGMT = 1u << 1;
constexpr uint32_t IWX_MAC_FILTER_ACCEPT_GRP = 1u << 2;
constexpr uint32_t IWX_MAC_FILTER_IN_BEACON = 1u << 6;
@@ -1055,6 +1101,16 @@ struct IwxMacDataSta {
uint32_t assoc_beacon_arrive_time;
} __attribute__((packed));
// The per-mac-type tail of MAC_CONTEXT_CMD is a union in the firmware API, so
// the command length is that of its LARGEST member -- p2p_sta, which is
// iwl_mac_data_sta plus a ctwin word. Sending only the sta member makes the
// command four bytes short and asserts the firmware (observed on AX211 fw 89:
// UMAC error 0x201002FF on command 0x128).
struct IwxMacDataP2pSta {
IwxMacDataSta sta;
uint32_t ctwin;
} __attribute__((packed));
struct IwxMacCtxCmd { // IWX_MAC_CONTEXT_CMD_API_S_VER_1 (sta)
uint32_t id_and_color;
uint32_t action;
@@ -1072,9 +1128,15 @@ struct IwxMacCtxCmd { // IWX_MAC_CONTEXT_CMD_API_S_VER_1 (sta)
uint32_t filter_flags;
uint32_t qos_flags;
IwxAcQos ac[IWX_AC_NUM + 1];
IwxMacDataSta sta;
union {
IwxMacDataSta sta;
IwxMacDataP2pSta p2p_sta; // the largest member: sizes the command
} u;
} __attribute__((packed));
static_assert(sizeof(IwxMacCtxCmd) == 148,
"MAC context command must stay 148 bytes (union sized by p2p_sta)");
// Binding context
constexpr uint32_t IWX_MAX_MACS_IN_BINDING = 3;
struct IwxBindingCmd {
@@ -1178,8 +1240,9 @@ struct IwxRlcConfigCmd {
uint8_t reserved[3];
} __attribute__((packed));
// Session protection
constexpr uint32_t IWX_SESSION_PROTECT_CONF_ASSOC = 1;
// Session protection. ASSOC is the first value of
// enum iwl_session_prot_conf_id, i.e. zero -- 1 is GO_CLIENT_ASSOC.
constexpr uint32_t IWX_SESSION_PROTECT_CONF_ASSOC = 0;
struct IwxSessionProtCmd {
uint32_t id_and_color;
uint32_t action;
@@ -1196,6 +1259,313 @@ struct IwxSessionProtNotif {
uint32_t conf_id;
} __attribute__((packed));
// =============================================================================
// MLD API (MAC_CONF group)
//
// Firmware that advertises IWX_UCODE_TLV_CAPA_MLD_API_SUPPORT -- which AX211
// firmware 89 does -- implements these instead of the legacy MAC_CONTEXT_CMD /
// BINDING_CONTEXT_CMD / ADD_STA. The legacy commands are simply absent, and
// sending one asserts the firmware.
//
// Every layout and size below was confirmed against a host-command trace taken
// from Linux driving this same adapter and firmware (see
// tests/wifi/decode_iwl_trace.py), not inferred from a kernel header.
// =============================================================================
constexpr uint8_t IWX_MAC_CONFIG_CMD = 0x08; // MAC_CONF group
constexpr uint8_t IWX_LINK_CONFIG_CMD = 0x09;
constexpr uint8_t IWX_STA_CONFIG_CMD = 0x0a;
constexpr uint8_t IWX_AUX_STA_CMD = 0x0b;
constexpr uint8_t IWX_STA_REMOVE_CMD = 0x0c;
// iwl_mac_config_filter_flags
constexpr uint32_t IWX_MAC_CFG_FILTER_PROMISC = 1u << 0;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_CTRL_MGMT = 1u << 1;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_GRP = 1u << 2;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_BEACON = 1u << 3;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_BCAST_PROBE_RESP = 1u << 4;
constexpr uint32_t IWX_MAC_CFG_FILTER_ACCEPT_PROBE_REQ = 1u << 5;
struct IwxMacClientData { // MAC_CONTEXT_CONFIG_CLIENT_DATA_API_S_VER_2
uint8_t is_assoc;
uint8_t esr_transition_timeout;
uint16_t medium_sync_delay;
uint16_t assoc_id;
uint16_t reserved1;
uint16_t data_policy;
uint16_t reserved2;
uint32_t ctwin;
} __attribute__((packed));
struct IwxMacConfigCmd { // MAC_CONTEXT_CONFIG_CMD_API_S_VER_2
uint32_t id_and_color;
uint32_t action;
uint32_t mac_type;
uint8_t local_mld_addr[6];
uint16_t reserved_for_local_mld_addr;
uint32_t filter_flags;
uint16_t he_support;
uint16_t he_ap_support;
uint32_t eht_support;
uint32_t nic_not_ack_enabled;
IwxMacClientData client; // union with p2p_dev; client is the largest
} __attribute__((packed));
static_assert(sizeof(IwxMacConfigCmd) == 52, "MAC_CONFIG_CMD must be 52 bytes");
// iwl_link_ctx_modify_flags
constexpr uint32_t IWX_LINK_MODIFY_ACTIVE = 1u << 0;
constexpr uint32_t IWX_LINK_MODIFY_RATES_INFO = 1u << 1;
constexpr uint32_t IWX_LINK_MODIFY_PROTECT_FLAGS = 1u << 2;
constexpr uint32_t IWX_LINK_MODIFY_QOS_PARAMS = 1u << 3;
constexpr uint32_t IWX_LINK_MODIFY_BEACON_TIMING = 1u << 4;
constexpr uint32_t IWX_LINK_MODIFY_HE_PARAMS = 1u << 5;
constexpr uint32_t IWX_LINK_MODIFY_ALL = 0xff;
struct IwxHeBackoffConf { // AC_QOS_MU_EDCA_API_S
uint16_t cwmin;
uint16_t cwmax;
uint16_t aifsn;
uint16_t mu_time;
} __attribute__((packed));
struct IwxLinkConfigCmd { // LINK_CONTEXT_CONFIG_CMD_API_S_VER_1/2/3
uint32_t action;
uint32_t link_id;
uint32_t mac_id;
uint32_t phy_id; // IWX_FW_CTXT_INVALID until bound
uint8_t local_link_addr[6];
uint16_t reserved_for_local_link_addr;
uint32_t modify_mask;
uint32_t active;
uint32_t listen_lmac;
uint32_t cck_rates;
uint32_t ofdm_rates;
uint32_t cck_short_preamble;
uint32_t short_slot;
uint32_t protection_flags;
uint32_t qos_flags;
IwxAcQos ac[IWX_AC_NUM + 1];
uint8_t htc_trig_based_pkt_ext;
uint8_t rand_alloc_ecwmin;
uint8_t rand_alloc_ecwmax;
uint8_t ndp_fdbk_buff_th_exp;
IwxHeBackoffConf trig_based_txf[IWX_AC_NUM];
uint32_t bi;
uint32_t dtim_interval;
uint16_t puncture_mask; // removed in _VER_3
uint16_t frame_time_rts_th;
uint32_t flags;
uint32_t flags_mask;
uint8_t ref_bssid_addr[6];
uint16_t reserved_for_ref_bssid_addr;
uint8_t bssid_index;
uint8_t bss_color;
uint8_t spec_link_id;
uint8_t reserved2;
uint8_t ibss_bssid_addr[6];
uint16_t reserved_for_ibss_bssid_addr;
uint32_t reserved3[8];
} __attribute__((packed));
static_assert(sizeof(IwxLinkConfigCmd) == 208, "LINK_CONFIG_CMD must be 208 bytes");
// 2 spatial streams x 5 bandwidth indices x 2 thresholds
struct IwxHePktExtV2 {
uint8_t pkt_ext_qam_th[2][5][2];
} __attribute__((packed));
struct IwxStaConfigCmd { // STA_CMD_API_S_VER_1
uint32_t sta_id;
uint32_t link_id;
uint8_t peer_mld_address[6];
uint16_t reserved_for_peer_mld_address;
uint8_t peer_link_address[6];
uint16_t reserved_for_peer_link_address;
uint32_t station_type;
uint32_t assoc_id;
uint32_t beamform_flags;
uint32_t mfp;
uint32_t mimo;
uint32_t mimo_protection;
uint32_t ack_enabled;
uint32_t trig_rnd_alloc;
uint32_t tx_ampdu_spacing;
uint32_t tx_ampdu_max_size;
uint32_t sp_length;
uint32_t uapsd_acs;
IwxHePktExtV2 pkt_ext;
uint32_t htc_flags;
} __attribute__((packed));
static_assert(sizeof(IwxStaConfigCmd) == 96, "STA_CONFIG_CMD must be 96 bytes");
struct IwxStaRemoveCmd {
uint32_t sta_id;
} __attribute__((packed));
// =============================================================================
// TX path (AX210 / "new TX API")
// =============================================================================
// TX_CMD on a data queue carries the *short* 4-byte command header and stays in
// group 0; only host commands on the command queue are re-tagged into
// LONG_GROUP. Layout of a queued frame:
//
// [IwxCmdHeader 4][IwxTxCmdGen3 28][802.11 header][pad to 4][payload]
//
// TB0 covers the first 20 bytes, TB1 the rest of the header block (dword
// aligned) and TB2 the payload, matching iwl_txq_gen2_build_tx().
struct IwxDramSecInfo {
uint32_t pn_low;
uint16_t pn_high;
uint16_t aux_info;
} __attribute__((packed)); // DRAM_SEC_INFO_API_S_VER_1
struct IwxTxCmdGen3 {
uint16_t len; // total 802.11 frame length, plaintext
uint16_t flags; // IWX_TX_FLAGS_*
uint32_t offload_assist;
IwxDramSecInfo dram_info;
uint32_t rate_n_flags;
uint8_t reserved[8]; // named "ttl" in TX_CMD_API_S_VER_8
// 802.11 header follows
} __attribute__((packed)); // TX_CMD_API_S_VER_8 / _10
static_assert(sizeof(IwxTxCmdGen3) == 28, "TX command header must stay 28 bytes");
// iwl_tx_cmd_flags (TX_FLAGS_BITS_API_S_VER_3)
constexpr uint16_t IWX_TX_FLAGS_CMD_RATE = 1 << 0; // use rate_n_flags
constexpr uint16_t IWX_TX_FLAGS_ENCRYPT_DIS = 1 << 1; // send in the clear
constexpr uint16_t IWX_TX_FLAGS_HIGH_PRI = 1 << 2;
constexpr uint16_t IWX_TX_FLAGS_RTS = 1 << 3;
constexpr uint16_t IWX_TX_FLAGS_CTS = 1 << 4;
// iwl_tx_offload_assist_flags_pos
constexpr uint32_t IWX_TX_CMD_OFFLD_MH_SIZE_POS = 8; // header length in words
constexpr uint32_t IWX_TX_CMD_OFFLD_MH_MASK = 0x1f;
constexpr uint32_t IWX_TX_CMD_OFFLD_PAD = 1u << 13;
constexpr uint32_t IWX_TX_CMD_OFFLD_AMSDU = 1u << 14;
// rate_n_flags. The firmware advertises which encoding it wants: TX_CMD
// notification version 7 and later use the "version 2" layout, where bits 10-8
// select the modulation and bits 3-0 index the legacy rate table. Older
// firmware uses the version 1 layout, which carries the PLCP value directly and
// flags CCK with bit 9. Antenna selection sits at bits 15-14 in both.
constexpr uint32_t IWX_RATE_MCS_ANT_POS = 14;
constexpr uint32_t IWX_RATE_MCS_ANT_A = 1u << IWX_RATE_MCS_ANT_POS;
constexpr uint32_t IWX_RATE_MCS_ANT_B = 2u << IWX_RATE_MCS_ANT_POS;
// version 2
constexpr uint32_t IWX_RATE_MCS_MOD_TYPE_POS = 8;
constexpr uint32_t IWX_RATE_MCS_MOD_CCK = 0u << IWX_RATE_MCS_MOD_TYPE_POS;
constexpr uint32_t IWX_RATE_MCS_MOD_LEGACY_OFDM = 1u << IWX_RATE_MCS_MOD_TYPE_POS;
constexpr uint32_t IWX_RATE_LEGACY_RATE_MSK = 0x7;
constexpr uint32_t IWX_RATE_MCS_CHAN_WIDTH_20 = 0u << 11;
// version 1 (PLCP encoded directly in bits 7-0)
constexpr uint32_t IWX_RATE_MCS_CCK_MSK_V1 = 1u << 9;
constexpr uint8_t IWX_RATE_1M_PLCP = 10;
constexpr uint8_t IWX_RATE_6M_PLCP = 13;
// TX response (TX_CMD notification). Only the leading fields are used: the
// driver just needs the slot back and a success/failure verdict.
constexpr uint32_t IWX_TX_STATUS_MSK = 0x000000ff;
constexpr uint32_t IWX_TX_STATUS_SUCCESS = 0x01;
constexpr uint32_t IWX_TX_STATUS_DIRECT_DONE = 0x02;
struct IwxTxResp {
uint8_t frame_count;
uint8_t bt_kill_count;
uint8_t failure_rts;
uint8_t failure_frame;
uint32_t initial_rate;
uint16_t wireless_media_time;
uint8_t pa_status;
uint8_t pa_integ_res_a[3];
uint8_t pa_integ_res_b[3];
uint8_t pa_integ_res_c[3];
uint16_t measurement_req_id;
uint8_t reduced_tpc;
uint8_t reserved;
uint32_t tfd_info;
uint16_t seq_ctl;
uint16_t byte_cnt;
uint8_t tlc_info;
uint8_t ra_tid;
uint16_t frame_ctrl;
// followed by per-frame status entries; entry 0 is what matters here
uint16_t status;
uint16_t sequence;
} __attribute__((packed));
// =============================================================================
// ADD_STA_KEY (hardware key installation)
// =============================================================================
// iwl_sta_key_flag
constexpr uint16_t IWX_STA_KEY_FLG_NO_ENC = 0 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_WEP = 1 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_CCM = 2 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_TKIP = 3 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_EXT = 4 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_GCMP = 5 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_CMAC = 6 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_EN_MSK = 7 << 0;
constexpr uint16_t IWX_STA_KEY_FLG_WEP_KEY_MAP = 1 << 3;
constexpr uint16_t IWX_STA_KEY_FLG_KEYID_POS = 8;
constexpr uint16_t IWX_STA_KEY_FLG_KEYID_MSK = 3 << IWX_STA_KEY_FLG_KEYID_POS;
constexpr uint16_t IWX_STA_KEY_NOT_VALID = 1 << 11;
constexpr uint16_t IWX_STA_KEY_FLG_KEY_32BYTES = 1 << 12;
constexpr uint16_t IWX_STA_KEY_MULTICAST = 1 << 14;
constexpr uint16_t IWX_STA_KEY_MFP = 1 << 15;
struct IwxAddStaKeyCommon {
uint8_t sta_id;
uint8_t key_offset;
uint16_t key_flags;
uint8_t key[32];
uint8_t rx_secur_seq_cnt[16];
} __attribute__((packed));
struct IwxAddStaKeyCmd {
IwxAddStaKeyCommon common;
uint64_t rx_mic_key;
uint64_t tx_mic_key;
uint64_t transmit_seq_cnt;
} __attribute__((packed));
static_assert(sizeof(IwxAddStaKeyCmd) == 76, "ADD_STA_KEY layout changed");
// SEC_KEY_CMD (DATA_PATH group): key installation for the MLD firmware. The
// legacy ADD_STA_KEY above is absent from fw 89 like the rest of the legacy
// station API; the Linux trace installs both the PTK and the GTK with this.
constexpr uint8_t IWX_SEC_KEY_CMD = 0x18;
// iwl_sec_key_flags: cipher in the low 3 bits, modifiers above.
constexpr uint32_t IWX_SEC_KEY_FLAG_CIPHER_CCMP = 0x02;
constexpr uint32_t IWX_SEC_KEY_FLAG_CIPHER_TKIP = 0x03;
constexpr uint32_t IWX_SEC_KEY_FLAG_CIPHER_GCMP = 0x05;
constexpr uint32_t IWX_SEC_KEY_FLAG_NO_TX = 0x08;
constexpr uint32_t IWX_SEC_KEY_FLAG_KEY_SIZE = 0x10; // 256-bit key
constexpr uint32_t IWX_SEC_KEY_FLAG_MFP = 0x20;
constexpr uint32_t IWX_SEC_KEY_FLAG_MCAST_KEY = 0x40;
struct IwxSecKeyCmd { // SEC_KEY_CMD_API_S_VER_1 (the add form)
uint32_t action; // IWX_FW_CTXT_ACTION_*
uint32_t sta_mask;
uint32_t key_id;
uint32_t key_flags;
uint8_t key[32];
uint8_t tkip_mic_rx_key[8];
uint8_t tkip_mic_tx_key[8];
uint64_t rx_seq;
uint64_t tx_seq;
} __attribute__((packed));
static_assert(sizeof(IwxSecKeyCmd) == 80, "SEC_KEY_CMD must be 80 bytes");
// =============================================================================
// Firmware error log (read from device SRAM after an assert)
// =============================================================================
+367 -12
View File
@@ -17,6 +17,7 @@
*/
#include "Iwx.hpp"
#include "Ieee80211.hpp"
#include <Pci/Pci.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
@@ -459,11 +460,15 @@ namespace Drivers::Net::Wifi {
asm volatile("" ::: "memory");
}
static bool IwxAllocTxRing(IwxTxRing& ring, int qid) {
// `stageSlots` reserves one page per concurrently queued frame; pass 0 for
// queues that only ever carry host commands.
static bool IwxAllocTxRing(IwxTxRing& ring, int qid, uint32_t stageSlots = 0) {
ring.Qid = qid;
ring.Cur = 0;
ring.CurHw = 0;
ring.Queued = 0;
ring.StageSlots = 0;
ring.Active = false;
if (!IwxDmaAlloc(ring.Desc, sizeof(IwxTfhTfd) * IWX_TX_RING_COUNT))
return false;
@@ -476,6 +481,15 @@ namespace Drivers::Net::Wifi {
// are staged in this page instead of the per-slot command area.
if (!IwxDmaAlloc(ring.Bounce, 4096))
return false;
if (stageSlots > IWX_TX_STAGE_SLOTS) stageSlots = IWX_TX_STAGE_SLOTS;
for (uint32_t i = 0; i < stageSlots; i++) {
void* p = Memory::g_pfa->AllocateZeroed();
if (!p) return false;
ring.Stage[i] = (uint8_t*)p;
ring.StagePhys[i] = Memory::SubHHDM(p);
ring.StageSlots = i + 1;
}
return true;
}
@@ -484,6 +498,14 @@ namespace Drivers::Net::Wifi {
IwxDmaFree(ring.BcTbl);
IwxDmaFree(ring.Cmd);
IwxDmaFree(ring.Bounce);
for (uint32_t i = 0; i < ring.StageSlots; i++) {
if (ring.Stage[i]) {
Memory::g_pfa->Free(ring.Stage[i]);
ring.Stage[i] = nullptr;
}
}
ring.StageSlots = 0;
ring.Active = false;
}
static void IwxResetTxRing(IwxTxRing& ring) {
@@ -846,6 +868,9 @@ namespace Drivers::Net::Wifi {
return -1;
}
void IwxDumpFwError();
static uint32_t g_cmdTimeouts = 0; // consecutive unanswered commands
bool IwxSendCmd(IwxHostCmd& hcmd) {
if (g_iwx.State == IwxFwState::Error) return false;
@@ -909,6 +934,12 @@ namespace Drivers::Net::Wifi {
g_iwx.CmdDone = false;
g_iwx.LastCmdId = code;
// Keep the payload so a firmware assert can show exactly what it
// choked on -- struct mismatches are invisible without the bytes.
g_iwx.LastCmdLen = hcmd.Len;
uint32_t keep = hcmd.Len < sizeof(g_iwx.LastCmdPayload)
? hcmd.Len : (uint32_t)sizeof(g_iwx.LastCmdPayload);
if (hcmd.Data && keep) memcpy(g_iwx.LastCmdPayload, hcmd.Data, keep);
g_iwx.CmdWantResp = hcmd.WantResp;
g_iwx.CmdRespLen = 0;
g_iwx.CmdIdx = idx;
@@ -921,18 +952,47 @@ namespace Drivers::Net::Wifi {
// Wait for the firmware's response/ack. Commands are serialized by
// CmdLock, so exactly one can be in flight and the completion is
// unambiguous.
// Two independent bounds. The wall clock is the intended one, but it
// is driven by the timer interrupt, so anything that leaves this loop
// running with interrupts disabled would spin forever and take the
// whole machine down with it -- the spin cap makes that impossible.
// Bail immediately if the firmware has asserted, because it will never
// answer this or any later command.
constexpr uint32_t MAX_SPINS = 20000; // ~2 s at 100 us
bool ok = false;
bool died = false;
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < 1000) {
for (uint32_t spins = 0; spins < MAX_SPINS; spins++) {
IwxProcessEvents();
if (g_iwx.CmdDone) { ok = true; break; }
if (g_iwx.State == IwxFwState::Error) { died = true; break; }
if (Timekeeping::GetMilliseconds() - start >= 1000) break;
IwxDelayUs(100);
}
if (!ok) {
KernelLogStream(WARNING, "WiFi") << "Command 0x" << base::hex
<< (uint64_t)code << base::dec << " timed out";
<< (uint64_t)code << base::dec
<< (died ? " abandoned: firmware has stopped responding"
: " timed out");
if (ring.Queued > 0) ring.Queued--;
if (!died) {
// Dump on the first silence: the firmware's error table names
// the command that asserted, and it is overwritten as later
// commands go unanswered.
if (++g_cmdTimeouts == 1) IwxDumpFwError();
// Repeated silence means it is wedged and every later command
// would burn the same timeout, so stop trying. A single late
// response is not worth disabling the adapter over.
if (g_cmdTimeouts >= 3) {
KernelLogStream(ERROR, "WiFi")
<< "Firmware stopped responding to host commands";
g_iwx.FwErrors++;
g_iwx.State = IwxFwState::Error;
}
}
} else {
g_cmdTimeouts = 0;
}
g_iwx.CmdWantResp = false;
@@ -969,10 +1029,10 @@ namespace Drivers::Net::Wifi {
// TX queue configuration (used by the connect path)
// =========================================================================
bool IwxEnableTxq(int staId, int qid, int tid) {
IwxTxRing& ring = g_iwx.MgmtQ;
bool IwxEnableTxq(IwxTxRing& ring, int staId, int qid, int tid) {
IwxResetTxRing(ring);
ring.Qid = qid;
ring.Active = false;
int cmdVer = IwxLookupCmdVer(IWX_DATA_PATH_GROUP, IWX_SCD_QUEUE_CONFIG_CMD);
@@ -1016,14 +1076,280 @@ namespace Drivers::Net::Wifi {
return false;
auto* pkt = (IwxRxPacket*)g_iwx.CmdRespBuf;
auto* resp = (IwxTxQueueCfgRsp*)pkt->data;
// On the v3 data-path API the firmware owns queue assignment: `qid` is
// only a hint and the response names the queue we actually got.
if (resp->queue_number != qid) {
KernelLogStream(WARNING, "WiFi") << "Firmware assigned queue "
<< (uint64_t)resp->queue_number << ", expected " << (uint64_t)qid;
KernelLogStream(INFO, "WiFi") << "Firmware assigned TX queue "
<< (uint64_t)resp->queue_number << " (asked for "
<< (uint64_t)qid << ")";
}
ring.Qid = resp->queue_number;
ring.Active = true;
return true;
}
void IwxDisableTxq(IwxTxRing& ring, int staId, int tid) {
if (!ring.Active) return;
ring.Active = false;
int cmdVer = IwxLookupCmdVer(IWX_DATA_PATH_GROUP, IWX_SCD_QUEUE_CONFIG_CMD);
if (cmdVer == 3) {
IwxScdQueueCfgCmd cmd = {};
cmd.operation = IWX_SCD_QUEUE_REMOVE;
cmd.u.remove.sta_mask = 1u << staId;
cmd.u.remove.tid = (uint32_t)tid;
IwxSendCmdPdu(IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_SCD_QUEUE_CONFIG_CMD),
&cmd, sizeof(cmd));
} else {
IwxTxQueueCfgCmd cmd = {};
cmd.sta_id = (uint8_t)staId;
cmd.tid = (uint8_t)tid;
cmd.flags = 0; // clear ENABLE_QUEUE
cmd.cb_size = IWX_TFD_QUEUE_CB_SIZE(IWX_TX_RING_COUNT);
cmd.byte_cnt_addr = ring.BcTbl.Phys;
cmd.tfdq_addr = ring.Desc.Phys;
IwxSendCmdPdu(IWX_SCD_QUEUE_CFG, &cmd, sizeof(cmd));
}
IwxResetTxRing(ring);
}
// =========================================================================
// Frame transmission
// =========================================================================
// Lowest usable transmit antenna, as a rate_n_flags antenna field.
static uint32_t IwxTxAntBits() {
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;
if (!ant) ant = 1;
uint8_t lowest = (uint8_t)(ant & (uint8_t)(~ant + 1)); // isolate low bit
return (uint32_t)lowest << IWX_RATE_MCS_ANT_POS;
}
// The lowest basic rate for the current band, in whichever rate_n_flags
// encoding the firmware advertises: 1 Mbps CCK on 2.4 GHz, 6 Mbps OFDM on
// 5 GHz. Management frames go out at this rate because rate control has
// no table for the station until it is associated.
static uint32_t IwxLowestRate() {
uint32_t ant = IwxTxAntBits();
// Firmware exposing TX_CMD notification version 7 or later (equally,
// command version 9+) uses the "version 2" rate layout.
int notifVer = IwxLookupNotifVer(IWX_LONG_GROUP, IWX_TX_CMD);
int cmdVer = IwxLookupCmdVer(IWX_LONG_GROUP, IWX_TX_CMD);
bool v2 = notifVer > 6 || cmdVer >= 9;
if (v2) {
uint32_t mod = g_iwx.Is5GHz ? IWX_RATE_MCS_MOD_LEGACY_OFDM
: IWX_RATE_MCS_MOD_CCK;
return ant | mod | IWX_RATE_MCS_CHAN_WIDTH_20 | 0u; // index 0
}
uint32_t plcp = g_iwx.Is5GHz ? IWX_RATE_6M_PLCP : IWX_RATE_1M_PLCP;
uint32_t cck = g_iwx.Is5GHz ? 0 : IWX_RATE_MCS_CCK_MSK_V1;
return ant | cck | plcp;
}
// Recover a queue whose completions stopped arriving. Without this a
// single lost TX response would permanently consume a slot and, after
// StageSlots of them, wedge the queue.
static uint64_t g_txStallMs = 0;
static bool IwxTxQueueHasRoom(IwxTxRing& ring) {
if (ring.Queued < ring.StageSlots) {
g_txStallMs = 0;
return true;
}
uint64_t now = Timekeeping::GetMilliseconds();
if (g_txStallMs == 0) {
g_txStallMs = now;
return false;
}
if (now - g_txStallMs < 2000) return false;
KernelLogStream(WARNING, "WiFi")
<< "TX queue " << (uint64_t)ring.Qid
<< " stopped completing; resetting its outstanding count";
ring.Queued = 0;
g_txStallMs = 0;
return true;
}
bool IwxTxFrame(IwxTxRing& ring, const uint8_t* hdr, uint32_t hdrLen,
const uint8_t* payload, uint32_t payloadLen,
bool encrypt, bool fixedRate) {
if (g_iwx.State != IwxFwState::Running) return false;
if (!ring.Active || ring.StageSlots == 0) return false;
if (!hdr || hdrLen < IEEE80211_HDR_LEN || hdrLen > 32) return false;
// The 802.11 header is padded to a dword boundary before the payload;
// TX_CMD_OFFLD_PAD tells the firmware to skip those bytes.
uint32_t padded = (hdrLen + 3) & ~3u;
uint32_t head = (uint32_t)(sizeof(IwxCmdHeader) + sizeof(IwxTxCmdGen3)) + padded;
if (head + payloadLen > 4096) return false;
g_iwx.TxLock.Acquire();
if (!IwxTxQueueHasRoom(ring)) {
g_iwx.TxLock.Release();
return false;
}
uint32_t idx = ring.Cur;
uint32_t slot = idx % ring.StageSlots;
uint8_t* buf = ring.Stage[slot];
uint64_t phys = ring.StagePhys[slot];
memset(buf, 0, head);
auto* ch = (IwxCmdHeader*)buf;
ch->code = IWX_TX_CMD;
ch->flags = 0; // TX_CMD stays in the legacy group
ch->idx = (uint8_t)idx;
ch->qid = (uint8_t)ring.Qid;
auto* tx = (IwxTxCmdGen3*)(buf + sizeof(IwxCmdHeader));
tx->len = (uint16_t)(hdrLen + payloadLen);
uint16_t flags = 0;
if (!encrypt) flags |= IWX_TX_FLAGS_ENCRYPT_DIS;
if (fixedRate) {
flags |= IWX_TX_FLAGS_CMD_RATE;
tx->rate_n_flags = IwxLowestRate();
}
tx->flags = flags;
uint32_t offload = ((hdrLen / 2) & IWX_TX_CMD_OFFLD_MH_MASK)
<< IWX_TX_CMD_OFFLD_MH_SIZE_POS;
if (hdrLen % 4) offload |= IWX_TX_CMD_OFFLD_PAD;
tx->offload_assist = offload;
uint8_t* body = buf + sizeof(IwxCmdHeader) + sizeof(IwxTxCmdGen3);
memcpy(body, hdr, hdrLen);
if (payloadLen) memcpy(body + padded, payload, payloadLen);
auto* desc = &((IwxTfhTfd*)ring.Desc.Virt)[idx];
memset(desc, 0, sizeof(*desc));
desc->tbs[0].tb_len = (uint16_t)IWX_FIRST_TB_SIZE;
desc->tbs[0].addr = phys;
desc->tbs[1].tb_len = (uint16_t)(head - IWX_FIRST_TB_SIZE);
desc->tbs[1].addr = phys + IWX_FIRST_TB_SIZE;
uint16_t numTbs = 2;
if (payloadLen) {
desc->tbs[2].tb_len = (uint16_t)payloadLen;
desc->tbs[2].addr = phys + head;
numTbs = 3;
}
desc->num_tbs = numTbs;
// Byte-count table: AX210 wants the frame length in bytes plus the
// number of extra 64-byte chunks the firmware must fetch for the TFD.
uint32_t filled = (uint32_t)(sizeof(uint16_t) + numTbs * sizeof(IwxTfhTb));
uint32_t chunks = ((filled + 63) / 64) - 1;
auto* bc = (IwxGen3BcTblEntry*)ring.BcTbl.Virt;
bc[idx].tfd_offset = (uint16_t)((hdrLen + payloadLen) | (chunks << 14));
asm volatile("" ::: "memory");
ring.Queued++;
ring.Cur = (ring.Cur + 1) % IWX_TX_RING_COUNT;
ring.CurHw = (ring.CurHw + 1) % IWX_TFD_QUEUE_SIZE_MAX_GEN3;
IwxWrite32(IWX_HBUS_TARG_WRPTR, ((uint32_t)ring.Qid << 16) | ring.CurHw);
g_iwx.TxLock.Release();
return true;
}
void IwxTxComplete(int qid, int idx, uint32_t status) {
(void)idx;
IwxTxRing* ring = nullptr;
if (g_iwx.MgmtQ.Active && qid == g_iwx.MgmtQ.Qid) ring = &g_iwx.MgmtQ;
if (!ring) return;
g_iwx.TxLock.Acquire();
if (ring->Queued > 0) ring->Queued--;
g_iwx.TxLock.Release();
if (status == IWX_TX_STATUS_SUCCESS || status == IWX_TX_STATUS_DIRECT_DONE)
g_iwx.TxPackets++;
else
g_iwx.TxFailures++;
}
// =========================================================================
// Hardware key installation
// =========================================================================
// Keys go in and out through SEC_KEY_CMD: the MLD firmware does not
// implement the legacy ADD_STA_KEY, like the rest of the legacy station
// API. Values mirror the Linux trace: PTK as {sta_mask 1, key_id 0,
// flags CIPHER}, GTK as {sta_mask 1, key_id N, flags CIPHER|MCAST}.
static uint32_t SecKeyFlags(uint8_t cipher, uint32_t keyLen, bool pairwise) {
uint32_t flags;
switch (cipher) {
case RSN_CIPHER_CCMP:
case RSN_CIPHER_CCMP_256:
flags = IWX_SEC_KEY_FLAG_CIPHER_CCMP;
break;
case RSN_CIPHER_GCMP:
case RSN_CIPHER_GCMP_256:
flags = IWX_SEC_KEY_FLAG_CIPHER_GCMP;
break;
default:
return 0;
}
if (keyLen == 32) flags |= IWX_SEC_KEY_FLAG_KEY_SIZE;
if (!pairwise) flags |= IWX_SEC_KEY_FLAG_MCAST_KEY;
return flags;
}
bool IwxSetKey(const uint8_t* key, uint32_t keyLen, uint8_t keyIdx,
bool pairwise, uint8_t cipher, const uint8_t* rsc) {
if (!key || (keyLen != 16 && keyLen != 32)) return false;
uint32_t flags = SecKeyFlags(cipher, keyLen, pairwise);
if (!flags) {
KernelLogStream(WARNING, "WiFi")
<< "Cannot install a key for cipher " << (uint64_t)cipher;
return false;
}
IwxSecKeyCmd cmd = {};
cmd.action = IWX_FW_CTXT_ACTION_ADD;
cmd.sta_mask = 1u << IWX_STATION_ID;
cmd.key_id = keyIdx;
cmd.key_flags = flags;
memcpy(cmd.key, key, keyLen);
// The EAPOL RSC carries the AP's packet number for the group key,
// lowest byte first; it becomes the initial receive counter.
if (rsc) {
uint64_t pn = 0;
for (int i = 5; i >= 0; i--) pn = (pn << 8) | rsc[i];
cmd.rx_seq = pn;
}
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_SEC_KEY_CMD),
&cmd, sizeof(cmd));
}
bool IwxRemoveKey(uint8_t keyIdx, bool pairwise, uint8_t cipher,
uint32_t keyLen) {
uint32_t flags = SecKeyFlags(cipher, keyLen, pairwise);
if (!flags) return false;
IwxSecKeyCmd cmd = {};
cmd.action = IWX_FW_CTXT_ACTION_REMOVE;
cmd.sta_mask = 1u << IWX_STATION_ID;
cmd.key_id = keyIdx;
cmd.key_flags = flags;
return IwxSendCmdPdu(IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_SEC_KEY_CMD),
&cmd, sizeof(cmd));
}
// =========================================================================
// RX / notification processing
// =========================================================================
@@ -1083,7 +1409,24 @@ namespace Drivers::Net::Wifi {
// between them they pin down which host command the firmware rejected.
void IwxDumpFwError() {
KernelLogStream(ERROR, "WiFi-FW") << "Firmware assert; last command sent: 0x"
<< base::hex << (uint64_t)g_iwx.LastCmdId << base::dec;
<< base::hex << (uint64_t)g_iwx.LastCmdId << base::dec
<< " (" << (uint64_t)g_iwx.LastCmdLen << " byte payload)";
// Dump the payload: a struct that does not match the firmware's
// expected layout is otherwise impossible to spot from the log.
{
uint32_t n = g_iwx.LastCmdLen;
if (n > sizeof(g_iwx.LastCmdPayload)) n = sizeof(g_iwx.LastCmdPayload);
for (uint32_t off = 0; off < n; off += 32) {
auto line = KernelLogStream(INFO, "WiFi-FW");
line << " +" << (uint64_t)off << ": " << base::hex;
for (uint32_t i = off; i < n && i < off + 32; i++) {
if (g_iwx.LastCmdPayload[i] < 0x10) line << "0";
line << (uint64_t)g_iwx.LastCmdPayload[i];
}
line << base::dec;
}
}
uint32_t base_ = g_iwx.UmacErrorTable;
if (base_ < 0x400000) {
@@ -1141,6 +1484,16 @@ namespace Drivers::Net::Wifi {
case IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP, IWX_PNVM_INIT_COMPLETE):
g_iwx.InitComplete |= 0x2;
break;
case IWX_TX_CMD: {
// TX completion for a frame we queued on a data/mgmt queue.
uint32_t status = 0;
if (IwxRxPacketPayloadLen(pkt) >= sizeof(IwxTxResp)) {
auto* r = (const IwxTxResp*)pkt->data;
status = r->status & IWX_TX_STATUS_MSK;
}
IwxTxComplete(qid & ~0x80, pkt->hdr.idx, status);
break;
}
case IWX_REPLY_ERROR: {
if (IwxRxPacketPayloadLen(pkt) >= 8) {
uint32_t errType = *(const uint32_t*)pkt->data;
@@ -1208,8 +1561,9 @@ namespace Drivers::Net::Wifi {
void IwxProcessEvents() {
if (!g_iwx.Mmio) return;
if (g_iwx.InProcessEvents) return; // never nest
g_iwx.InProcessEvents = true;
// Never nest. With interrupts enabled during command waits this is a
// genuine multi-core race, so it has to be an atomic test-and-set.
if (g_iwx.InProcessEvents.test_and_set(std::memory_order_acquire)) return;
if (g_msix) {
uint32_t fh = IwxRead32(IWX_CSR_MSIX_FH_INT_CAUSES_AD);
@@ -1276,7 +1630,7 @@ namespace Drivers::Net::Wifi {
IwxNotifIntr();
g_iwx.WorkPending = false;
g_iwx.InProcessEvents = false;
g_iwx.InProcessEvents.clear(std::memory_order_release);
}
// =========================================================================
@@ -1435,7 +1789,8 @@ namespace Drivers::Net::Wifi {
|| !IwxDmaAlloc(g_iwx.PrphInfo, 4096)
|| !IwxAllocRxRing()
|| !IwxAllocTxRing(g_iwx.CmdQ, IWX_DQA_CMD_QUEUE)
|| !IwxAllocTxRing(g_iwx.MgmtQ, IWX_DQA_MGMT_QUEUE)) {
|| !IwxAllocTxRing(g_iwx.MgmtQ, IWX_DQA_MGMT_QUEUE,
IWX_TX_STAGE_SLOTS)) {
KernelLogStream(ERROR, "WiFi") << "Could not allocate device DMA memory";
IwxFreeRxRing();
IwxFreeTxRing(g_iwx.CmdQ);
+173 -32
View File
@@ -13,6 +13,7 @@
#include "Wifi.hpp"
#include "Iwx.hpp"
#include "Wpa.hpp"
#include <Fs/Vfs.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
@@ -20,6 +21,7 @@
#include <CppLib/Spinlock.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Hal/SmpBoot.hpp>
#include <Sched/Scheduler.hpp>
#include <atomic>
using namespace Kt;
@@ -36,6 +38,10 @@ namespace Drivers::Net::Wifi {
static constexpr int MAX_SCAN_RESULTS = 64;
// Long enough for a personal-mode RSN element: version, group cipher, one
// or two pairwise ciphers, a handful of AKMs and the capability field.
static constexpr int MAX_RSN_IE = 64;
struct ScanEntry {
uint8_t Bssid[6];
char Ssid[33];
@@ -45,6 +51,9 @@ namespace Drivers::Net::Wifi {
uint8_t Band; // 0 = 2.4 GHz, 1 = 5 GHz
uint8_t Security; // WifiSecurity value
uint16_t BeaconInterval;
uint8_t DtimPeriod;
uint8_t RsnIe[MAX_RSN_IE]; // element body, without id/len
uint8_t RsnIeLen;
bool Used;
};
@@ -71,6 +80,7 @@ namespace Drivers::Net::Wifi {
// Element IDs used here.
static constexpr uint8_t ELEMID_SSID = 0;
static constexpr uint8_t ELEMID_DSPARMS = 3;
static constexpr uint8_t ELEMID_TIM = 5;
static constexpr uint8_t ELEMID_RSN = 48;
static constexpr uint8_t ELEMID_VENDOR = 221;
@@ -108,6 +118,9 @@ namespace Drivers::Net::Wifi {
uint8_t Channel = 0;
uint8_t Security = WIFI_SEC_OPEN;
uint16_t BeaconInterval = 0;
uint8_t DtimPeriod = 0;
const uint8_t* Rsn = nullptr; // RSN element body
uint8_t RsnLen = 0;
};
static bool ParseBeacon(const uint8_t* frame, uint32_t len, ParsedBeacon* out) {
@@ -139,9 +152,18 @@ namespace Drivers::Net::Wifi {
case ELEMID_DSPARMS:
if (ielen >= 1) out->Channel = body[0];
break;
case ELEMID_TIM:
// DTIM count, then DTIM period. The firmware needs the
// period to schedule wake-ups once associated.
if (ielen >= 2) out->DtimPeriod = body[1];
break;
case ELEMID_RSN:
haveRsn = true;
out->Security = ClassifyRsn(body, ielen);
// Kept verbatim: the connect path negotiates ciphers and
// AKMs straight out of it.
out->Rsn = body;
out->RsnLen = ielen;
break;
case ELEMID_VENDOR:
// WPA1: Microsoft OUI 00:50:F2, type 1.
@@ -220,6 +242,12 @@ namespace Drivers::Net::Wifi {
slot->Band = ch > 14 ? 1 : 0;
slot->Security = pb.Security;
slot->BeaconInterval = pb.BeaconInterval;
if (pb.DtimPeriod) slot->DtimPeriod = pb.DtimPeriod;
slot->RsnIeLen = 0;
if (pb.Rsn && pb.RsnLen && pb.RsnLen <= MAX_RSN_IE) {
memcpy(slot->RsnIe, pb.Rsn, pb.RsnLen);
slot->RsnIeLen = pb.RsnLen;
}
g_resultLock.Release();
}
@@ -371,8 +399,18 @@ namespace Drivers::Net::Wifi {
| (g_iwx.Nvm.Sku52GHz ? 2 : 0));
out->channels = (uint16_t)g_iwx.ChannelCount;
out->rxPackets = g_iwx.RxPackets;
out->txPackets = g_iwx.TxPackets;
out->fwErrors = (uint32_t)g_iwx.FwErrors;
out->connState = (uint32_t)IwxConnectState();
out->connected = IwxLinkUp() ? 1 : 0;
if (IwxConnectState() != (int)IwxConnStateId::Idle) {
const char* ssid = IwxConnectSsid();
int k = 0;
for (; k < 32 && ssid[k]; k++) out->ssid[k] = ssid[k];
out->ssid[k] = '\0';
memcpy(out->bssid, IwxConnectBssid(), 6);
}
int i = 0;
for (; i < 31 && g_iwx.Fw.Version[i]; i++) out->fwVersion[i] = g_iwx.Fw.Version[i];
@@ -380,55 +418,136 @@ namespace Drivers::Net::Wifi {
return 0;
}
// Wait for the association + handshake to settle. The whole exchange is
// driven from IwxConnectService(), so this pumps both while it waits
// instead of relying on the idle loop getting scheduled.
static int WaitForConnection(uint32_t timeoutMs) {
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
IwxProcessEvents();
IwxConnectService();
auto state = (IwxConnStateId)IwxConnectState();
if (state == IwxConnStateId::Connected) return 0;
if (state == IwxConnStateId::Failed) {
// A handshake that got as far as exchanging EAPOL frames and
// then failed is almost always a wrong passphrase.
int rc = WpaGetState() == WpaState::Failed
? WIFI_ERR_AUTH : WIFI_ERR_FAILED;
// A rejection seen on the RX path only marks the state; the
// firmware contexts are still up and have to come back down.
IwxConnectAbort();
return rc;
}
if (state == IwxConnStateId::Idle) return WIFI_ERR_FAILED;
if (g_iwx.State == IwxFwState::Error) {
IwxConnectAbort();
return WIFI_ERR_FAILED;
}
// Yield rather than burn the core: this can wait seconds, and the
// idle loop on other cores drives the same state machine.
Sched::Schedule();
}
IwxConnectAbort();
return WIFI_ERR_TIMEOUT;
}
int Connect(const char* ssid, const char* password) {
if (!g_initialized || !ssid) return -1;
if (!g_initialized || !ssid) return WIFI_ERR_NO_ADAPTER;
if (g_iwx.State != IwxFwState::Running) return WIFI_ERR_NO_ADAPTER;
// Locate the network in the most recent scan results: the firmware
// contexts need its BSSID and channel.
// contexts need its BSSID and channel, and the connect path needs its
// RSN element to negotiate ciphers.
uint8_t bssid[6];
uint8_t channel = 0;
bool is5 = false;
uint8_t security = WIFI_SEC_OPEN;
uint8_t rsnIe[MAX_RSN_IE];
uint8_t rsnIeLen = 0;
uint16_t beaconInterval = 0;
uint8_t dtimPeriod = 0;
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;
// Strongest first, so a network seen on several bands or repeaters is
// joined through the best AP rather than whichever answered first.
int8_t bestRssi = -128;
for (int attempt = 0; attempt < 2 && !found; attempt++) {
if (attempt == 1) {
// Nothing matched: the caller may never have scanned, or the
// results may predate this network appearing.
KernelLogStream(INFO, "WiFi")
<< "\"" << ssid << "\" is not in the scan results; scanning again";
WifiNetwork tmp[1];
Scan(tmp, 1, 4000);
}
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) {
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;
if (found && e.Rssi <= bestRssi) continue;
memcpy(bssid, e.Bssid, 6);
channel = e.Channel;
is5 = e.Band == 1;
security = e.Security;
rsnIeLen = e.RsnIeLen;
if (rsnIeLen) memcpy(rsnIe, e.RsnIe, rsnIeLen);
beaconInterval = e.BeaconInterval;
dtimPeriod = e.DtimPeriod;
bestRssi = e.Rssi;
found = true;
}
g_resultLock.Release();
}
if (!found) return WIFI_ERR_NOT_FOUND;
bool havePassword = password && password[0];
// WEP and WPA1 use RC4/TKIP, which the supplicant deliberately does
// not implement; say so rather than failing mid-handshake.
if (security == WIFI_SEC_WEP || security == WIFI_SEC_WPA) {
KernelLogStream(WARNING, "WiFi")
<< "Network not in scan results; run a scan first";
return -1;
<< "WEP and the original WPA use ciphers this driver does not implement";
return WIFI_ERR_UNSUPPORTED;
}
// 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;
if (!rsnIeLen) {
KernelLogStream(WARNING, "WiFi")
<< "The beacon for this network carried no RSN element";
return WIFI_ERR_UNSUPPORTED;
}
if (!havePassword) return WIFI_ERR_NEED_KEY;
}
(void)password;
return IwxConnectStart(bssid, channel, is5, ssid) ? 0 : -1;
if (!IwxConnectStart(bssid, channel, is5, ssid,
havePassword ? password : nullptr,
security == WIFI_SEC_OPEN ? nullptr : rsnIe,
security == WIFI_SEC_OPEN ? 0 : rsnIeLen,
beaconInterval, dtimPeriod)) {
// IwxConnectStart already logged the specific reason. Only report
// a security problem when that is genuinely what happened; a radio
// or firmware failure needs completely different advice.
bool security_ = IwxConnectRefusedForSecurity();
auto state = (IwxConnStateId)IwxConnectState();
if (state != IwxConnStateId::Idle) IwxConnectAbort();
return security_ ? WIFI_ERR_UNSUPPORTED : WIFI_ERR_FAILED;
}
return WaitForConnection(15000);
}
int Disconnect() {
@@ -436,4 +555,26 @@ namespace Drivers::Net::Wifi {
IwxConnectAbort();
return 0;
}
// =========================================================================
// Network interface
// =========================================================================
static RxCallback g_rxCallback = nullptr;
const uint8_t* GetMacAddress() { return g_iwx.Nvm.HwAddr; }
bool IsLinkUp() { return g_initialized && IwxLinkUp(); }
bool SendPacket(const uint8_t* data, uint16_t length) {
if (!IsLinkUp()) return false;
return IwxConnectSendEthernet(data, length);
}
void SetRxCallback(RxCallback callback) { g_rxCallback = callback; }
// Called by the connect path for every decapsulated Ethernet frame.
void WifiRxEthernet(const uint8_t* frame, uint32_t len) {
if (g_rxCallback && len <= 0xffff) g_rxCallback(frame, (uint16_t)len);
}
}
+18 -3
View File
@@ -32,9 +32,24 @@ namespace Drivers::Net::Wifi {
// 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.
// Join a network. Blocks (pumping firmware events) until the link is up
// or the attempt fails. Returns 0 on success, or a negative WIFI_ERR_*
// value describing why it could not connect.
int Connect(const char* ssid, const char* password);
int Disconnect();
// -------------------------------------------------------------------------
// Network interface, registered with Net::NetIf once associated
// -------------------------------------------------------------------------
const uint8_t* GetMacAddress();
// Send an Ethernet frame. Fails while the link is down.
bool SendPacket(const uint8_t* data, uint16_t length);
// True once a network has been joined and, for encrypted networks, keyed.
bool IsLinkUp();
using RxCallback = void(*)(const uint8_t* data, uint16_t length);
void SetRxCallback(RxCallback callback);
}
+830
View File
@@ -0,0 +1,830 @@
/*
* Wpa.cpp
* WPA2/WPA3-PSK supplicant: PMK derivation, the EAPOL-Key 4-way handshake
* and the group-key handshake used for periodic GTK rekeying.
*
* Supported: RSN (WPA2) with CCMP or GCMP, AKM PSK (00-0F-AC:2) and
* PSK-SHA256 (:6). Key descriptor versions 2 (HMAC-SHA1-128 MIC, AES key
* wrap) and 3 (AES-128-CMAC MIC) are handled.
*
* Not supported: SAE (WPA3 needs finite-field / elliptic-curve crypto that
* is well beyond what belongs in this kernel), TKIP and WEP (descriptor
* version 1 needs HMAC-MD5 and RC4, and the ciphers are broken anyway), and
* 802.1X/EAP enterprise authentication. Those are rejected up front with a
* clear reason rather than failing halfway through the handshake.
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "Wpa.hpp"
#include "Ieee80211.hpp"
#include <Libraries/Crypto.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
using namespace Kt;
namespace Drivers::Net::Wifi {
// =========================================================================
// EAPOL-Key frame layout (IEEE 802.1X-2004 + IEEE 802.11 key descriptor)
// =========================================================================
constexpr uint8_t EAPOL_TYPE_KEY = 3;
constexpr uint8_t EAPOL_KEY_DESC_RSN = 2;
constexpr uint8_t EAPOL_KEY_DESC_WPA = 254;
constexpr uint16_t KEY_INFO_VERSION_MASK = 0x0007;
constexpr uint16_t KEY_INFO_KEY_TYPE = 0x0008; // set = pairwise
constexpr uint16_t KEY_INFO_INSTALL = 0x0040;
constexpr uint16_t KEY_INFO_ACK = 0x0080;
constexpr uint16_t KEY_INFO_MIC = 0x0100;
constexpr uint16_t KEY_INFO_SECURE = 0x0200;
constexpr uint16_t KEY_INFO_ERROR = 0x0400;
constexpr uint16_t KEY_INFO_REQUEST = 0x0800;
constexpr uint16_t KEY_INFO_ENCRYPTED = 0x1000;
// Key descriptor versions: 1 is HMAC-MD5 + RC4 (unsupported), 2 is
// HMAC-SHA1-128 + AES key wrap, 3 is AES-128-CMAC + AES key wrap.
constexpr uint8_t KEY_DESC_VER_RC4 = 1;
constexpr uint8_t KEY_DESC_VER_AES = 2;
constexpr uint8_t KEY_DESC_VER_AES_CMAC = 3;
struct EapolKey {
uint8_t version;
uint8_t type;
uint8_t length[2]; // big endian, bytes after this field
uint8_t descType;
uint8_t keyInfo[2]; // big endian
uint8_t keyLength[2]; // big endian
uint8_t replay[8];
uint8_t nonce[32];
uint8_t iv[16];
uint8_t rsc[8];
uint8_t keyId[8];
uint8_t mic[16];
uint8_t keyDataLen[2]; // big endian
// key data follows
} __attribute__((packed));
static_assert(sizeof(EapolKey) == 99, "EAPOL-Key header must stay 99 bytes");
constexpr uint32_t EAPOL_MIC_LEN = 16;
constexpr uint32_t MAX_KEY_DATA = 512;
constexpr uint32_t MAX_EAPOL_TX = sizeof(EapolKey) + 128;
// =========================================================================
// Supplicant state
// =========================================================================
static WpaConfig g_cfg = {};
static WpaState g_state = WpaState::Idle;
static uint8_t g_pmk[32];
static uint8_t g_ptk[64]; // KCK | KEK | TK
static uint32_t g_kckLen = 16;
static uint32_t g_kekLen = 16;
static uint32_t g_tkLen = 16;
static uint8_t g_anonce[32];
static uint8_t g_snonce[32];
static uint8_t g_replay[8];
static bool g_haveReplay = false;
static uint8_t g_keyDescVer = KEY_DESC_VER_AES;
static uint8_t g_descType = EAPOL_KEY_DESC_RSN;
static uint8_t g_eapolVersion = 2;
static uint8_t g_rsnIe[32];
static uint32_t g_rsnIeLen = 0;
// Retransmission of the last message we sent. The AP retries msg 1 and 3
// on its own, but a lost msg 2 or 4 otherwise stalls the exchange until the
// AP gives up and deauthenticates.
static uint8_t g_lastTx[MAX_EAPOL_TX];
static uint32_t g_lastTxLen = 0;
static uint64_t g_lastTxMs = 0;
static int g_retries = 0;
static uint64_t g_startMs = 0;
static constexpr uint64_t RETRY_INTERVAL_MS = 500;
static constexpr int MAX_RETRIES = 4;
static constexpr uint64_t HANDSHAKE_TIMEOUT_MS = 5000;
// =========================================================================
// Nonce generation
// =========================================================================
// A SNonce only has to be unique per (PMK, ANonce) pair, but making it
// unpredictable costs nothing: fold a run of TSC samples into SHA-256 along
// with our MAC and a monotonic counter. RDRAND is deliberately avoided --
// see Api/Random.hpp for why it is unreliable on this hardware.
static void GenNonce(uint8_t out[32]) {
static uint64_t counter = 0;
struct {
uint64_t tsc[8];
uint64_t counter;
uint8_t mac[6];
uint8_t pad[2];
} pool;
for (int i = 0; i < 8; i++) {
uint64_t tsc;
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax"
: "=a"(tsc) :: "rdx");
pool.tsc[i] = tsc;
// Space the samples out so the low bits differ between them.
for (int k = 0; k < 64; k++) asm volatile("pause" ::: "memory");
}
pool.counter = ++counter;
memcpy(pool.mac, g_cfg.OwnMac, 6);
pool.pad[0] = pool.pad[1] = 0;
Crypto::Sha256(&pool, sizeof(pool), out);
}
// =========================================================================
// Key derivation
// =========================================================================
// IEEE 802.11 PRF-N built on HMAC-SHA1 (used by AKM PSK).
static void Prf(const uint8_t* key, uint32_t keyLen, const char* label,
const uint8_t* data, uint32_t dataLen,
uint8_t* out, uint32_t outLen) {
uint32_t labelLen = 0;
while (label[labelLen]) labelLen++;
uint8_t counter = 0;
uint32_t pos = 0;
const uint8_t zero = 0;
while (pos < outLen) {
const uint8_t* parts[4] = {
(const uint8_t*)label, &zero, data, &counter
};
size_t lens[4] = { labelLen, 1, dataLen, 1 };
uint8_t digest[Crypto::SHA1_DIGEST_SIZE];
Crypto::HmacSha1(key, keyLen, parts, lens, 4, digest);
uint32_t take = outLen - pos;
if (take > Crypto::SHA1_DIGEST_SIZE) take = Crypto::SHA1_DIGEST_SIZE;
memcpy(out + pos, digest, take);
pos += take;
counter++;
Crypto::SecureZero(digest, sizeof(digest));
}
}
// IEEE 802.11 KDF built on HMAC-SHA256 (used by AKM PSK-SHA256 and SAE).
static void KdfSha256(const uint8_t* key, uint32_t keyLen, const char* label,
const uint8_t* data, uint32_t dataLen,
uint8_t* out, uint32_t outLen) {
uint32_t labelLen = 0;
while (label[labelLen]) labelLen++;
uint16_t bits = (uint16_t)(outLen * 8);
uint8_t lenLe[2] = { (uint8_t)bits, (uint8_t)(bits >> 8) };
uint16_t iter = 1;
uint32_t pos = 0;
while (pos < outLen) {
uint8_t iterLe[2] = { (uint8_t)iter, (uint8_t)(iter >> 8) };
const uint8_t* parts[4] = {
iterLe, (const uint8_t*)label, data, lenLe
};
size_t lens[4] = { 2, labelLen, dataLen, 2 };
uint8_t digest[Crypto::SHA256_DIGEST_SIZE];
Crypto::HmacSha256(key, keyLen, parts, lens, 4, digest);
uint32_t take = outLen - pos;
if (take > Crypto::SHA256_DIGEST_SIZE) take = Crypto::SHA256_DIGEST_SIZE;
memcpy(out + pos, digest, take);
pos += take;
iter++;
Crypto::SecureZero(digest, sizeof(digest));
}
}
static bool UsesSha256Kdf(uint8_t akm) {
return akm == RSN_AKM_PSK_SHA256 || akm == RSN_AKM_SAE
|| akm == RSN_AKM_FT_SAE;
}
// PTK = KDF(PMK, "Pairwise key expansion",
// min(AA,SPA) || max(AA,SPA) || min(ANonce,SNonce) || max(...))
static void DerivePtk() {
const uint8_t* aa = g_cfg.Bssid;
const uint8_t* spa = g_cfg.OwnMac;
uint8_t data[76];
int cmp = memcmp(aa, spa, 6);
const uint8_t* lo = (cmp < 0) ? aa : spa;
const uint8_t* hi = (cmp < 0) ? spa : aa;
memcpy(data, lo, 6);
memcpy(data + 6, hi, 6);
cmp = memcmp(g_anonce, g_snonce, 32);
const uint8_t* nlo = (cmp < 0) ? g_anonce : g_snonce;
const uint8_t* nhi = (cmp < 0) ? g_snonce : g_anonce;
memcpy(data + 12, nlo, 32);
memcpy(data + 44, nhi, 32);
uint32_t ptkLen = g_kckLen + g_kekLen + g_tkLen;
if (UsesSha256Kdf(g_cfg.Akm))
KdfSha256(g_pmk, 32, "Pairwise key expansion", data, sizeof(data),
g_ptk, ptkLen);
else
Prf(g_pmk, 32, "Pairwise key expansion", data, sizeof(data),
g_ptk, ptkLen);
Crypto::SecureZero(data, sizeof(data));
}
static const uint8_t* Kck() { return g_ptk; }
static const uint8_t* Kek() { return g_ptk + g_kckLen; }
static const uint8_t* Tk() { return g_ptk + g_kckLen + g_kekLen; }
// =========================================================================
// MIC
// =========================================================================
// The MIC covers the whole 802.1X frame with the MIC field zeroed.
static void ComputeMic(const uint8_t* frame, uint32_t len, uint8_t out[16]) {
if (g_keyDescVer == KEY_DESC_VER_AES_CMAC) {
const uint8_t* parts[1] = { frame };
size_t lens[1] = { len };
Crypto::AesCmac(Kck(), g_kckLen, parts, lens, 1, out);
} else {
uint8_t digest[Crypto::SHA1_DIGEST_SIZE];
Crypto::HmacSha1(Kck(), g_kckLen, frame, len, digest);
memcpy(out, digest, 16);
Crypto::SecureZero(digest, sizeof(digest));
}
}
static bool VerifyMic(const uint8_t* frame, uint32_t len) {
// Copy so the received frame can keep its MIC for logging.
static uint8_t scratch[sizeof(EapolKey) + MAX_KEY_DATA];
if (len > sizeof(scratch)) return false;
memcpy(scratch, frame, len);
auto* k = (EapolKey*)scratch;
uint8_t received[16];
memcpy(received, k->mic, 16);
memset(k->mic, 0, 16);
uint8_t computed[16];
ComputeMic(scratch, len, computed);
return Crypto::SecureEqual(received, computed, 16);
}
// =========================================================================
// RSN information element
// =========================================================================
static void PutSuite(uint8_t* p, uint8_t type) {
p[0] = 0x00; p[1] = 0x0f; p[2] = 0xac; p[3] = type;
}
static bool IsRsnSuite(const uint8_t* p) {
return p[0] == 0x00 && p[1] == 0x0f && p[2] == 0xac;
}
uint32_t WpaBuildRsnIe(uint8_t* out, uint32_t cap) {
constexpr uint32_t BODY = 20;
if (cap < BODY + 2) return 0;
uint8_t* p = out;
*p++ = IEEE80211_ELEMID_RSN;
*p++ = (uint8_t)BODY;
Put16Le(p, 1); p += 2; // RSN version
PutSuite(p, g_cfg.GroupCipher); p += 4;
Put16Le(p, 1); p += 2; // one pairwise cipher
PutSuite(p, g_cfg.PairwiseCipher); p += 4;
Put16Le(p, 1); p += 2; // one AKM
PutSuite(p, g_cfg.Akm); p += 4;
// RSN capabilities: no PMF (BIP/IGTK is not implemented), no preauth,
// one replay counter per key.
Put16Le(p, 0); p += 2;
return (uint32_t)(p - out);
}
bool WpaParseApRsn(const uint8_t* ie, uint32_t len, WpaConfig& cfg) {
if (len < 8) return false;
uint32_t off = 0;
uint16_t version = Get16Le(ie);
off += 2;
if (version != 1) return false;
if (off + 4 > len) return false;
uint8_t groupCipher = RSN_CIPHER_CCMP;
if (IsRsnSuite(ie + off)) groupCipher = ie[off + 3];
off += 4;
if (off + 2 > len) return false;
uint16_t pairwiseCount = Get16Le(ie + off);
off += 2;
// Prefer CCMP; fall back to GCMP. TKIP-only networks are rejected.
int bestPairwise = -1;
for (uint16_t i = 0; i < pairwiseCount && off + 4 <= len; i++, off += 4) {
if (!IsRsnSuite(ie + off)) continue;
uint8_t c = ie[off + 3];
if (c == RSN_CIPHER_CCMP) bestPairwise = c;
else if (c == RSN_CIPHER_GCMP && bestPairwise < 0) bestPairwise = c;
else if (c == RSN_CIPHER_GCMP_256 && bestPairwise < 0) bestPairwise = c;
else if (c == RSN_CIPHER_CCMP_256 && bestPairwise < 0) bestPairwise = c;
}
if (bestPairwise < 0) {
KernelLogStream(WARNING, "WiFi")
<< "AP offers no supported pairwise cipher (CCMP/GCMP required)";
return false;
}
if (off + 2 > len) return false;
uint16_t akmCount = Get16Le(ie + off);
off += 2;
// Prefer plain PSK; PSK-SHA256 works too but needs the SHA-256 KDF.
int bestAkm = -1;
bool sawSae = false;
for (uint16_t i = 0; i < akmCount && off + 4 <= len; i++, off += 4) {
if (!IsRsnSuite(ie + off)) continue;
uint8_t a = ie[off + 3];
if (a == RSN_AKM_SAE || a == RSN_AKM_FT_SAE) sawSae = true;
if (a == RSN_AKM_PSK) bestAkm = a;
else if (a == RSN_AKM_PSK_SHA256 && bestAkm < 0) bestAkm = a;
}
if (bestAkm < 0) {
if (sawSae)
KernelLogStream(WARNING, "WiFi")
<< "Network is WPA3-only (SAE); SAE authentication is not implemented";
else
KernelLogStream(WARNING, "WiFi")
<< "AP offers no pre-shared-key AKM (enterprise 802.1X is not supported)";
return false;
}
uint16_t rsnCaps = 0;
if (off + 2 <= len) rsnCaps = Get16Le(ie + off);
if (rsnCaps & RSN_CAP_MFPR) {
KernelLogStream(WARNING, "WiFi")
<< "AP requires management frame protection, which needs BIP; not supported";
return false;
}
// A mixed WPA/WPA2 network can pair with CCMP but still broadcast under
// TKIP. The firmware key slot and the RX decryption check both only
// handle CCMP/GCMP, so the group key would silently fail to install and
// every broadcast frame -- ARP requests, broadcast DHCP replies --
// would be dropped. Refuse up front instead of half connecting.
if (groupCipher != RSN_CIPHER_CCMP && groupCipher != RSN_CIPHER_GCMP
&& groupCipher != RSN_CIPHER_CCMP_256
&& groupCipher != RSN_CIPHER_GCMP_256) {
KernelLogStream(WARNING, "WiFi")
<< "AP broadcasts with an unsupported group cipher ("
<< (uint64_t)groupCipher
<< "); this is usually a mixed WPA/WPA2 network still using TKIP";
return false;
}
cfg.GroupCipher = groupCipher;
cfg.PairwiseCipher = (uint8_t)bestPairwise;
cfg.Akm = (uint8_t)bestAkm;
cfg.Mfp = false;
return true;
}
// =========================================================================
// Start / reset
// =========================================================================
static bool HexNibble(char c, uint8_t* out) {
if (c >= '0' && c <= '9') { *out = (uint8_t)(c - '0'); return true; }
if (c >= 'a' && c <= 'f') { *out = (uint8_t)(c - 'a' + 10); return true; }
if (c >= 'A' && c <= 'F') { *out = (uint8_t)(c - 'A' + 10); return true; }
return false;
}
// A 64-character hex string is the raw 256-bit PSK; anything else is a
// passphrase and goes through PBKDF2 with the SSID as salt.
static bool DerivePmk() {
if (g_cfg.PassLen == 64) {
bool allHex = true;
uint8_t tmp[32];
for (int i = 0; i < 32; i++) {
uint8_t hi, lo;
if (!HexNibble(g_cfg.Passphrase[i * 2], &hi)
|| !HexNibble(g_cfg.Passphrase[i * 2 + 1], &lo)) {
allHex = false;
break;
}
tmp[i] = (uint8_t)((hi << 4) | lo);
}
if (allHex) {
memcpy(g_pmk, tmp, 32);
Crypto::SecureZero(tmp, sizeof(tmp));
return true;
}
}
if (g_cfg.PassLen < 8) {
KernelLogStream(WARNING, "WiFi")
<< "WPA passphrases must be at least 8 characters";
return false;
}
// 4096 iterations of HMAC-SHA1 over a 32-byte output: this is a couple
// of hundred milliseconds of pure CPU, once per connect.
Crypto::Pbkdf2Sha1(g_cfg.Passphrase, g_cfg.PassLen,
g_cfg.Ssid, g_cfg.SsidLen, 4096, g_pmk, 32);
return true;
}
void WpaReset() {
g_state = WpaState::Idle;
g_haveReplay = false;
g_lastTxLen = 0;
g_retries = 0;
Crypto::SecureZero(g_pmk, sizeof(g_pmk));
Crypto::SecureZero(g_ptk, sizeof(g_ptk));
Crypto::SecureZero(g_snonce, sizeof(g_snonce));
Crypto::SecureZero(&g_cfg.Passphrase, sizeof(g_cfg.Passphrase));
}
bool WpaStart(const WpaConfig& cfg) {
WpaReset();
g_cfg = cfg;
switch (g_cfg.PairwiseCipher) {
case RSN_CIPHER_CCMP:
case RSN_CIPHER_GCMP:
g_tkLen = 16;
break;
case RSN_CIPHER_CCMP_256:
case RSN_CIPHER_GCMP_256:
g_tkLen = 32;
break;
default:
KernelLogStream(WARNING, "WiFi")
<< "Unsupported pairwise cipher " << (uint64_t)g_cfg.PairwiseCipher;
return false;
}
g_kckLen = 16;
g_kekLen = 16;
if (!DerivePmk()) return false;
g_rsnIeLen = WpaBuildRsnIe(g_rsnIe, sizeof(g_rsnIe));
if (!g_rsnIeLen) return false;
GenNonce(g_snonce);
g_state = WpaState::WaitMsg1;
g_startMs = 0;
KernelLogStream(INFO, "WiFi") << "Starting WPA handshake (AKM "
<< (uint64_t)g_cfg.Akm << ", pairwise cipher "
<< (uint64_t)g_cfg.PairwiseCipher << ")";
return true;
}
WpaState WpaGetState() { return g_state; }
bool WpaIsComplete() { return g_state == WpaState::Complete; }
// =========================================================================
// Outbound messages
// =========================================================================
static bool SendKeyFrame(uint16_t keyInfo, const uint8_t* keyData,
uint32_t keyDataLen, const uint8_t* nonce) {
uint32_t total = sizeof(EapolKey) + keyDataLen;
if (total > sizeof(g_lastTx)) return false;
memset(g_lastTx, 0, total);
auto* k = (EapolKey*)g_lastTx;
k->version = g_eapolVersion;
k->type = EAPOL_TYPE_KEY;
Put16Be(k->length, (uint16_t)(total - 4));
k->descType = g_descType;
Put16Be(k->keyInfo, keyInfo);
Put16Be(k->keyLength, 0); // RSN: always zero from the STA
memcpy(k->replay, g_replay, 8);
if (nonce) memcpy(k->nonce, nonce, 32);
Put16Be(k->keyDataLen, (uint16_t)keyDataLen);
if (keyDataLen) memcpy(g_lastTx + sizeof(EapolKey), keyData, keyDataLen);
if (keyInfo & KEY_INFO_MIC) ComputeMic(g_lastTx, total, k->mic);
g_lastTxLen = total;
g_lastTxMs = Timekeeping::GetMilliseconds();
g_retries = 0;
return WpaTxEapol(g_lastTx, total);
}
static bool SendMsg2() {
uint16_t keyInfo = (uint16_t)(g_keyDescVer | KEY_INFO_KEY_TYPE
| KEY_INFO_MIC);
return SendKeyFrame(keyInfo, g_rsnIe, g_rsnIeLen, g_snonce);
}
static bool SendMsg4() {
uint16_t keyInfo = (uint16_t)(g_keyDescVer | KEY_INFO_KEY_TYPE
| KEY_INFO_MIC | KEY_INFO_SECURE);
return SendKeyFrame(keyInfo, nullptr, 0, nullptr);
}
// Group key handshake reply: same shape as msg 4 without the pairwise bit.
static bool SendGroupAck() {
uint16_t keyInfo = (uint16_t)(g_keyDescVer | KEY_INFO_MIC
| KEY_INFO_SECURE);
return SendKeyFrame(keyInfo, nullptr, 0, nullptr);
}
// =========================================================================
// Key data (KDE) handling
// =========================================================================
// Decrypt the key data field of msg 3 or a group-key message. Returns the
// plaintext length, or 0 on failure.
static uint32_t DecryptKeyData(const uint8_t* in, uint32_t inLen,
uint8_t* out, uint32_t outCap) {
if (g_keyDescVer == KEY_DESC_VER_RC4) {
KernelLogStream(WARNING, "WiFi")
<< "AP used RC4 key wrapping (WPA1/TKIP), which is not supported";
return 0;
}
if (inLen < 24 || (inLen % 8) != 0 || inLen - 8 > outCap) return 0;
if (!Crypto::AesKeyUnwrap(Kek(), g_kekLen, in, inLen, out)) {
KernelLogStream(WARNING, "WiFi")
<< "EAPOL key data failed its integrity check (wrong passphrase?)";
return 0;
}
return inLen - 8;
}
// Walk the KDE list looking for a GTK (00-0F-AC data type 1).
static bool FindGtk(const uint8_t* data, uint32_t len,
const uint8_t** gtk, uint32_t* gtkLen, uint8_t* keyIdx) {
uint32_t off = 0;
while (off + 2 <= len) {
uint8_t id = data[off];
uint8_t elen = data[off + 1];
if (elen == 0 || off + 2 + elen > len) break;
const uint8_t* body = data + off + 2;
if (id == IEEE80211_ELEMID_VENDOR && elen >= 6 && IsRsnSuite(body)
&& body[3] == 1) {
// GTK KDE: [OUI 3][type 1][keyid+tx 1][reserved 1][GTK ...]
*keyIdx = (uint8_t)(body[4] & 0x03);
*gtk = body + 6;
*gtkLen = (uint32_t)(elen - 6);
return true;
}
off += 2 + elen;
}
return false;
}
// =========================================================================
// Inbound handling
// =========================================================================
static void Fail(const char* why) {
KernelLogStream(WARNING, "WiFi") << "WPA handshake failed: " << why;
g_state = WpaState::Failed;
Crypto::SecureZero(g_ptk, sizeof(g_ptk));
}
static void HandleMsg1(const uint8_t* frame, uint32_t len) {
(void)len;
auto* k = (const EapolKey*)frame;
memcpy(g_anonce, k->nonce, 32);
memcpy(g_replay, k->replay, 8);
g_haveReplay = true;
DerivePtk();
if (!SendMsg2()) {
Fail("could not transmit message 2");
return;
}
g_state = WpaState::WaitMsg3;
KernelLogStream(INFO, "WiFi") << "Handshake message 1 received; sent message 2";
}
static void HandleMsg3(const uint8_t* frame, uint32_t len) {
auto* k = (const EapolKey*)frame;
KernelLogStream(INFO, "WiFi") << "Handshake message 3 received";
// The ANonce must not have changed; if it has, the AP restarted the
// exchange and our PTK is stale.
if (memcmp(k->nonce, g_anonce, 32) != 0) {
Fail("the AP changed its nonce mid-handshake");
return;
}
memcpy(g_replay, k->replay, 8);
uint16_t keyInfo = Get16Be(k->keyInfo);
uint32_t keyDataLen = Get16Be(k->keyDataLen);
if (sizeof(EapolKey) + keyDataLen > len) {
Fail("truncated key data");
return;
}
const uint8_t* keyData = frame + sizeof(EapolKey);
uint8_t plain[MAX_KEY_DATA];
uint32_t plainLen = 0;
if (keyInfo & KEY_INFO_ENCRYPTED) {
plainLen = DecryptKeyData(keyData, keyDataLen, plain, sizeof(plain));
if (!plainLen) {
Fail("could not decrypt the group key");
return;
}
} else if (keyDataLen <= sizeof(plain)) {
memcpy(plain, keyData, keyDataLen);
plainLen = keyDataLen;
}
// Message 4 goes out before the keys are installed: the AP is still
// sending in the clear until it sees it.
if (!SendMsg4()) {
Fail("could not transmit message 4");
return;
}
if (!WpaInstallPtk(Tk(), g_tkLen, g_cfg.PairwiseCipher)) {
Fail("the firmware rejected the pairwise key");
return;
}
const uint8_t* gtk = nullptr;
uint32_t gtkLen = 0;
uint8_t keyIdx = 0;
if (FindGtk(plain, plainLen, &gtk, &gtkLen, &keyIdx)) {
if (!WpaInstallGtk(gtk, gtkLen, keyIdx, g_cfg.GroupCipher, k->rsc))
KernelLogStream(WARNING, "WiFi")
<< "Group key install failed; broadcast traffic will not be received";
} else {
KernelLogStream(WARNING, "WiFi")
<< "No group key in message 3; broadcast traffic will not be received";
}
g_state = WpaState::Complete;
g_lastTxLen = 0;
KernelLogStream(OK, "WiFi") << "WPA handshake complete; link is encrypted";
}
// Periodic GTK rekey initiated by the AP (a 2-way exchange).
static void HandleGroupKey(const uint8_t* frame, uint32_t len) {
auto* k = (const EapolKey*)frame;
memcpy(g_replay, k->replay, 8);
uint16_t keyInfo = Get16Be(k->keyInfo);
uint32_t keyDataLen = Get16Be(k->keyDataLen);
if (sizeof(EapolKey) + keyDataLen > len) return;
const uint8_t* keyData = frame + sizeof(EapolKey);
uint8_t plain[MAX_KEY_DATA];
uint32_t plainLen = 0;
if (keyInfo & KEY_INFO_ENCRYPTED) {
plainLen = DecryptKeyData(keyData, keyDataLen, plain, sizeof(plain));
if (!plainLen) return;
} else if (keyDataLen <= sizeof(plain)) {
memcpy(plain, keyData, keyDataLen);
plainLen = keyDataLen;
}
const uint8_t* gtk = nullptr;
uint32_t gtkLen = 0;
uint8_t keyIdx = 0;
if (FindGtk(plain, plainLen, &gtk, &gtkLen, &keyIdx))
WpaInstallGtk(gtk, gtkLen, keyIdx, g_cfg.GroupCipher, k->rsc);
SendGroupAck();
KernelLogStream(INFO, "WiFi") << "Group key rekeyed";
}
bool WpaOnEapol(const uint8_t* data, uint32_t len) {
if (g_state == WpaState::Idle || g_state == WpaState::Failed) return false;
if (len < sizeof(EapolKey)) return false;
auto* k = (const EapolKey*)data;
if (k->type != EAPOL_TYPE_KEY) return false;
if (k->descType != EAPOL_KEY_DESC_RSN && k->descType != EAPOL_KEY_DESC_WPA)
return false;
// Trust the frame's own length field over the (possibly padded) buffer
// the driver handed us.
uint32_t declared = (uint32_t)Get16Be(k->length) + 4;
if (declared < sizeof(EapolKey) || declared > len) return false;
len = declared;
g_eapolVersion = k->version > 3 ? 2 : k->version;
g_descType = k->descType;
uint16_t keyInfo = Get16Be(k->keyInfo);
uint8_t ver = (uint8_t)(keyInfo & KEY_INFO_VERSION_MASK);
if (keyInfo & KEY_INFO_REQUEST) return false; // STA->AP direction
if (ver == KEY_DESC_VER_RC4) {
Fail("the AP asked for RC4/TKIP key wrapping, which is not supported");
return true;
}
if (ver != KEY_DESC_VER_AES && ver != KEY_DESC_VER_AES_CMAC) {
Fail("unknown EAPOL key descriptor version");
return true;
}
bool pairwise = (keyInfo & KEY_INFO_KEY_TYPE) != 0;
bool hasMic = (keyInfo & KEY_INFO_MIC) != 0;
bool hasAck = (keyInfo & KEY_INFO_ACK) != 0;
if (keyInfo & KEY_INFO_ERROR) {
Fail("the AP reported a MIC failure");
return true;
}
// Message 1 is the only frame that arrives before a PTK exists, so it
// is also the only one whose descriptor version we can adopt.
if (pairwise && hasAck && !hasMic) {
if (g_state != WpaState::WaitMsg1 && g_state != WpaState::WaitMsg3) {
// The AP restarted the handshake; take it from the top.
GenNonce(g_snonce);
}
g_keyDescVer = ver;
HandleMsg1(data, len);
return true;
}
// Everything else is MIC-protected, so the PTK has to exist first.
if (!hasMic) return false;
if (g_state == WpaState::WaitMsg1) return false;
if (!VerifyMic(data, len)) {
KernelLogStream(WARNING, "WiFi")
<< "Discarding EAPOL frame with a bad MIC";
return true;
}
if (pairwise && hasAck) {
if (g_state == WpaState::Complete) {
// Our message 4 did not reach the AP and it retried message 3.
// The keys are already in place, so just answer again.
memcpy(g_replay, k->replay, 8);
SendMsg4();
} else {
HandleMsg3(data, len);
}
} else if (!pairwise && hasAck) {
HandleGroupKey(data, len);
}
return true;
}
// =========================================================================
// Retransmission / timeout
// =========================================================================
// Unsigned deadline test that survives a `since` newer than `nowMs`. The
// caller samples the clock once per service pass while g_lastTxMs is
// stamped the moment a frame goes out, so the two can cross; a plain
// subtraction then wraps and expires every timer at once.
static bool Elapsed(uint64_t nowMs, uint64_t since, uint64_t ms) {
return nowMs > since && nowMs - since > ms;
}
void WpaService(uint64_t nowMs) {
if (g_state != WpaState::WaitMsg1 && g_state != WpaState::WaitMsg3) return;
if (g_startMs == 0) g_startMs = nowMs;
if (Elapsed(nowMs, g_startMs, HANDSHAKE_TIMEOUT_MS)) {
Fail("the AP stopped responding");
return;
}
// Only our own messages are worth retrying; while waiting for msg 1
// there is nothing to resend, the AP drives that.
if (g_state == WpaState::WaitMsg3 && g_lastTxLen
&& Elapsed(nowMs, g_lastTxMs, RETRY_INTERVAL_MS)) {
if (g_retries >= MAX_RETRIES) {
Fail("no response to message 2");
return;
}
g_retries++;
g_lastTxMs = nowMs;
WpaTxEapol(g_lastTx, g_lastTxLen);
}
}
}
+82
View File
@@ -0,0 +1,82 @@
/*
* Wpa.hpp
* WPA2/WPA3-PSK supplicant: PMK derivation and the EAPOL-Key 4-way
* handshake that unlocks the link after association.
*
* The supplicant lives in the kernel because the handshake sits between
* association and the first IP packet: nothing above the driver can send or
* receive until the pairwise key is installed in the firmware. It drives
* the exchange but does not touch the hardware itself -- transmitting and
* key installation are provided by the MLME through the three hooks at the
* bottom of this header.
*
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::Net::Wifi {
enum class WpaState : uint8_t {
Idle = 0,
WaitMsg1, // associated, waiting for the AP to start the exchange
WaitMsg3, // msg 2 sent, waiting for the GTK
Complete, // keys installed, link is usable
Failed,
};
struct WpaConfig {
uint8_t OwnMac[6];
uint8_t Bssid[6];
uint8_t Ssid[32];
uint8_t SsidLen;
char Passphrase[64];
uint8_t PassLen;
uint8_t Akm; // RSN_AKM_*
uint8_t PairwiseCipher; // RSN_CIPHER_*
uint8_t GroupCipher;
bool Mfp; // management frame protection negotiated
};
// Derive the PMK and arm the handshake. Returns false when the
// configuration names a cipher or AKM this supplicant cannot do.
bool WpaStart(const WpaConfig& cfg);
void WpaReset();
// Feed the 802.1X payload of an inbound EAPOL frame (everything after the
// LLC/SNAP header). Returns true when the frame was consumed.
bool WpaOnEapol(const uint8_t* data, uint32_t len);
// Re-send the last outbound message if the AP has gone quiet, and fail the
// handshake once it has been silent for too long. Called from the idle
// loop; `nowMs` is a monotonic millisecond clock.
void WpaService(uint64_t nowMs);
WpaState WpaGetState();
bool WpaIsComplete();
// Build the RSN information element advertising what WpaStart() was
// configured with. Returns the number of bytes written, 0 on error.
uint32_t WpaBuildRsnIe(uint8_t* out, uint32_t cap);
// Pick the pairwise/group cipher and AKM out of an AP's RSN IE. `ie`
// points at the element body (after id/len). Returns false when nothing
// in the IE is supported.
bool WpaParseApRsn(const uint8_t* ie, uint32_t len, WpaConfig& cfg);
// -------------------------------------------------------------------------
// Hooks implemented by the MLME (IwxConnect.cpp)
// -------------------------------------------------------------------------
// Transmit an EAPOL frame body (802.1X header included) to the AP.
bool WpaTxEapol(const uint8_t* body, uint32_t len);
// Install the pairwise temporal key. `cipher` is an RSN_CIPHER_* value.
bool WpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher);
// Install a group temporal key at `keyIdx`. `rsc` is the EAPOL key RSC
// field: 8 bytes, of which the low 6 are the AP's packet number.
bool WpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx,
uint8_t cipher, const uint8_t* rsc);
}