diff --git a/QualcommWifi/QualcommWifi.cpp b/QualcommWifi/QualcommWifi.cpp index bc480eb..6bd2f85 100644 --- a/QualcommWifi/QualcommWifi.cpp +++ b/QualcommWifi/QualcommWifi.cpp @@ -19,16 +19,21 @@ extern "C" { #include #include +// Buffer pour le fil d'Ariane persistant (chaînage des balises d'étapes sans verrous) +static char gNvramTrail[512] = {0}; +static int sGetWorkQueueCount = 0; + static inline void logNvramStage(const char *tag) { + if (!tag) return; IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag); - if (tag) { - PEWriteNVRAMProperty("qcom_stage", tag, (unsigned int)strlen(tag)); - } - IORegistryEntry *options = IORegistryEntry::fromPath("/options", gIODTPlane); - if (options) { - options->setProperty("qcom_stage", tag); - options->release(); + PEWriteNVRAMProperty("qcom_stage", tag, (unsigned int)strlen(tag)); + + // Mise à jour continue du fil d'Ariane + if (gNvramTrail[0] != '\0') { + strlcat(gNvramTrail, ">", sizeof(gNvramTrail)); } + strlcat(gNvramTrail, tag, sizeof(gNvramTrail)); + PEWriteNVRAMProperty("qcom_trail", gNvramTrail, (unsigned int)strlen(gNvramTrail)); } // Classe simulant un FaultReporter IOKit valide (attendu par IO80211Controller::findAndAttachToFaultReporter) @@ -169,6 +174,42 @@ static IO80211SkywalkInterface* createSkywalkInterface(void) { OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller) bool QualcommWifi::init(OSDictionary *dictionary) { + // 1. Sauvegarde non volatile de l'état du boot précédent si un crash a eu lieu + char prevStage[128] = {0}; + unsigned int prevLen = sizeof(prevStage) - 1; + if (PEReadNVRAMProperty("qcom_stage", prevStage, &prevLen) && prevLen > 0) { + prevStage[prevLen] = '\0'; + if (strcmp(prevStage, "start_done") != 0 && strcmp(prevStage, "clean") != 0) { + PEWriteNVRAMProperty("qcom_last_crash", prevStage, (unsigned int)strlen(prevStage)); + IOLog("QualcommWifi: >>> [ALERTE] Crash boot précédent détecté au tag: '%s' (sauvegardé dans qcom_last_crash) <<<\n", prevStage); + } + } + + char prevTrail[512] = {0}; + unsigned int prevTrailLen = sizeof(prevTrail) - 1; + if (PEReadNVRAMProperty("qcom_trail", prevTrail, &prevTrailLen) && prevTrailLen > 0) { + prevTrail[prevTrailLen] = '\0'; + bool hadStartDone = false; + size_t tLen = strlen(prevTrail); + const char *doneTag = "start_done"; + size_t dLen = strlen(doneTag); + if (tLen >= dLen) { + for (size_t i = 0; i <= tLen - dLen; i++) { + if (memcmp(prevTrail + i, doneTag, dLen) == 0) { + hadStartDone = true; + break; + } + } + } + if (!hadStartDone) { + PEWriteNVRAMProperty("qcom_last_trail", prevTrail, (unsigned int)strlen(prevTrail)); + IOLog("QualcommWifi: >>> [ALERTE] Fil d'Ariane du crash précédent: '%s' (sauvegardé dans qcom_last_trail) <<<\n", prevTrail); + } + } + + bzero(gNvramTrail, sizeof(gNvramTrail)); + sGetWorkQueueCount = 0; + logNvramStage("init_enter"); if (!super::init(dictionary)) { logNvramStage("init_super_failed"); @@ -855,9 +896,9 @@ bool QualcommWifi::start(IOService *provider) { IOLog("QualcommWifi: >>> ETAPE 6 : Appel de fSkywalkInterface->start(this) (substep=%d) <<<\n", substep); IOLog("QualcommWifi: ========================================================\n"); - if (substep == 1) { - IOLog("QualcommWifi: [SUBSTEP 1] PAUSE de 10s AVANT start(this) pour lecture visuelle...\n"); - IOSleep(10000); + if (substep >= 1) { + IOLog("QualcommWifi: [PAUSE 1/3] Pause de 3s AVANT start(this) pour lecture visuelle...\n"); + IOSleep(3000); } logNvramStage("s6_1_calling_start"); @@ -867,12 +908,12 @@ bool QualcommWifi::start(IOService *provider) { fSkywalkStarted = true; logNvramStage("s6_2_start_ok"); IOLog("QualcommWifi: ========================================================\n"); - IOLog("QualcommWifi: >>> [SUCCES] fSkywalkInterface->start(this) A REUSSI ! <<<\n"); + IOLog("QualcommWifi: >>> [SUCCES MAJEUR] fSkywalkInterface->start(this) A REUSSI ! <<<\n"); IOLog("QualcommWifi: ========================================================\n"); - if (substep == 2) { - IOLog("QualcommWifi: [SUBSTEP 2] PAUSE de 10s APRES start(this) reussi pour observation...\n"); - IOSleep(10000); + if (substep >= 1) { + IOLog("QualcommWifi: [PAUSE 2/3] Pause de 5s APRES start(this) reussi pour observation...\n"); + IOSleep(5000); } if (skywalk_step >= 7) { @@ -886,6 +927,10 @@ bool QualcommWifi::start(IOService *provider) { IOLog("QualcommWifi: AVERTISSEMENT - fSkywalkInterface->start(this) a echoue (interface preservee pour diagnostic) !\n"); } logNvramStage("s6_3_post_start"); + if (substep >= 1) { + IOLog("QualcommWifi: [PAUSE 3/3] Pause de 3s avant de terminer QualcommWifi::start()...\n"); + IOSleep(3000); + } } } else { logNvramStage("skywalk_attach_err"); @@ -984,8 +1029,6 @@ bool QualcommWifi::createWorkQueue(void) { return true; } -static int sGetWorkQueueCount = 0; - IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const { sGetWorkQueueCount++; char tag[64]; diff --git a/docs/ISSUES_AND_POSTMORTEMS.md b/docs/ISSUES_AND_POSTMORTEMS.md index 1f1a92c..41dafab 100644 --- a/docs/ISSUES_AND_POSTMORTEMS.md +++ b/docs/ISSUES_AND_POSTMORTEMS.md @@ -708,6 +708,44 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage - Déploiement vers `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext`. - Mise à jour du `boot-args` OpenCore dans `/Volumes/EFI/EFI/OC/config.plist` : `qcom_step=6 qcom_substep=1`. +--- + +### Postmortem 18 : Découpage Chirurgical en 3 Pauses de l'Étape 6 et Blindage du Fil d'Ariane NVRAM sans Lock IORegistry + +* **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 du Test en Direct (Stade 6)** : + - Boot-args armé : `qcom_step=6 qcom_substep=1`. + - La pause visuelle de 10 secondes précédant l'appel `fSkywalkInterface->start(this)` s'est exécutée avec un succès absolu : le mode verbose de macOS est resté figé 10s de manière saine, attestant de la stabilité intégrale des étapes 1 à 5 (allocation, initialisation, rôle infrastructure, attachement IOKit et arborescence `CCPipe`). + - Dès la fin exacte des 10 secondes, lors de l'engagement de `fSkywalkInterface->start(this)` ou immédiatement après, le système a crashé et redémarré instantanément sans afficher l'écran de Kernel Panic classique (malgré `debug=0x100`). + - Au redémarrage de secours sous `qcom_step=5`, `QualcommWifi::start()` a complété son initialisation et réécrit `"start_done"` dans `qcom_stage`, masquant le point d'interruption du crash. +* **Causes Racines & Breakthroughs Techniques** : + 1. **Écrasement Silencieux de la Télémétrie NVRAM** : + - Lorsque l'utilisateur revient sur le bureau avec `qcom_step=5` pour diagnostiquer le crash, `QualcommWifi::init()` et `start()` s'exécutent avec succès et écrasent `qcom_stage`. + - *Résolution* : Implémentation dans `QualcommWifi::init()` d'une détection de crash antécédent. Si `qcom_stage` ne vaut pas `start_done` ou `clean`, il est automatiquement copié de manière non volatile dans `qcom_last_crash`, et le fil d'Ariane est copié dans `qcom_last_trail`. + 2. **Contention et Risque de Deadlock sous IOKit Power Management** : + - `logNvramStage()` appelait systématiquement `IORegistryEntry::fromPath("/options", gIODTPlane)` pour y inscrire la propriété IOKit. + - Dans `IO80211SkywalkInterface::start()`, macOS initialise la gestion d'alimentation (`PMinit()`, `joinPMtree(this)`, `registerPowerDriver`), puis ordonne les transitions `changePowerStateToPriv(1)` et `changePowerStateTo(0)`, déclenchant le callback `QualcommWifi::setPowerState()`. + - Tenter de traverser et verrouiller le plan `/options` du Device Tree (`gIODTPlane`) sous un verrou PM d'un thread noyau peut provoquer une contention mortelle ou un blocage critique. + - *Résolution* : Suppression totale de l'accès à `IORegistryEntry::fromPath` dans `logNvramStage()`. Utilisation exclusive et directe de `PEWriteNVRAMProperty` (Platform Expert) qui écrit dans la NVRAM matérielle sans aucun verrou de plan IORegistry. + 3. **Manque de Temporisation Post-`start(this)` et Fil d'Ariane Continu** : + - Sous `qcom_substep=1`, aucune pause n'était observée après `start(this)`. Si `start(this)` réussissait, le kext sortait immédiatement de `start()`, exposant l'interface aux démons macOS (`airportd`, `Skywalk`) sans permettre de vérifier si `s6_2_start_ok` avait été atteint. + - *Résolution* : Implémentation d'un fil d'Ariane continu (`qcom_trail`) enchaînant chaque balise (`s6_0>s6_1>cb_wq_1>cb_lock>...`) et découpage de l'Étape 6 en 3 pauses balisées explicites : + * `[PAUSE 1/3]` : 3 secondes avant `fSkywalkInterface->start(this)`. + * `[PAUSE 2/3]` : 5 secondes après retour réussi de `start(this)` avec bannière géante `[SUCCES MAJEUR]`. + * `[PAUSE 3/3]` : 3 secondes avant la fin de `QualcommWifi::start()`. +* **Modifications Apportées** : + 1. `QualcommWifi/QualcommWifi.cpp` : + - Élimination des locks IORegistry dans `logNvramStage()`. + - Ajout de `gNvramTrail` et écriture en temps réel de `qcom_trail`. + - Sauvegarde automatique de `qcom_last_crash` et `qcom_last_trail` dans `init()`. + - Découpage en 3 pauses de l'Étape 6 sous `qcom_substep >= 1`. + 2. Déploiement : + - Compilation sans erreur avec `./build_wifi.sh` (validation `kmutil` à 100%). + - Déploiement dans `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext`. + - Armement de `qcom_step=6 qcom_substep=1` dans `/Volumes/EFI/EFI/OC/config.plist`. + +