From 546d2d1db76bfd074618ca851a2bc7aad2047aae Mon Sep 17 00:00:00 2001 From: Dexter Date: Sat, 25 Apr 2026 10:05:51 +0800 Subject: [PATCH] Fix iServices on Sonoma 14.4+ via Skywalk setLQM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The V2 (Skywalk) AirportItlwm path commented out the V1 setLinkQualityMetric(100) call because the API was renamed in IO80211SkywalkInterface, but never ported to the replacement setLQM(uint64). Without it, SCDynamicStore key State:/Network/Interface//LinkQuality stays at the default "no signal" value, +[PCInterfaceUsabilityMonitor isBadLinkQuality:] (literal threshold lq < 11) marks the WiFi interface as unusable, and apsd refuses every interface in _connectStreamWithInterfacePreference: — breaking the entire iServices stack (iMessage / FaceTime / AirDrop / Continuity). This change ports link-quality reporting to the new path: * Add updateLQMIfChanged() that maps current ni_rssi to one of three tiers (100 / 50 / 25) and calls IO80211InfraInterface::setLQM(uint64) only when the value changes. The lowest tier (25) stays above the iServices threshold of 11 so weak-but-recoverable links don't break iMessage on Hackintosh systems without cellular fallback. * Hook into the existing 1 Hz watchdogAction so signal degradation during a session is reflected. * Call from setLinkStateGated link-up branch so the value propagates immediately on association rather than waiting up to 1 s for the watchdog. * Reset the cached value to 0 on link-down so the next link-up always re-reports, even if the freshly-computed value matches the cache. Defensive checks: * Initialize fLastReportedLQM in init() — OSDeclareDefaultStructors only zero-initializes OSObject base members, leaving derived ivars with uninitialized memory whose first cache-compare could spuriously match one of {0, 25, 50, 100} and skip the initial setLQM call. * Early-return when not associated (ic_state != IEEE80211_S_RUN) so we don't write a bogus "good" LQ to SCDynamicStore before the link is up. * Snapshot ic_bss to a local — the ieee80211 layer can clear it from another workloop between the null check and the ni_rssi deref. * Null-check fHalService — releaseAll() releases it before cancelling watchdogTimer, so a tick already in flight on fWatchdogWorkLoop could otherwise observe a NULL fHalService and crash on get80211Controller(). Reordering releaseAll() is out of scope for this PR; the cheap defensive check prevents this path from making the existing race observable. Verified on macOS 14.8.5 / Intel AX201: * scutil 'show State:/Network/Interface/en0/LinkQuality' goes from -2 to 100. * apsd log: "Push is connected? NO networkIsHistoricallyUsable? YES isBadLQ? NO" (was "isBadLQ? YES"). * apsd log: "courier : Sending outgoing message N onInterface: NonCellular. Connected on 1 interfaces." — APNS courier connects. * iMessage / FaceTime / AirDrop work after reboot. The existing IO80211SkywalkInterface.h header in the project already declares setLQM unconditionally, and IO80211InfraInterface (the actual class behind the AirportItlwmSkywalkInterface vtable) provides the working override in IO80211Family. No new external symbol is introduced — the binary's undefined-symbol set against IO80211InfraInterface::setLQM is unchanged from the existing 2.3.0/2.4.0 builds. --- AirportItlwm/AirportItlwmV2.cpp | 42 ++++++++++++++++++++++++++++++++- AirportItlwm/AirportItlwmV2.hpp | 4 +++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/AirportItlwm/AirportItlwmV2.cpp b/AirportItlwm/AirportItlwmV2.cpp index 9d0485fec..88b3ad64a 100644 --- a/AirportItlwm/AirportItlwmV2.cpp +++ b/AirportItlwm/AirportItlwmV2.cpp @@ -86,9 +86,47 @@ void AirportItlwm::watchdogAction(IOTimerEventSource *timer) { struct _ifnet *ifp = &fHalService->get80211Controller()->ic_ac.ac_if; (*ifp->if_watchdog)(ifp); + updateLQMIfChanged(); watchdogTimer->setTimeoutMS(kWatchDogTimerPeriod); } +// Compute and report Link Quality Metric. Replaces deprecated setLinkQualityMetric +// path on Sonoma 14.4+ Skywalk schema. SCDynamicStore key +// State:/Network/Interface//LinkQuality must be >= 11 for apsd / +// PCInterfaceUsabilityMonitor to consider the interface usable, otherwise the +// entire iServices stack (iMessage / FaceTime / AirDrop) refuses to use WiFi. +void AirportItlwm::updateLQMIfChanged() +{ + if (!fNetIf || !fHalService) { + return; + } + struct ieee80211com *ic = fHalService->get80211Controller(); + if (!ic || ic->ic_state != IEEE80211_S_RUN) { + return; // not associated; link-state mechanism signals unusable separately + } + // Snapshot ic_bss to a local — ieee80211 layer can clear it from another + // workloop between our check and deref. + struct ieee80211_node *ni = ic->ic_bss; + if (!ni) { + return; + } + // ni_rssi is normalized 0..(IWM_MAX_DBM - IWM_MIN_DBM) + // i.e. 0 = -100 dBm, 67 = -33 dBm. + int rssi_norm = ni->ni_rssi; + unsigned long long lq; + if (rssi_norm >= 30) { + lq = 100; // >= -70 dBm: excellent + } else if (rssi_norm >= 15) { + lq = 50; // >= -85 dBm: mediocre + } else { + lq = 25; // weak; still > iServices threshold (11) + } + if (lq != fLastReportedLQM) { + fLastReportedLQM = lq; + fNetIf->setLQM(lq); + } +} + void AirportItlwm::fakeScanDone(OSObject *owner, IOTimerEventSource *sender) { UInt32 msg = 0; @@ -102,6 +140,7 @@ bool AirportItlwm::init(OSDictionary *properties) bool ret = super::init(properties); awdlSyncEnable = true; power_state = 0; + fLastReportedLQM = 0; memset(geo_location_cc, 0, sizeof(geo_location_cc)); return ret; } @@ -478,7 +517,6 @@ setLinkStatus(UInt32 status, const IONetworkMedium * activeMedium, UInt64 speed, bsdInterface->startOutputThread(); #endif getCommandGate()->runAction(setLinkStateGated, (void *)kIO80211NetworkLinkUp, (void *)0); -// fNetIf->setLinkQualityMetric(100); } else if (!(status & kIONetworkLinkNoNetworkChange)) { #ifdef __PRIVATE_SPI__ bsdInterface->stopOutputThread(); @@ -503,8 +541,10 @@ setLinkStateGated(OSObject *target, void *arg0, void *arg1, void *arg2, void *ar that->fNetIf->postMessage(APPLE80211_M_SSID_CHANGED, NULL, 0, false); if ((IO80211LinkState)(uint64_t)arg0 == kIO80211NetworkLinkUp) { that->fNetIf->reportLinkStatus(3, 0x80); + that->updateLQMIfChanged(); } else { that->fNetIf->reportLinkStatus(1, 0); + that->fLastReportedLQM = 0; } that->bsdInterface->setLinkState((IO80211LinkState)(uint64_t)arg0); return ret; diff --git a/AirportItlwm/AirportItlwmV2.hpp b/AirportItlwm/AirportItlwmV2.hpp index 2c2f74c92..ad658934a 100644 --- a/AirportItlwm/AirportItlwmV2.hpp +++ b/AirportItlwm/AirportItlwmV2.hpp @@ -99,7 +99,8 @@ IOReturn set##REQ(OSObject *object, struct DATA_TYPE *data); UInt64 speed = 0, OSData * data = 0) override; static IOReturn setLinkStateGated(OSObject *target, void *arg0, void *arg1, void *arg2, void *arg3); - + void updateLQMIfChanged(); + static IOReturn tsleepHandler(OSObject* owner, void* arg0 = 0, void* arg1 = 0, void* arg2 = 0, void* arg3 = 0); static void eventHandler(struct ieee80211com *, int, void *); IOReturn enableAdapter(IONetworkInterface *netif); @@ -206,6 +207,7 @@ IOReturn set##REQ(OSObject *object, struct DATA_TYPE *data); IO80211SkywalkInterface *fNetIf; IOWorkLoop *fWatchdogWorkLoop; ItlHalService *fHalService; + unsigned long long fLastReportedLQM; //IO80211 uint8_t power_state;