diff --git a/QualcommWifi/QualcommWifi.cpp b/QualcommWifi/QualcommWifi.cpp index cdec805..83ae5fa 100644 --- a/QualcommWifi/QualcommWifi.cpp +++ b/QualcommWifi/QualcommWifi.cpp @@ -24,10 +24,18 @@ static char gNvramTrail[2048] = {0}; static int sGetWorkQueueCount = 0; static int sGetLoggerCount = 0; static int sGetFaultReporterCount = 0; +static bool sInStep6 = false; +static int sStep6WqCount = 0; +static IORecursiveLock *sNvramLock = nullptr; static inline void logNvramStage(const char *tag) { if (!tag) return; IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag); + + if (sNvramLock) { + IORecursiveLockLock(sNvramLock); + } + PEWriteNVRAMProperty("qcom_stage", tag, (unsigned int)strlen(tag)); // Mise à jour continue du fil d'Ariane @@ -36,6 +44,10 @@ static inline void logNvramStage(const char *tag) { } strlcat(gNvramTrail, tag, sizeof(gNvramTrail)); PEWriteNVRAMProperty("qcom_trail", gNvramTrail, (unsigned int)strlen(gNvramTrail)); + + if (sNvramLock) { + IORecursiveLockUnlock(sNvramLock); + } } // Classe simulant un FaultReporter IOKit valide (attendu par IO80211Controller::findAndAttachToFaultReporter) @@ -209,10 +221,15 @@ bool QualcommWifi::init(OSDictionary *dictionary) { } } + if (!sNvramLock) { + sNvramLock = IORecursiveLockAlloc(); + } bzero(gNvramTrail, sizeof(gNvramTrail)); sGetWorkQueueCount = 0; sGetLoggerCount = 0; sGetFaultReporterCount = 0; + sInStep6 = false; + sStep6WqCount = 0; logNvramStage("init_enter"); if (!super::init(dictionary)) { @@ -264,6 +281,11 @@ void QualcommWifi::free(void) { OSSafeReleaseNULL(fLogger); OSSafeReleaseNULL(fWorkLoop); OSSafeReleaseNULL(fWorkQueue); + if (sNvramLock) { + IORecursiveLock *lock = sNvramLock; + sNvramLock = nullptr; + IORecursiveLockFree(lock); + } super::free(); } @@ -905,9 +927,12 @@ bool QualcommWifi::start(IOService *provider) { IOSleep(3000); } + sInStep6 = true; + sStep6WqCount = 0; logNvramStage("s6_1_calling_start"); IOSleep(200); // Laisse au bus SPI de la carte mère le temps d'écrire s6_1 physiquement bool startRes = fSkywalkInterface->start(this); + sInStep6 = false; if (startRes) { fSkywalkStarted = true; @@ -1040,15 +1065,24 @@ IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const { char tag[64]; snprintf(tag, sizeof(tag), "cb_wq_%d", sGetWorkQueueCount); logNvramStage(tag); + } else if (sInStep6 && sStep6WqCount < 5) { + sStep6WqCount++; + char tag[64]; + snprintf(tag, sizeof(tag), "cb_wq_s6_%d", sStep6WqCount); + logNvramStage(tag); + IOSleep(20); } IOLog("QualcommWifi: >>> CALLBACK getWorkQueue(#%d) -> 0x%p <<<\n", sGetWorkQueueCount, fWorkQueue); return fWorkQueue; } IORecursiveLock* QualcommWifi::allocIO80211RecursiveLock(void) { - logNvramStage("cb_allocLock"); - IOLog("QualcommWifi: >>> CALLBACK allocIO80211RecursiveLock() <<<\n"); + logNvramStage("cb_allocLock_enter"); + IOSleep(50); + IOLog("QualcommWifi: >>> CALLBACK allocIO80211RecursiveLock() [ENTER] <<<\n"); IORecursiveLock *lock = super::allocIO80211RecursiveLock(); + logNvramStage("cb_allocLock_super_ok"); + IOSleep(50); IOLog("QualcommWifi: super::allocIO80211RecursiveLock() = 0x%p\n", lock); if (!lock) { logNvramStage("cb_allocLock_null"); @@ -1056,6 +1090,8 @@ IORecursiveLock* QualcommWifi::allocIO80211RecursiveLock(void) { lock = IORecursiveLockAlloc(); IOLog("QualcommWifi: IORecursiveLockAlloc() = 0x%p\n", lock); } + logNvramStage("cb_allocLock_exit"); + IOSleep(50); return lock; } @@ -1135,6 +1171,7 @@ void* QualcommWifi::getFaultReporterFromDriver(void) { } UInt32 QualcommWifi::getActionFramePoolCapacity(void) { + logNvramStage("cb_actFrameCap"); return 0x100; } @@ -1165,7 +1202,7 @@ IOReturn QualcommWifi::getPOWER(IO80211SkywalkInterface *iface, apple80211_power } IOReturn QualcommWifi::setPOWER(IO80211SkywalkInterface *iface, apple80211_power_data *pwr) { - logNvramStage("setPOWER"); + logNvramStage("cb_setPOWER"); int val = (pwr && pwr->power_state[0]) ? 1 : 0; fLinkActive = (val == 1); IOLog("QualcommWifi: setPOWER invoqué par airportd (val=%d, fLinkActive=%d)\n", val, fLinkActive); diff --git a/docs/ISSUES_AND_POSTMORTEMS.md b/docs/ISSUES_AND_POSTMORTEMS.md index 7e4f525..ba85d82 100644 --- a/docs/ISSUES_AND_POSTMORTEMS.md +++ b/docs/ISSUES_AND_POSTMORTEMS.md @@ -26,6 +26,8 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage | **INC-16** | Bootloop par démarrage complet Skywalk & Piège de libération `free()` | Skywalk Lifecycle & Ivars Guard | Exécution aveugle du stade 6 (`start`), `#PF` CR2:0xb8 si `free()` sans `initIvars`, et `stop` non-gardé | **Résolu** | | **INC-17** | Page Fault sur table de propriétés NULL par court-circuit de `super::init(0)` | Skywalk Init Architecture | `initIvars` prématuré faisait croire à `init()` que `super::init(0)` avait déjà eu lieu | **Résolu** | | **INC-18** | Page Fault `CR2: 0xb8` dans `free()` par `initIvars` manquant et absence de boot-arg EFI | Skywalk Lifecycle & EFI Configuration | `free()` déréférence `[0x120]+0xb8` sans test NULL si `init()` échoue ; boot-arg `qcom_step=1` absent d'OpenCore | **Résolu** | +| **INC-19** | Saturation du fil d'Ariane NVRAM à 511 octets & latence SPI de repli | Télémétrie NVRAM | Buffer `gNvramTrail` fixé à 512 octets rejetant les tags de l'Étape 6 & réentrance SPI | **Résolu** | +| **INC-20** | **Freeze au seuil Étape 6 (`allocIO80211RecursiveLock`)** | Verrou Skywalk & Réentrance UEFI | Non-réentrance de `PEWriteNVRAMProperty` / UEFI Runtime Services & angle mort télémétrique | **Résolu** | --- @@ -779,6 +781,52 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage 4. **Validation** : - Compilation réussie sans avertissement (`./build_wifi.sh`), conformité `kmutil libraries` à 100% contre `BootKernelExtensions.kc`. +--- + +### Postmortem 20 : Analyse du Freeze / Deadlock dans l'Étape 6 (`allocIO80211RecursiveLock`), Sécurisation Anti-Réentrance NVRAM et Télémétrie Chirurgicale + +* **Date** : 19 Octobre 2026 +* **Cible** : macOS Tahoe 26.x (Darwin 25.6.0 x86_64), AMD Ryzen 7 5800X3D, `QualcommWifi.kext` (WCN7850 `0x17cb:0x1107`) +* **Symptômes & Retours Utilisateur (/grill-me)** : + - **Élimination complète du Kernel Panic / Triple Fault** : la machine ne redémarre plus brusquement au seuil de l'Étape 6. + - Le système se fige (freeze / deadlock matériel) durant `fSkywalkInterface->start(this)`. + - La télémétrie non volatile gravée dans la puce SPI Flash capture la balise exacte : + `qcom_last_crash = cb_allocLock` + `qcom_last_trail = ...>s6_0_before_start>s6_1_calling_start>cb_allocLock`. +* **Causes Racines & Breakthroughs Techniques** : + 1. **Analyse Désassemblée Approfondie de la Chaîne d'Allocation du Verrou** : + - Dans `fSkywalkInterface->start(this)` (`0xffffff80022f5792`), Skywalk appelle `createPeerManager()` (`0xffffff80022f7be6`). + - Dans `IO80211PeerManager::initWithInterface()` (`0xffffff8002246c88`) : + * À `0xffffff8002246ed5`, Skywalk appelle `controller->getWorkQueue()`. + * À `0xffffff8002246f04`, Skywalk appelle `controller->allocIO80211RecursiveLock()`. + * Notre implémentation appelait `super::allocIO80211RecursiveLock()`, qui invoque `controller->getWorkQueue()` puis saute dans `IO80211IORecursiveLock::allocWithParams(workQueue)` (`0xffffff8002194baa`). + * L'objet `IO80211IORecursiveLock` alloue un wrapper `OSObject` (taille 24 octets, vtable `__ZTV22IO80211IORecursiveLock`) contenant un `IORecursiveLock*` interne et une référence vers `fWorkQueue`. + * `IO80211PeerManager` stocke ce verrou dans `ivars + 0x78` sans l'acquérir immédiatement. + 2. **Non-Réentrance Critique des Services d'Exécution UEFI (`PEWriteNVRAMProperty`)** : + - `PEWriteNVRAMProperty` fait appel aux EFI Runtime Services (`SetVariable`) pour communiquer directement avec la puce SPI Flash de la carte mère Gigabyte B550. + - Les services d'exécution UEFI **ne sont pas réentrants**. Si deux threads (par exemple le thread principal exécutant `start(this)` et le thread asynchrone de gestion d'énergie `IOPMrootDomain` exécutant `setPowerState()`) tentent d'écrire simultanément dans la NVRAM matérielle, le contrôleur SPI de la carte mère subit un interblocage (deadlock matériel / spinlock infini), figeant irrémédiablement le processeur. + 3. **Angle Mort Télémétrique Post-`cb_allocLock`** : + - Le filtrage des écritures NVRAM imposé au Postmortem 19 supprimait les logs de `getWorkQueue()` au-delà de 3 appels. Or, `start(this)` invoque `getWorkQueue()` à nouveau pour `createPeerManager()` et `allocIO80211RecursiveLock()`. + - Aucune balise n'était inscrite entre `cb_allocLock` et `s6_2_start_ok`, englobant plus de 20 opérations critiques (`CommandGate`, 4 files `CommandQueue`, `TimerSource`, `PMinit`, `joinPMtree`, `registerPowerDriver`, `changePowerStateTo`, `IO80211Glue`, `IO80211MacAddressAgent`). +* **Résolutions Techniques Appliquées** : + 1. **Sécurisation Anti-Réentrance NVRAM par Verrou Récursif IOKit (`sNvramLock`)** : + - Allocation d'un `IORecursiveLock *sNvramLock` dès `init()`. + - Protection intégrale de `logNvramStage()` par `IORecursiveLockLock` / `Unlock`, garantissant la sérialisation absolue de toutes les écritures SPI Flash et interdisant toute collision entre threads noyau. + 2. **Balisage Granulaire Chirurgical de `allocIO80211RecursiveLock`** : + - Découpage en 3 balises horodatées matérielles : + * `cb_allocLock_enter` : confirmation d'entrée dans le callback. + * `cb_allocLock_super_ok` : confirmation de retour sans crash de `super::allocIO80211RecursiveLock()`. + * `cb_allocLock_exit` : confirmation de sortie et retour du pointeur de verrou. + - Insertion de délais de gravure physique `IOSleep(50)`. + 3. **Traçage Spécifique de l'Étape 6 pour `getWorkQueue`** : + - Activation d'un drapeau d'état `sInStep6` encadrant `fSkywalkInterface->start(this)`. + - Journalisation fine des appels de file de travail sous l'Étape 6 (`cb_wq_s6_1`, `cb_wq_s6_2`...), bornée à 5 écritures pour prévenir toute saturation. + 4. **Balisage des Callbacks Dérivés** : + - Ajout de balises proactives sur `getActionFramePoolCapacity` (`cb_actFrameCap`), `setPOWER` (`cb_setPOWER`), `setMulticastList` et `setPowerState`. + 5. **Validation** : + - Compilation réussie sans avertissement (`./build_wifi.sh`), conformité `kmutil libraries` à 100% contre `BootKernelExtensions.kc`. + +