fix: fix race in IwxProcessEvents; fix WiFi bugs
This commit is contained in:
1 parent
c91de69189
commit
ef1e69ae83
7 files changed
+191
-27
No files matched your search
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 279
|
#define MONTAUK_BUILD_NUMBER 283
|
||||||
@@ -173,6 +173,10 @@ namespace Drivers::Net::Wifi {
|
|||||||
|
|
||||||
// Interrupt-to-idle-loop deferral
|
// Interrupt-to-idle-loop deferral
|
||||||
volatile bool WorkPending = false;
|
volatile bool WorkPending = false;
|
||||||
|
// When IwxProcessEvents() last ran to completion. The idle loop
|
||||||
|
// sweeps the ring on a timer as well as on the interrupt, so a single
|
||||||
|
// lost interrupt cannot silence the adapter for good.
|
||||||
|
volatile uint64_t LastPumpMs = 0;
|
||||||
|
|
||||||
IwxFwInfo Fw;
|
IwxFwInfo Fw;
|
||||||
|
|
||||||
@@ -321,6 +325,13 @@ namespace Drivers::Net::Wifi {
|
|||||||
// from any process/idle context; self-guarded against reentry.
|
// from any process/idle context; self-guarded against reentry.
|
||||||
void IwxProcessEvents();
|
void IwxProcessEvents();
|
||||||
|
|
||||||
|
// A firmware error seen inside IwxProcessEvents() is only latched there:
|
||||||
|
// reading the error table can mean a long wait for the MAC to wake, and
|
||||||
|
// the pump runs on the BSP. The dump happens here instead, from the
|
||||||
|
// service path, and at the latest before IwxStopDevice() wipes it.
|
||||||
|
bool IwxFwErrorDumpPending();
|
||||||
|
void IwxFlushFwErrorDump();
|
||||||
|
|
||||||
// Look up the firmware-advertised version of a command/notification.
|
// Look up the firmware-advertised version of a command/notification.
|
||||||
int IwxLookupCmdVer(uint8_t group, uint8_t cmd);
|
int IwxLookupCmdVer(uint8_t group, uint8_t cmd);
|
||||||
int IwxLookupNotifVer(uint8_t group, uint8_t cmd);
|
int IwxLookupNotifVer(uint8_t group, uint8_t cmd);
|
||||||
@@ -419,6 +430,9 @@ namespace Drivers::Net::Wifi {
|
|||||||
void IwxConnectRxData(const uint8_t* frame, uint32_t len);
|
void IwxConnectRxData(const uint8_t* frame, uint32_t len);
|
||||||
// Apply state changes the RX path queued (it cannot send commands itself).
|
// Apply state changes the RX path queued (it cannot send commands itself).
|
||||||
void IwxConnectService();
|
void IwxConnectService();
|
||||||
|
// Whether IwxConnectService() has anything to do: queued RX-path work, or
|
||||||
|
// a join in progress whose retransmission/give-up timers need watching.
|
||||||
|
bool IwxConnectHasWork();
|
||||||
int IwxConnectState();
|
int IwxConnectState();
|
||||||
|
|
||||||
// Link status for the network stack.
|
// Link status for the network stack.
|
||||||
|
|||||||
@@ -1091,6 +1091,18 @@ namespace Drivers::Net::Wifi {
|
|||||||
__atomic_clear(&g_inService, __ATOMIC_RELEASE);
|
__atomic_clear(&g_inService, __ATOMIC_RELEASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IwxConnectHasWork() {
|
||||||
|
if (g_teardownPending || g_linkLostPending
|
||||||
|
|| g_sendAssocPending || g_postAssocPending)
|
||||||
|
return true;
|
||||||
|
if (g_eapolTail != g_eapolHead) return true;
|
||||||
|
// Every state between "contexts up" and "connected" runs on timers
|
||||||
|
// (retransmission, give-up, the handshake's own retries).
|
||||||
|
return g_state != ConnState::Idle
|
||||||
|
&& g_state != ConnState::Connected
|
||||||
|
&& g_state != ConnState::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
bool IwxConnectRefusedForSecurity() { return g_refusedForSecurity; }
|
bool IwxConnectRefusedForSecurity() { return g_refusedForSecurity; }
|
||||||
|
|
||||||
int IwxConnectState() {
|
int IwxConnectState() {
|
||||||
|
|||||||
@@ -711,6 +711,9 @@ namespace Drivers::Net::Wifi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IwxStopDevice() {
|
void IwxStopDevice() {
|
||||||
|
// Last chance to read the firmware's error table before the reset
|
||||||
|
// below wipes it.
|
||||||
|
IwxFlushFwErrorDump();
|
||||||
IwxDisableInterrupts();
|
IwxDisableInterrupts();
|
||||||
|
|
||||||
if (IwxNicLock()) {
|
if (IwxNicLock()) {
|
||||||
@@ -1647,15 +1650,63 @@ namespace Drivers::Net::Wifi {
|
|||||||
IwxWrite32(IWX_RFH_Q0_FRBDCB_WIDX_TRG, widx & ~7u);
|
IwxWrite32(IWX_RFH_Q0_FRBDCB_WIDX_TRG, widx & ~7u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Latched by IwxProcessEvents(), performed by IwxFlushFwErrorDump().
|
||||||
|
static volatile bool g_fwDumpPending = false;
|
||||||
|
|
||||||
|
bool IwxFwErrorDumpPending() { return g_fwDumpPending; }
|
||||||
|
|
||||||
|
void IwxFlushFwErrorDump() {
|
||||||
|
if (!__atomic_exchange_n(&g_fwDumpPending, false, __ATOMIC_ACQ_REL)) return;
|
||||||
|
IwxDumpFwError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first fatal error moves the adapter to Error; repeats of the same
|
||||||
|
// cause before the reset lands are not news and must not re-log.
|
||||||
|
static void IwxNoteFatal(const char* what, bool dump) {
|
||||||
|
if (g_iwx.State == IwxFwState::Error) return;
|
||||||
|
KernelLogStream(ERROR, "WiFi") << what;
|
||||||
|
g_iwx.State = IwxFwState::Error;
|
||||||
|
g_iwx.FwErrors++;
|
||||||
|
if (dump) g_fwDumpPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
void IwxProcessEvents() {
|
void IwxProcessEvents() {
|
||||||
if (!g_iwx.Mmio) return;
|
if (!g_iwx.Mmio) return;
|
||||||
// Never nest. With interrupts enabled during command waits this is a
|
// 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.
|
// 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_iwx.InProcessEvents.test_and_set(std::memory_order_acquire)) return;
|
||||||
|
|
||||||
|
// Consume the wakeup BEFORE looking at the hardware, never after.
|
||||||
|
// This used to be cleared on the way out, after the automask had
|
||||||
|
// already been re-armed below and after the ring sweep -- which hands
|
||||||
|
// received frames to the network stack and so is anything but short.
|
||||||
|
// An interrupt landing in that window set the flag only for it to be
|
||||||
|
// wiped a moment later: the work was lost, and because MSI-X masks
|
||||||
|
// the vector each time it fires until the driver re-arms it here, no
|
||||||
|
// further interrupt could ever arrive to replace it. The adapter went
|
||||||
|
// permanently silent -- no RX, no TX completions, so not even the link
|
||||||
|
// supervision noticed -- while the link was still reported up.
|
||||||
|
// Clearing first means an interrupt at any point after this line
|
||||||
|
// leaves the flag set and earns another pass.
|
||||||
|
g_iwx.WorkPending = false;
|
||||||
|
__atomic_thread_fence(__ATOMIC_SEQ_CST);
|
||||||
|
|
||||||
if (g_msix) {
|
if (g_msix) {
|
||||||
uint32_t fh = IwxRead32(IWX_CSR_MSIX_FH_INT_CAUSES_AD);
|
uint32_t fh = IwxRead32(IWX_CSR_MSIX_FH_INT_CAUSES_AD);
|
||||||
uint32_t hwc = IwxRead32(IWX_CSR_MSIX_HW_INT_CAUSES_AD);
|
uint32_t hwc = IwxRead32(IWX_CSR_MSIX_HW_INT_CAUSES_AD);
|
||||||
|
// All-ones is not a cause word, it is what a read returns when the
|
||||||
|
// device has dropped off the bus (or its link is down). Taken at
|
||||||
|
// face value it reads as every cause at once, firmware errors
|
||||||
|
// included, and would be "handled" -- dump and all -- on every
|
||||||
|
// single pass. Mid-bring-up a reset can produce the same read
|
||||||
|
// transiently, so only a running adapter is declared gone.
|
||||||
|
if (fh == 0xffffffff && hwc == 0xffffffff) {
|
||||||
|
if (g_iwx.State == IwxFwState::Running)
|
||||||
|
IwxNoteFatal("Adapter is not responding on the bus", false);
|
||||||
|
g_iwx.LastPumpMs = Timekeeping::GetMilliseconds();
|
||||||
|
g_iwx.InProcessEvents.clear(std::memory_order_release);
|
||||||
|
return;
|
||||||
|
}
|
||||||
IwxWrite32(IWX_CSR_MSIX_FH_INT_CAUSES_AD, fh);
|
IwxWrite32(IWX_CSR_MSIX_FH_INT_CAUSES_AD, fh);
|
||||||
IwxWrite32(IWX_CSR_MSIX_HW_INT_CAUSES_AD, hwc);
|
IwxWrite32(IWX_CSR_MSIX_HW_INT_CAUSES_AD, hwc);
|
||||||
fh &= g_fhMask;
|
fh &= g_fhMask;
|
||||||
@@ -1673,15 +1724,10 @@ namespace Drivers::Net::Wifi {
|
|||||||
if ((fh & IWX_MSIX_FH_INT_CAUSES_FH_ERR)
|
if ((fh & IWX_MSIX_FH_INT_CAUSES_FH_ERR)
|
||||||
|| (hwc & IWX_MSIX_HW_INT_CAUSES_REG_SW_ERR)
|
|| (hwc & IWX_MSIX_HW_INT_CAUSES_REG_SW_ERR)
|
||||||
|| (hwc & IWX_MSIX_HW_INT_CAUSES_REG_SW_ERR_V2)) {
|
|| (hwc & IWX_MSIX_HW_INT_CAUSES_REG_SW_ERR_V2)) {
|
||||||
KernelLogStream(ERROR, "WiFi") << "Fatal firmware error";
|
IwxNoteFatal("Fatal firmware error", true);
|
||||||
g_iwx.State = IwxFwState::Error;
|
|
||||||
g_iwx.FwErrors++;
|
|
||||||
IwxDumpFwError();
|
|
||||||
}
|
}
|
||||||
if (hwc & IWX_MSIX_HW_INT_CAUSES_REG_HW_ERR) {
|
if (hwc & IWX_MSIX_HW_INT_CAUSES_REG_HW_ERR) {
|
||||||
KernelLogStream(ERROR, "WiFi") << "Hardware error";
|
IwxNoteFatal("Hardware error", false);
|
||||||
g_iwx.State = IwxFwState::Error;
|
|
||||||
g_iwx.FwErrors++;
|
|
||||||
}
|
}
|
||||||
if (hwc & IWX_MSIX_HW_INT_CAUSES_REG_RF_KILL) {
|
if (hwc & IWX_MSIX_HW_INT_CAUSES_REG_RF_KILL) {
|
||||||
if (IwxCheckRfKill()) {
|
if (IwxCheckRfKill()) {
|
||||||
@@ -1703,10 +1749,7 @@ namespace Drivers::Net::Wifi {
|
|||||||
IwxWrite32(IWX_RFH_Q0_FRBDCB_WIDX_TRG, 8);
|
IwxWrite32(IWX_RFH_Q0_FRBDCB_WIDX_TRG, 8);
|
||||||
}
|
}
|
||||||
if (r1 & (1u << 25)) { // SW_ERR
|
if (r1 & (1u << 25)) { // SW_ERR
|
||||||
KernelLogStream(ERROR, "WiFi") << "Fatal firmware error";
|
IwxNoteFatal("Fatal firmware error", true);
|
||||||
g_iwx.State = IwxFwState::Error;
|
|
||||||
g_iwx.FwErrors++;
|
|
||||||
IwxDumpFwError();
|
|
||||||
}
|
}
|
||||||
if (r1 & ((1u << 31) | (1u << 3) | (1u << 28))) // FH_RX/SW_RX/periodic
|
if (r1 & ((1u << 31) | (1u << 3) | (1u << 28))) // FH_RX/SW_RX/periodic
|
||||||
IwxNotifIntr();
|
IwxNotifIntr();
|
||||||
@@ -1717,7 +1760,7 @@ namespace Drivers::Net::Wifi {
|
|||||||
// been consumed by another poller, so always sweep the ring.
|
// been consumed by another poller, so always sweep the ring.
|
||||||
IwxNotifIntr();
|
IwxNotifIntr();
|
||||||
|
|
||||||
g_iwx.WorkPending = false;
|
g_iwx.LastPumpMs = Timekeeping::GetMilliseconds();
|
||||||
g_iwx.InProcessEvents.clear(std::memory_order_release);
|
g_iwx.InProcessEvents.clear(std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
#include <CppLib/Spinlock.hpp>
|
#include <CppLib/Spinlock.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/ApicTimer.hpp>
|
||||||
#include <Hal/SmpBoot.hpp>
|
#include <Hal/SmpBoot.hpp>
|
||||||
|
#include <Hal/Apic/Apic.hpp>
|
||||||
|
#include <Hal/Apic/Interrupts.hpp>
|
||||||
#include <Sched/Scheduler.hpp>
|
#include <Sched/Scheduler.hpp>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
@@ -417,9 +419,52 @@ namespace Drivers::Net::Wifi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At most one core inside the blocking half at a time.
|
||||||
|
static volatile bool g_blockingBusy = false;
|
||||||
|
static volatile uint64_t g_lastApKickMs = 0;
|
||||||
|
static constexpr uint64_t AP_KICK_INTERVAL_MS = 10;
|
||||||
|
|
||||||
|
// The adapter is swept on a timer as well as on its interrupt whenever
|
||||||
|
// anything is in flight. Cheap (two register reads when idle), and it
|
||||||
|
// means no single lost interrupt can leave the link silently dead.
|
||||||
|
static constexpr uint64_t PUMP_POLL_MS = 100;
|
||||||
|
|
||||||
|
// True when the caller should leave the blocking work alone because an AP
|
||||||
|
// has it (or has just been asked to take it).
|
||||||
|
static bool LeaveToAp() {
|
||||||
|
auto* cpu = Smp::GetCurrentCpuData();
|
||||||
|
if (!cpu || cpu->cpuIndex != 0) return false;
|
||||||
|
if (g_blockingBusy) return true;
|
||||||
|
|
||||||
|
uint64_t now = Timekeeping::GetMilliseconds();
|
||||||
|
uint64_t last = g_lastApKickMs;
|
||||||
|
if (last && now >= last && now - last < AP_KICK_INTERVAL_MS) return true;
|
||||||
|
|
||||||
|
for (int i = 1; i < Smp::GetCpuCount(); i++) {
|
||||||
|
auto* t = Smp::GetCpuData(i);
|
||||||
|
if (!t || !t->started) continue;
|
||||||
|
if (t->currentSlot >= 0 || t->reservedForKernelWork) continue;
|
||||||
|
if (Hal::LocalApic::SendFixedIpi(t->lapicId,
|
||||||
|
Hal::IRQ_VECTOR_BASE + Hal::IRQ_RESCHEDULE)) {
|
||||||
|
g_lastApKickMs = now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool InitDue() {
|
||||||
|
return g_initPending.load(std::memory_order_acquire) && !g_initialized
|
||||||
|
&& Fs::Vfs::IsDriveRegistered(0);
|
||||||
|
}
|
||||||
|
|
||||||
void ServiceDeferredInit() {
|
void ServiceDeferredInit() {
|
||||||
if (!g_initPending.load(std::memory_order_relaxed) || g_initialized) return;
|
if (!InitDue()) return;
|
||||||
if (!Fs::Vfs::IsDriveRegistered(0)) return; // ramdisk not mounted yet
|
if (LeaveToAp()) return;
|
||||||
|
if (__atomic_test_and_set(&g_blockingBusy, __ATOMIC_ACQUIRE)) return;
|
||||||
|
struct Guard {
|
||||||
|
~Guard() { __atomic_clear(&g_blockingBusy, __ATOMIC_RELEASE); }
|
||||||
|
} guard;
|
||||||
|
|
||||||
bool expected = true;
|
bool expected = true;
|
||||||
if (!g_initPending.compare_exchange_strong(expected, false,
|
if (!g_initPending.compare_exchange_strong(expected, false,
|
||||||
@@ -440,20 +485,30 @@ namespace Drivers::Net::Wifi {
|
|||||||
static void ServiceAsync();
|
static void ServiceAsync();
|
||||||
static void ServiceRecovery();
|
static void ServiceRecovery();
|
||||||
|
|
||||||
bool HasDeferredWork() {
|
// Whether the RX ring is due a sweep regardless of the interrupt.
|
||||||
if (g_initPending.load(std::memory_order_acquire) &&
|
static bool PumpDue(uint64_t now) {
|
||||||
!g_initialized && Fs::Vfs::IsDriveRegistered(0)) {
|
if (g_iwx.State != IwxFwState::Running) return false;
|
||||||
return true;
|
if (IwxConnectState() == (int)IwxConnStateId::Idle && !g_iwx.ScanActive)
|
||||||
}
|
return false;
|
||||||
if (!g_iwx.Mmio) return false;
|
uint64_t last = g_iwx.LastPumpMs;
|
||||||
if (g_iwx.WorkPending) return true;
|
return now > last && now - last >= PUMP_POLL_MS;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t now = Timekeeping::GetMilliseconds();
|
// Work that may send firmware commands, i.e. wait on the adapter.
|
||||||
if (g_scanDeadline != 0 && now >= g_scanDeadline) return true;
|
static bool HasBlockingWork(uint64_t now) {
|
||||||
|
// A finished sweep only needs its generation bumped; an overrunning
|
||||||
|
// one needs aborting. Both live in ServiceAsync().
|
||||||
|
if (g_scanDeadline != 0 && (now >= g_scanDeadline || !g_iwx.ScanActive))
|
||||||
|
return true;
|
||||||
// The MLME/WPA state machine owns sub-second retransmission timers in
|
// The MLME/WPA state machine owns sub-second retransmission timers in
|
||||||
// addition to the overall async deadline. Service it until ServiceAsync
|
// addition to the overall async deadline. Service it until ServiceAsync
|
||||||
// observes Connected/Failed/Idle and clears this flag.
|
// observes Connected/Failed/Idle and clears this flag.
|
||||||
if (g_asyncConnect) return true;
|
if (g_asyncConnect) return true;
|
||||||
|
// Work the RX path queued for IwxConnectService(). This used to ride
|
||||||
|
// on WorkPending, but the pump that clears that flag may now run on a
|
||||||
|
// different core from the one that services the connection.
|
||||||
|
if (IwxConnectHasWork()) return true;
|
||||||
|
if (IwxFwErrorDumpPending()) return true;
|
||||||
if (g_iwx.State == IwxFwState::Error && g_initialized &&
|
if (g_iwx.State == IwxFwState::Error && g_initialized &&
|
||||||
!g_recoveryGaveUp &&
|
!g_recoveryGaveUp &&
|
||||||
(g_lastRecoveryMs == 0 ||
|
(g_lastRecoveryMs == 0 ||
|
||||||
@@ -463,9 +518,30 @@ namespace Drivers::Net::Wifi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HasDeferredWork() {
|
||||||
|
if (InitDue()) return true;
|
||||||
|
if (!g_iwx.Mmio) return false;
|
||||||
|
if (g_iwx.WorkPending) return true;
|
||||||
|
|
||||||
|
uint64_t now = Timekeeping::GetMilliseconds();
|
||||||
|
if (PumpDue(now)) return true;
|
||||||
|
return HasBlockingWork(now);
|
||||||
|
}
|
||||||
|
|
||||||
void ServiceEvents() {
|
void ServiceEvents() {
|
||||||
if (!g_iwx.Mmio) return;
|
if (!g_iwx.Mmio) return;
|
||||||
if (g_iwx.WorkPending) IwxProcessEvents();
|
|
||||||
|
// The non-blocking half: any core, the BSP included.
|
||||||
|
uint64_t now = Timekeeping::GetMilliseconds();
|
||||||
|
if (g_iwx.WorkPending || PumpDue(now)) IwxProcessEvents();
|
||||||
|
|
||||||
|
now = Timekeeping::GetMilliseconds();
|
||||||
|
if (!HasBlockingWork(now)) return;
|
||||||
|
if (LeaveToAp()) return;
|
||||||
|
if (__atomic_test_and_set(&g_blockingBusy, __ATOMIC_ACQUIRE)) return;
|
||||||
|
struct Guard {
|
||||||
|
~Guard() { __atomic_clear(&g_blockingBusy, __ATOMIC_RELEASE); }
|
||||||
|
} guard;
|
||||||
|
|
||||||
// Everything below can send firmware commands, and each one waits by
|
// Everything below can send firmware commands, and each one waits by
|
||||||
// busy-spinning. This runs from ServiceDeferredWork(), which has set
|
// busy-spinning. This runs from ServiceDeferredWork(), which has set
|
||||||
@@ -487,6 +563,11 @@ namespace Drivers::Net::Wifi {
|
|||||||
|
|
||||||
if (ownsPass) IwxEndServicePass();
|
if (ownsPass) IwxEndServicePass();
|
||||||
|
|
||||||
|
// A firmware error the pump latched. Normally the reset below dumps
|
||||||
|
// it on its way through IwxStopDevice(); this catches the cases where
|
||||||
|
// no reset is coming (recovery exhausted, or the adapter never came up).
|
||||||
|
IwxFlushFwErrorDump();
|
||||||
|
|
||||||
// Deliberately outside the budget: a reset is a bring-up, not a pass of
|
// Deliberately outside the budget: a reset is a bring-up, not a pass of
|
||||||
// routine servicing, and it has its own wall-clock handshakes to run.
|
// routine servicing, and it has its own wall-clock handshakes to run.
|
||||||
ServiceRecovery();
|
ServiceRecovery();
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ def link_of(result):
|
|||||||
f = result.split()
|
f = result.split()
|
||||||
return int(f[f.index('LINK') + 1]) if 'LINK' in f else -1
|
return int(f[f.index('LINK') + 1]) if 'LINK' in f else -1
|
||||||
|
|
||||||
|
def work_of(result):
|
||||||
|
f = result.split()
|
||||||
|
return int(f[f.index('WORK') + 1]) if 'WORK' in f else -1
|
||||||
|
|
||||||
def check(cond, what):
|
def check(cond, what):
|
||||||
print(f" {'PASS' if cond else 'FAIL'} {what}")
|
print(f" {'PASS' if cond else 'FAIL'} {what}")
|
||||||
if not cond:
|
if not cond:
|
||||||
@@ -656,6 +660,9 @@ d8.cmd("RXMGMT " + mgmt(0x10, STA, AP, AP,
|
|||||||
struct.pack('<HHH', 0x0421, 0, 7 | 0xc000)).hex())
|
struct.pack('<HHH', 0x0421, 0, 7 | 0xc000)).hex())
|
||||||
ev = d8.cmd("SERVICE")
|
ev = d8.cmd("SERVICE")
|
||||||
check(link_of(ev['result']) == 1, "the open network is associated and the link is up")
|
check(link_of(ev['result']) == 1, "the open network is associated and the link is up")
|
||||||
|
# The idle loop only hands a connection to a core for servicing when this
|
||||||
|
# says there is something to do, so a settled link must report nothing.
|
||||||
|
check(work_of(ev['result']) == 0, "a settled link has no service work pending")
|
||||||
|
|
||||||
# Well short of the threshold: a few unacknowledged frames are ordinary.
|
# Well short of the threshold: a few unacknowledged frames are ordinary.
|
||||||
ev = d8.cmd("TXSTATUS 0 15")
|
ev = d8.cmd("TXSTATUS 0 15")
|
||||||
@@ -682,6 +689,10 @@ check(link_of(ev['result']) == 1,
|
|||||||
"the transmit path itself does not tear anything down")
|
"the transmit path itself does not tear anything down")
|
||||||
check(not ev['CMD'],
|
check(not ev['CMD'],
|
||||||
"no firmware command is sent from the completion path")
|
"no firmware command is sent from the completion path")
|
||||||
|
# The pump that noticed this may run on a different core from the one that
|
||||||
|
# services the connection; the pending teardown has to be visible by itself.
|
||||||
|
check(work_of(ev['result']) == 1,
|
||||||
|
"the pending teardown is reported as service work")
|
||||||
|
|
||||||
ev = d8.cmd("SERVICE")
|
ev = d8.cmd("SERVICE")
|
||||||
check(state_of(ev['result']) == 0 and link_of(ev['result']) == 0,
|
check(state_of(ev['result']) == 0 and link_of(ev['result']) == 0,
|
||||||
@@ -689,6 +700,7 @@ check(state_of(ev['result']) == 0 and link_of(ev['result']) == 0,
|
|||||||
check(any(c == MAC_CONFIG and struct.unpack_from('<I', p, 4)[0] == 3
|
check(any(c == MAC_CONFIG and struct.unpack_from('<I', p, 4)[0] == 3
|
||||||
for c, p in ev['CMD']),
|
for c, p in ev['CMD']),
|
||||||
"and unwinds the firmware contexts")
|
"and unwinds the firmware contexts")
|
||||||
|
check(work_of(ev['result']) == 0, "and leaves no service work behind")
|
||||||
d8.p.stdin.close()
|
d8.p.stdin.close()
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|||||||
@@ -204,10 +204,12 @@ int main() {
|
|||||||
unsigned acked, count;
|
unsigned acked, count;
|
||||||
sscanf(line.c_str(), "TXSTATUS %u %u", &acked, &count);
|
sscanf(line.c_str(), "TXSTATUS %u %u", &acked, &count);
|
||||||
for (unsigned i = 0; i < count; i++) IwxConnectNoteTx(acked != 0);
|
for (unsigned i = 0; i < count; i++) IwxConnectNoteTx(acked != 0);
|
||||||
printf("DONE STATE %d LINK %d\n", IwxConnectState(), IwxLinkUp() ? 1 : 0);
|
printf("DONE STATE %d LINK %d WORK %d\n", IwxConnectState(),
|
||||||
|
IwxLinkUp() ? 1 : 0, IwxConnectHasWork() ? 1 : 0);
|
||||||
} else if (line.rfind("SERVICE", 0) == 0) {
|
} else if (line.rfind("SERVICE", 0) == 0) {
|
||||||
IwxConnectService();
|
IwxConnectService();
|
||||||
printf("DONE STATE %d LINK %d\n", IwxConnectState(), IwxLinkUp() ? 1 : 0);
|
printf("DONE STATE %d LINK %d WORK %d\n", IwxConnectState(),
|
||||||
|
IwxLinkUp() ? 1 : 0, IwxConnectHasWork() ? 1 : 0);
|
||||||
} else if (line.rfind("TICK ", 0) == 0) {
|
} else if (line.rfind("TICK ", 0) == 0) {
|
||||||
unsigned ms; sscanf(line.c_str(), "TICK %u", &ms);
|
unsigned ms; sscanf(line.c_str(), "TICK %u", &ms);
|
||||||
Timekeeping::g_ms += ms;
|
Timekeeping::g_ms += ms;
|
||||||
|
|||||||
Reference in new issue
Block a user