AirportItlwm: report Link Quality via Skywalk setLQM to fix iServices on macOS 14.4+#1056
AirportItlwm: report Link Quality via Skywalk setLQM to fix iServices on macOS 14.4+#1056DexterSLamb wants to merge 1 commit into
Conversation
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/<if>/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 <private>: 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.
|
@DexterSLamb This sounds promising. Would this work with Sequoia and Tahoe if successfully tested and merged with the main branch? |
I only tested on Sonoma 14.8.5 (Intel AX201). The fix targets If anyone running Sequoia or Tahoe can build from this branch and report back,
If both pass, iServices (iMessage / FaceTime / Continuity / iCloud Drive push) |
|
Hi, I have AX200 on an AMD 7730U platform (Zenbook UM3402Y) on Sequoia 15.7.1 and full Xcode locally. I'm building the Sonoma144 target right now from this branch and will report back I had a local tahoe as well, but I installed the 26.4 beta and it bricked boot, so I might need to fix that first |
|
Both the built from actions and my own compile of the Sonoma144 variant produce failures, before the kext is even loaded: It seems that the symbols in the kernel collection for Sequoia have changed sufficiently as to prevent loading? It's the only kext that produces this issue. I don't have a Sonoma partition to attempt to distinguish if it's my configuration that's an issue or not, but I doubt that the kext injection configuration I'm using is causing issues. Here's the snippet: FYI: ^ |
|
Changing AppleSecureBoot to Default (was disabled) doesn't do anything either. Sometimes this was recommended to replace the OC kernelcollection generation with Apple's, and also attempted forcing IO80211Family to load which used to be required. neither did anything For a while, one had to use OCLP on Sequoia to downgrade the 80211 stack to load AirportItlwm at all, and then it would be from Ventura (https://github.com/5T33Z0/OCLP4Hackintosh/blob/main/Enable_Features/AirportItllwm_Sequoia.md) Which of course would bypass the issue entirely since the wifi stack is just being downgraded. I can also try to kill AMFI and SIP enough to load the kext at runtime with -- So
Presumably this is true for the fix, but not necessarily the rest of the code stack--surely it wouldn't work on Sequoia without other updates/function patches? That would have to be achieved first Hmm, but checking for symbol resolution in userspace doesn't show any issues: So is this an OpenCore issue? I can't test injecting on known-working Sonoma, because I don't have that install available... |
|
That kmutil libraries output is super useful, thanks for grabbing it. It rules out the simple missing-symbol case I was first suspecting, and shifts the Our Info.plist for the Sonoma144 target declares these OSBundleLibraries: If Apple bumped the OSBundleCompatibleVersion of IO80211Family (or its successor IO80211FamilyV2) or IOSkywalkFamily on Sequoia past those numbers, OC's Two things that would help confirm:
You're right that the setLQM patch is one piece, and the rest of the V2 stack absolutely needs to be checked for runtime ABI breakage on 15.x. But right Re OCLP: I'd like to avoid that route on principle, the goal here is to make AirportItlwm itself work on the new OS, not to downgrade the OS to fit I'm setting up Sequoia 15.7.5 locally so I can do the binary/Info.plist diff myself rather than ping-ponging through your machine. Will report back once |
|
Lines from Sequoia: (notice that there's no IO80211FamilyV2.. ?) I couldn't figure out how to get OC to log more than that single line... probably has to do with the Target/DisplayLevel... DisplayLevel I changed but only produced that one line, which I know is incorrect, and I should probably do the bitmask math manually to enable specific options that I want rather than just using the one from the dortnaia guide) What's your progress setting up 15.7.5 Sequoia on your machine? It's within the ballpark of mine at 15.7.1 anyway, so I really doubt updating it would do anything. certianly by now the API/ABIs are stable for Sequoia drivers |
|
@DexterSLamb have you looked at this repo which has reversed engineered Apple WiFi services? |
|
Quick update. Did some symbol diffing today between Sonoma 14.8.5 and Sequoia 15.7.5 IO80211Family. The "massive API rewrite" framing turns out to be Going to take a real shot at this over the May 1 long weekend. Plan: add the Sequoia15 target, patch the 4 confirmed signature changes, push to fork, @jzrodriguez98 thanks for the apple-unofficial-ecosystem pointer, looked at it but it's userspace stuff, not relevant here. Appreciated anyway. Wish me luck. |
|
@DexterSLamb Sounds good, I might also take a crack at the fix or rather try to document some more of the call sites. Could you possibly provide the symbol diff from 14.8.5 and 15.7.5 and possible 14.8.5 IO80211Family |
|
Yes happy to. Easiest is I push the raw lists and methodology notes into a research/sequoia-port/ directory on the fork so you can pull them, will do The headline files:
The 4 confirmed signature renames I found by fuzzy-matching same-name same-class methods: Reproducing on your end (you have the better data point, a Sequoia install with full SystemKernelExtensions.kc): That last comm is the one I really want — it'll cut the 19 unknowns down to whatever is actually deleted vs just relocated to System KC. If you do that Pushing the data to fork shortly. |
|
I can get some stuff in a bit... I saw you've been committing to your repo, last like 15 minutes even? The fuzzy matching seems good. What has been your progress on those commits you've been doing overall? |
|
Hey, thanks for following along! Yeah, I've been pushing a lot — spent the last few days deep in this. Honest summary: real progress on the discovery/visibility side, but not yet at "WiFi actually connects". Let me share what I've nailed down so the next person doesn't have to re-discover it. Driver loads cleanly, no boot panic Wall I hit (the actual scan/associate path): Sandbox MACF (mac_iokit_check_open_service in xnu): airportd's entitlement com.apple.security.temporary-exception.iokit-user-client-class is whitelisted to only IOUserUserClient and IO80211APIUserClient. Custom UserClient class names get kIOReturnNotPermitted (0xe00002e2). Can't bypass without AMFI patch. provider OSDynamicCast IO80211SkywalkInterface // Cast 1 vtable alignment: re-enabling fNetIf->attach(this) (the H1-H4 era hack — was originally skipped to avoid a panic in framework dispatch) on Sequoia 15.7.5 currently causes super::start (IO80211Controller::start) to silently return false, before any of our Plan A code runs. I have a tracing build in flight to figure out which vtable slot the framework is calling that's wrong. Notable RE finds others might want: Sequoia 15.7.5 actual SIOCGA80211 cmd is 0xc02869c9 (size=40, modern 64-bit struct). The apple80211_ioctl.h header in this fork has the value baked from old 32-bit struct (size=48 → 0xc03069c9) — so cmd == SIOCGA80211 comparison silently never matches. Use the literal. Where I'm going next: Drilling into why super::start fails on the re-enabled-attach build (TRACE_STEP gives a postmortem via ioreg -k airportitlwm_trace — already proved that's the failure point) Will keep pushing. If anyone has the IO80211Controller::start RE for 15.7.5 already done, that'd save me a lot of cycles. |
Summary
On macOS Sonoma 14.4 and later, AirportItlwm V2 (Skywalk) leaves
State:/Network/Interface/<if>/LinkQualityat the default "no signal"value, because the
setLinkQualityMetric(100)call from V1 was commentedout (the API was renamed when
IO80211SkywalkInterfacereplacedIO80211Interface) but never ported to the newIO80211InfraInterface::setLQM(uint64). As a result, the entire iServicesstack (iMessage / FaceTime / AirDrop / Continuity) refuses to use Wi-Fi:
+[PCInterfaceUsabilityMonitor isBadLinkQuality:](literal thresholdlq < 11) marks the interface unusable, andapsd._connectStreamWithInterfacePreference:rejects every interface in the APNS path.
This PR ports link-quality reporting to the new path.
What changed
AirportItlwm/AirportItlwmV2.cppand.hpp:updateLQMIfChanged()that maps the currentni_rssito oneof three tiers (100 / 50 / 25) and calls
IO80211InfraInterface::setLQM(uint64)(resolved via virtual dispatchon
IO80211SkywalkInterface*) only when the tier changes. The lowesttier (25) deliberately stays above the iServices threshold of 11 so
that weak-but-recoverable links don't break iMessage on Hackintosh
systems without cellular fallback.
watchdogActionso that signal degradationduring a session is reflected.
setLinkStateGatedlink-up branch so the valuepropagates immediately on association rather than waiting up to 1 s
for the watchdog tick.
re-reports, even if the freshly-computed value happens to match the
cache.
Defensive checks
fLastReportedLQM = 0ininit()—OSDeclareDefaultStructorsonly zero-initializesOSObjectbasemembers, leaving derived ivars with uninitialized memory; the first
cache-compare could spuriously match one of {0, 25, 50, 100} and
skip the initial
setLQMcall.ic_state != IEEE80211_S_RUN) sowe don't write a bogus "good" LQ to SCDynamicStore before the link is
actually up.
ic_bssto a local — the ieee80211 layer can clear it fromanother workloop between the null check and the
ni_rssideref.fHalService—releaseAll()releases it beforecancelling the watchdog timer, so a tick already in flight on
fWatchdogWorkLoopcould otherwise observe a NULLfHalServiceandcrash on
get80211Controller(). ReorderingreleaseAll()is out ofscope for this PR; the cheap defensive check prevents this code path
from making the existing race observable.
ABI / compatibility
IO80211SkywalkInterface.hin this repository already declaressetLQMunconditionally (line 108 on master), and
IO80211InfraInterfaceprovides the working override in
IO80211Family.kext. No new externalsymbol is introduced — the binary's undefined-symbol set against
__ZN21IO80211InfraInterface6setLQMEyis unchanged from existing2.3.0/2.4.0 builds. So this is safe to ship for all macOS targets the
project currently supports; on macOS versions where
IO80211Familyprovides only the empty
IO80211SkywalkInterface::setLQMstub at thatvtable slot, the call is a no-op rather than a crash.
Verification (Intel AX201, macOS 14.8.5)
Before:
scutilshow State:/Network/Interface/en0/LinkQuality⇒-2apsdlog:Push is connected? NO networkIsHistoricallyUsable? YES isBadLQ? YESAfter this patch:
scutilshow State:/Network/Interface/en0/LinkQuality⇒100apsdlog:Push is connected? NO networkIsHistoricallyUsable? YES isBadLQ? NOapsdlog:courier <private>: Sending outgoing message N onInterface: NonCellular. Connected on 1 interfaces.Test plan
macos-latestrunner for all eightmacOS targets (Big Sur through Sonoma 14.4) via the existing CI.
Sonoma 14.8.5, OpenCore boot.
send/receive after reboot.
to confirm
ni_rssiunits are normalized identically across HALs.