fix(wifi): sécuriser logNvramStage sans locks IORegistry et découper l'étape 6 en 3 pauses balisées

- Breakthroughs & Rétro-ingénierie :
  * Analyse du crash survenu au seuil de l'Étape 6 immédiatement après la pause visuelle de 10s sous qcom_step=6 qcom_substep=1.
  * Identification d'une cause majeure de panique/deadlock sous Power Management : logNvramStage() interrogeait le plan du Device Tree IOKit via IORegistryEntry::fromPath("/options", gIODTPlane). Durant IO80211SkywalkInterface::start(), macOS enchaîne PMinit(), joinPMtree(this), registerPowerDriver() puis changePowerStateToPriv(1) et changePowerStateTo(0). L'exécution de setPowerState() sous verrou PM tout en tentant d'acquérir le verrou gIODTPlane engendre un risque critique de deadlock ou de faute d'accès en ring 0.
  * Résolution : bascule exclusive de logNvramStage() sur PEWriteNVRAMProperty() (Platform Expert direct, aucun verrou de plan IORegistry).
  * Résolution de l'écrasement silencieux de diagnostic : lors du reboot sous qcom_step=5, QualcommWifi::init() et start() s'exécutaient avec succès et écrasaient qcom_stage par 'start_done'. Implémentation d'une détection dans init() qui recopie automatiquement qcom_stage vers qcom_last_crash et le nouveau fil d'Ariane continu qcom_trail vers qcom_last_trail.
  * Découpage de l'Étape 6 en 3 pauses d'observation sous qcom_substep>=1 : Pause 1/3 (3s avant start(this)), Pause 2/3 (5s après start(this) réussi avec bannière [SUCCES MAJEUR]), et Pause 3/3 (3s avant de terminer QualcommWifi::start()).

- Modifications apportées :
  * QualcommWifi/QualcommWifi.cpp :
    - Élimination des locks IORegistry dans logNvramStage(), maintien du fil d'Ariane global gNvramTrail.
    - Ajout dans init() de la lecture non volatile de qcom_stage / qcom_trail et sauvegarde vers qcom_last_crash / qcom_last_trail.
    - Séquençage en 3 pauses visuelles balisées pour l'Étape 6.
  * docs/ISSUES_AND_POSTMORTEMS.md : rédaction détaillée du Postmortem 18.
  * EFI OpenCore : déploiement du binaire recompilé dans /Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext et configuration boot-args qcom_step=6 qcom_substep=1 dans config.plist.

- Validation :
  * Compilation sans aucun avertissement via ./build_wifi.sh.
  * kmutil libraries -p build/Release/QualcommWifi.kext validé à 100% contre BootKernelExtensions.kc.
  * Déploiement vérifié sur /Volumes/EFI/EFI/OC/.
This commit is contained in:
Erwan committed 2026-10-19 10:07:13 +02:00
1 parent 6017b35300
commit 0988463366
2 files changed
+97 -16

No files matched your search

+59 -16
View File
@@ -19,16 +19,21 @@ extern "C" {
#include <IOKit/IOPlatformExpert.h> #include <IOKit/IOPlatformExpert.h>
#include <IOKit/IOLocks.h> #include <IOKit/IOLocks.h>
// 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) { static inline void logNvramStage(const char *tag) {
if (!tag) return;
IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag); IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag);
if (tag) { PEWriteNVRAMProperty("qcom_stage", tag, (unsigned int)strlen(tag));
PEWriteNVRAMProperty("qcom_stage", tag, (unsigned int)strlen(tag));
} // Mise à jour continue du fil d'Ariane
IORegistryEntry *options = IORegistryEntry::fromPath("/options", gIODTPlane); if (gNvramTrail[0] != '\0') {
if (options) { strlcat(gNvramTrail, ">", sizeof(gNvramTrail));
options->setProperty("qcom_stage", tag);
options->release();
} }
strlcat(gNvramTrail, tag, sizeof(gNvramTrail));
PEWriteNVRAMProperty("qcom_trail", gNvramTrail, (unsigned int)strlen(gNvramTrail));
} }
// Classe simulant un FaultReporter IOKit valide (attendu par IO80211Controller::findAndAttachToFaultReporter) // Classe simulant un FaultReporter IOKit valide (attendu par IO80211Controller::findAndAttachToFaultReporter)
@@ -169,6 +174,42 @@ static IO80211SkywalkInterface* createSkywalkInterface(void) {
OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller) OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller)
bool QualcommWifi::init(OSDictionary *dictionary) { 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"); logNvramStage("init_enter");
if (!super::init(dictionary)) { if (!super::init(dictionary)) {
logNvramStage("init_super_failed"); 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: >>> ETAPE 6 : Appel de fSkywalkInterface->start(this) (substep=%d) <<<\n", substep);
IOLog("QualcommWifi: ========================================================\n"); IOLog("QualcommWifi: ========================================================\n");
if (substep == 1) { if (substep >= 1) {
IOLog("QualcommWifi: [SUBSTEP 1] PAUSE de 10s AVANT start(this) pour lecture visuelle...\n"); IOLog("QualcommWifi: [PAUSE 1/3] Pause de 3s AVANT start(this) pour lecture visuelle...\n");
IOSleep(10000); IOSleep(3000);
} }
logNvramStage("s6_1_calling_start"); logNvramStage("s6_1_calling_start");
@@ -867,12 +908,12 @@ bool QualcommWifi::start(IOService *provider) {
fSkywalkStarted = true; fSkywalkStarted = true;
logNvramStage("s6_2_start_ok"); logNvramStage("s6_2_start_ok");
IOLog("QualcommWifi: ========================================================\n"); 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"); IOLog("QualcommWifi: ========================================================\n");
if (substep == 2) { if (substep >= 1) {
IOLog("QualcommWifi: [SUBSTEP 2] PAUSE de 10s APRES start(this) reussi pour observation...\n"); IOLog("QualcommWifi: [PAUSE 2/3] Pause de 5s APRES start(this) reussi pour observation...\n");
IOSleep(10000); IOSleep(5000);
} }
if (skywalk_step >= 7) { 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"); IOLog("QualcommWifi: AVERTISSEMENT - fSkywalkInterface->start(this) a echoue (interface preservee pour diagnostic) !\n");
} }
logNvramStage("s6_3_post_start"); 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 { } else {
logNvramStage("skywalk_attach_err"); logNvramStage("skywalk_attach_err");
@@ -984,8 +1029,6 @@ bool QualcommWifi::createWorkQueue(void) {
return true; return true;
} }
static int sGetWorkQueueCount = 0;
IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const { IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const {
sGetWorkQueueCount++; sGetWorkQueueCount++;
char tag[64]; char tag[64];
+38
View File
@@ -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`. - 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`. - 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`.