feat: wi-fi - join WPA2/WPA3-PSK networks and carry traffic like ethernet
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user