Files
QualcommWireless/docs/ISSUES_AND_POSTMORTEMS.md
T
Erwan 0809b01856 fix(wifi): prevent CPU triple fault and instant reboot via CCLogStream layout conforming FakeLogStream
### Breakthroughs & Rétro-ingénierie
- Analyse forensique du redémarrage instantané ("coupure de courant" sans log) survenu au commit 5357ca6 :
  1. Le désassemblage de `CCLogStream::shouldLog` (0xffffff80032ff9e6) dans `BootKernelExtensions.kc` a révélé un déréférencement en cascade non protégé : `movq 0x90(%rdi), %rax` puis `movq 0x58(%rax), %rax`.
  2. `fLogger` retournant un `OSString*` de seulement 32 octets (0x20), l'accès à +0x90 lisait une adresse arbitraire du tas kernel dont le déréférencement en Ring 0 déclenchait un #PF/#GP transformé en Triple Fault CPU immédiat (reset matériel sans panic handler).
  3. Parallèlement, `IO80211RNGAgent::withOptions` et `IO80211ControllerMonitor::withControllerAndProvider` requièrent impérativement un pointeur non-nul et invoquent `logger->retain()` (*0x20(%rax)).
  4. La résolution consiste en une classe `FakeLogStream : public OSObject` respectant au millimètre l'ABI Apple : un pointeur `p1` placé à l'offset strict +0x90 (validé par `static_assert`) pointant vers une zone mémoire `fP1Mem` dont l'offset +0x58 pointe vers `fP2Mem` initialisé à zéro. `shouldLog` et `log*` retournent ainsi false sans aucun déréférencement illégal et sans risquer d'écrire sur un pipe non configuré.

### Modifications apportées
- `QualcommWifi/QualcommWifi.cpp` :
  - Définition et enregistrement IOKit de `FakeFaultReporter` et `FakeLogStream` avec méthodes usine `create()`.
  - Initialisation de `fFaultReporter = FakeFaultReporter::create()` et `fLogger = FakeLogStream::create()` dans `init()`.
  - Implémentation locale de `QualcommWifi::getActionFramePoolCapacity()` (slot 440) renvoyant 0x100 (256 frames).
  - Suppression de l'écriture directe `kIOPCIConfigCommand` sur le bus PCIe AMD pendant `start()` afin d'éviter tout conflit de bridge ou transaction d'arbitrage invalidée.
  - Nettoyage du buffer statique non-thread-safe dans `logNvramStage`.
- `QualcommWifi/QualcommWifi.hpp` :
  - Déclaration de `virtual UInt32 getActionFramePoolCapacity(void) override`.
- `build_wifi.sh` :
  - Ajout du flag `-Wno-invalid-offsetof` pour compilation propre de l'assertion ABI.
- `docs/ISSUES_AND_POSTMORTEMS.md` :
  - Documentation détaillée du post-mortem INC-10 expliquant le mécanisme de triple fault et la solution apportée.

### Validation
- Compilation réussie avec 0 erreur.
- Vérification de la vtable `__ZTV12QualcommWifi` : taille exacte de 3728 octets (0xe90, 466 slots) conforme à OpenCore et Apple `IO80211Controller`.
- Validation de conformité des symboles et dépendances à 100% via `kmutil libraries`.
- Déploiement du kext compilé dans `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext`.
- Activation confirmée dans `/Volumes/EFI/EFI/OC/config.plist` (`Enabled = True`).
2026-10-17 23:14:09 +02:00

197 lines
14 KiB
Markdown

# Historique & Post-Mortems des Problèmes Rencontrés
Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocages, panics et bootloops rencontrés lors du développement du pilote natif Wi-Fi Qualcomm WCN7850 (`QualcommWifi`), avec leurs analyses techniques de cause racine et leurs résolutions vérifiées.
---
## Sommaire des Incidents
| ID | Incident / Symptôme | Composant Touché | Cause Racine | Statut |
| :--- | :--- | :--- | :--- | :--- |
| **INC-01** | Erreur d'édition de liens : symboles de réservation `IONetworkController` non résolus | Linker / KPI macOS | Méthodes virtuelles de réserve non exportées par le noyau Tahoe | **Résolu** |
| **INC-02** | Crash précoce OpenCore : `padslot mismatch` | ABI / Vtable | Vtable `IO80211Controller` allongée à 466 slots sous Darwin 25 | **Résolu** |
| **INC-03** | Échec silencieux de démarrage : `IO80211Controller::start fails` | WorkQueue / IOKit | `IONetworkController::start` requiert une `IO80211WorkQueue` | **Résolu** |
| **INC-04** | **Bootloop de 5 secondes** : réinitialisation matérielle brutale du PC | Kernel Panic Apple | `panic("No ivars->_faultReporter")` dans `findAndAttachToFaultReporter` | **Résolu** |
| **INC-05** | Échec d'allocation interne : `Failed to allocate RNG Agent` | RNG / Logger | `IO80211RNGAgent` requiert un logger non-nul lors de l'initialisation | **Résolu** |
| **INC-06** | Désynchronisation du cycle d'initialisation au boot | Cycle Kexts (`Info.plist`) | `OSBundleRequired` positionné à `Root` au lieu de `Network-Root` | **Résolu** |
| **INC-07** | Blocage du chargement firmware AMSS lors du téléversement BHIe | PCIe ASPM | Désynchronisation BAR0 causée par les transitions d'énergie L0s/L1 | **Résolu** |
| **INC-08** | Corruptions mémoires & gel sur plateforme AMD avec > 4 Go RAM | Moteur DMA 36-bit | Pointeur DMA physique tronqué au-delà de 64 Go | **Résolu** |
| **INC-09** | Impossibilité de lire les rapports de panique (redémarrage instantané) | OpenCore / NVRAM | Paramètres de débogage du noyau par défaut provoquant un reboot | **Résolu** |
---
## Détail des Post-Mortems
### INC-01 : Erreur d'édition de liens sur les réservations `IONetworkController`
* **Symptôme** :
Lors de la compilation et de l'édition de liens du binaire Mach-O Kext, le compilateur Clang échouait avec des erreurs `Undefined symbols for architecture x86_64` :
`_ZN19IONetworkController29_RESERVEDIONetworkController2Ev` jusqu'à `7Ev`.
* **Cause Racine** :
Dans le Kernel Collection `BootKernelExtensions.kc` de macOS Tahoe, Apple conserve les slots virtuels de réservation pour préserver l'ABI C++, mais n'exporte pas leurs symboles dans les tables d'exportation globales (`libkern`/`KPI`).
* **Résolution** :
Définition de stubs d'export locaux C dans [`QualcommWifi.cpp`](file:///Users/erwan/Documents/QualcommWireless/QualcommWifi/QualcommWifi.cpp) :
```cpp
extern "C" {
void _ZN19IONetworkController29_RESERVEDIONetworkController2Ev(void) {}
void _ZN19IONetworkController29_RESERVEDIONetworkController3Ev(void) {}
void _ZN19IONetworkController29_RESERVEDIONetworkController4Ev(void) {}
void _ZN19IONetworkController29_RESERVEDIONetworkController5Ev(void) {}
void _ZN19IONetworkController29_RESERVEDIONetworkController6Ev(void) {}
void _ZN19IONetworkController29_RESERVEDIONetworkController7Ev(void) {}
}
```
---
### INC-02 : Crash OpenCore `padslot mismatch` sur la Vtable
* **Symptôme** :
Le Kext provoquait un plantage immédiat au chargement par OpenCore ou un KP dans `Vtables.c`.
* **Cause Racine** :
OpenCore valide strictement la taille de la table de fonctions virtuelles de chaque classe dérivée. Sous macOS Sonoma/Sequoia (Darwin 24), la vtable d'`IO80211Controller` comptait 448 entrées. Sous macOS Tahoe 26.x (Darwin 25), elle a été portée à 466 entrées (taille binaire de `0xe90` = 3728 octets). Tout écart dimensionnel invalide la correspondance des slots mémoires.
* **Résolution** :
Vérification systématique par script de la vtable `__ZTV12QualcommWifi` contre `BootKernelExtensions.kc`. Tous les slots jusqu'à l'offset `0xe90` sont rigoureusement alignés sur l'ABI Tahoe.
---
### INC-03 : Échec de `IO80211Controller::start` sans WorkQueue
* **Symptôme** :
Le pilote démarrait dans `init()` et `probe()`, mais `IO80211Controller::start` affichait `IO80211Controller::start fails` et retournait `false`.
* **Cause Racine** :
L'analyse désassemblée de `IO80211Controller::start` (`0xffffff8002291f30`) a révélé qu'à l'offset `+0x41`, le code teste le retour de `IONetworkController::start(provider)`. Cette dernière exige impérativement un gestionnaire de queue de travail valide pour instancier ses boucles d'événements.
* **Résolution** :
Implémentation des surcharges de création de la file de travail dans [`QualcommWifi.cpp`](file:///Users/erwan/Documents/QualcommWireless/QualcommWifi/QualcommWifi.cpp) :
```cpp
bool QualcommWifi::createWorkQueue(void) {
if (!fWorkQueue) {
fWorkQueue = IO80211WorkQueue::workQueue();
}
return (fWorkQueue != nullptr);
}
IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const { return fWorkQueue; }
bool QualcommWifi::createWorkLoop(void) { return createWorkQueue(); }
IOWorkLoop* QualcommWifi::getWorkLoop(void) const { return getWorkQueue(); }
```
---
### INC-04 : Le Bootloop de 5 Secondes - Kernel Panic `_faultReporter`
* **Symptôme** :
L'utilisateur subissait un redémarrage en boucle constant du Mac : OpenCore se lance, commence le chargement de macOS, puis au bout de 5 secondes environ, le PC s'éteint et redémarre instantanément sans message visible.
* **Cause Racine (Breakthrough Majeur)** :
Désassemblage complet du flux de `IO80211Controller::start` à l'adresse `0xffffff8002291f30` dans `BootKernelExtensions.kc` :
1. À `+0x272`, le pilote invoque `findAndAttachToFaultReporter()` (`0xffffff8002292628`).
2. À `+0x13e`, cette routine appelle la méthode virtuelle `this->getFaultReporterFromDriver()` (`callq *0xd80(%rax)`, vtable slot 434).
3. Notre code renvoyait initialement `nullptr`.
4. À l'adresse `0xffffff8002292737` (`+0x227`), `findAndAttachToFaultReporter` contient un appel direct et inconditionnel à `_panic` :
```text
panic("\"No ivars->_faultReporter\" @%s:%d", "IO80211Controller.cpp", 2877)
```
5. Faute d'argument empêchant le redémarrage, macOS déclenchait immédiatement un hardware reset.
* **Résolution** :
1. Instanciation dans `init()` d'un objet `OSObject` valide :
```cpp
fFaultReporter = OSString::withCString("QualcommFaultReporter");
```
2. Surcharge de `getFaultReporterFromDriver()` retournant `fFaultReporter`.
3. Libération propre dans `free()` via `OSSafeReleaseNULL(fFaultReporter)`.
4. Dans le noyau, `findAndAttachToFaultReporter()` constate que le pointeur est non-nul, stocke l'adresse dans `ivars->_faultReporter`, incrémente la référence via `obj->retain()` (`*0x20(%rax)`), et **aucun panic n'est émis**.
---
### INC-05 : Échec d'allocation `IO80211RNGAgent` / `getLogger()`
* **Symptôme** :
Échec dans `IO80211Controller::start` à l'étape d'initialisation du générateur aléatoire Wi-Fi avec le log : `Failed to allocate RNG Agent`.
* **Cause Racine** :
L'inspection binaire de `IO80211RNGAgent::withOptions()` (`0xffffff80022dd55c`) a montré qu'à l'offset `+0xe6`, la fonction appelle `this->getLogger()` (vtable slot 426, `*0xd40(%rax)`). Si ce pointeur est nul, elle saute directement en erreur (`+0x12f`), détruit le descripteur et renvoie `NULL`.
* **Résolution** :
Instanciation dans `init()` d'un objet `fLogger = OSString::withCString("QualcommLogger")`, et implémentation de `getLogger()` retournant `(void*)fLogger`. `RNGAgent` effectue son appel `logger->retain()` et s'initialise avec succès.
---
### INC-06 : Mésalignement du Cycle Kext `OSBundleRequired`
* **Symptôme** :
Tentative de matching du pilote PCI trop précoce lors de la séquence de boot, provoquant des échecs d'allocation de services réseau.
* **Cause Racine** :
Dans [`Info.plist`](file:///Users/erwan/Documents/QualcommWireless/QualcommWifi/Info.plist), `OSBundleRequired` avait été temporairement passé à `Root`. Or, `IO80211Family.kext` d'Apple spécifie impérativement `Network-Root`. Déclarer `Root` force le chargement du pilote avant que la pile des protocoles réseau et des sockets du noyau ne soit prête.
* **Résolution** :
Rétablissement strict de :
```xml
<key>OSBundleRequired</key>
<string>Network-Root</string>
```
---
### INC-07 : Blocage du Téléversement BHIe par ASPM PCIe
* **Symptôme** :
Timeout lors du transfert de l'image AMSS complète (~6 Mo) via BHIe, le statut BHI restant figé en boucle.
* **Cause Racine** :
La gestion d'énergie active du bus PCIe (Active State Power Management - ASPM) tente de basculer la liaison en veille L0s ou L1 pendant les transferts DMA volumineux, corrompant les accès aux registres MMIO de notification de sonnette (`ATH12K_BHIE_TXVECDB`).
* **Résolution** :
Désactivation dynamique d'ASPM avant tout transfert firmware :
```cpp
UInt8 pciCapOffset = 0;
if (fPciDevice->findPCICapability(kIOPCIPCIExpressCapability, &pciCapOffset) && pciCapOffset != 0) {
UInt16 linkCtrl = fPciDevice->configRead16(pciCapOffset + 0x10);
linkCtrl &= ~0x3; // Désactive L0s et L1
fPciDevice->configWrite16(pciCapOffset + 0x10, linkCtrl);
}
```
---
### INC-08 : Échec et Corruption DMA sur Plateformes AMD 64-bit (> 4 Go RAM)
* **Symptôme** :
Gel ou reboot brutal lors de l'accès aux anneaux de commandes ou lors du démarrage du Primary Boot Loader.
* **Cause Racine** :
Le SoC WCN7850 possède une unité DMA adressable sur 36 bits maximum (plage physique `0x000000000` à `0xFFFFFF000`). Sur les cartes mères équipées de 32 Go ou 64 Go de RAM, les allocations kernel classiques peuvent renvoyer des tampons au-dessus de 64 Go. Les bits de poids fort étaient tronqués par le matériel, écrivant dans des zones mémoires arbitraires du noyau.
* **Résolution** :
Toutes les allocations DMA contiguës sont contraintes par un masque physique 36 bits :
```cpp
#define ATH12K_DMA_MASK_36BIT_PAGE 0xFFFFFF000ULL
IOBufferMemoryDescriptor::inTaskWithPhysicalMask(
kernel_task,
kIODirectionInOut | kIOMemoryPhysicallyContiguous,
size,
ATH12K_DMA_MASK_36BIT_PAGE
);
```
---
### INC-09 : Redémarrages Automatiques Invisibles sans Affichage de KP
* **Symptôme** :
En cas de panic du noyau, le PC redémarrait instantanément sans donner le temps à l'utilisateur de lire la pile d'appels ou la cause du crash.
* **Cause Racine** :
Par défaut, le noyau XNU sous OpenCore réinitialise le processeur en cas de panique non interceptée.
* **Résolution** :
Injection de `panic=0` dans les `boot-args` OpenCore du fichier `config.plist`. En cas d'erreur critique, le noyau gèle l'affichage, permettant de lire textuellement le fichier source et la ligne fautive (ex: `IO80211Controller.cpp:2877`).
---
### INC-10 : Triple Fault Processeur ("Coupure de Courant") lors de l'appel `CCLogStream::shouldLog` sur `fLogger`
* **Symptôme** :
Lors du démarrage d'OpenCore avec `QualcommWifi.kext` actif, le PC s'éteint et redémarre instantanément comme une coupure de courant brutale, sans panic log ni rapport d'erreur NVRAM.
* **Cause Racine (Breakthrough Fondamental)** :
1. Lors du commit `5357ca6`, `getLogger()` avait été implémenté en renvoyant une instance d'`OSString` (`OSString::withCString("QualcommLogger")`).
2. Lors de l'exécution de `IO80211Controller::start()` -> `createIOReporters()` -> `addReporterLegend()`, le noyau macOS invoque `CCLogStream::shouldLog(this->getLogger(), level)` (`0xffffff80032ff9e6`).
3. Le désassemblage de `CCLogStream::shouldLog` a révélé :
```assembly
movq 0x90(%rdi), %rax ; Lit l'offset +0x90 de l'objet passé en rdi (fLogger)
movq 0x58(%rax), %rax ; Déréférence ce pointeur en Ring 0 !
cmpl 0x8(%rax), %esi ; Compare le niveau de log
setle %al
retq
```
4. L'objet `OSString` ne mesurant que 32 octets (`0x20`), l'accès à `+0x90` pointait sur une adresse mémoire arbitraire ou non mappée dans le heap kernel. Le déréférencement immédiat de cette valeur via `movq 0x58(%rax), %rax` déclenchait une faute de page (`#PF`) ou une protection générale (`#GP`) non interceptée en mode superviseur (Ring 0), provoquant un Triple Fault CPU réinitialisant la carte mère instantanément.
5. Parallèlement, `IO80211RNGAgent::withOptions()` (`0xffffff80022dd55c`) et `IO80211ControllerMonitor::initWithControllerAndProvider()` (`0xffffff80023929d0`) requièrent impérativement que `getLogger()` retourne un pointeur non-nul et appellent `logger->retain()` (`*0x20(%rax)`).
* **Résolution Technique** :
1. Conception d'une classe `FakeLogStream : public OSObject` respectant au millimètre l'ABI de `CCLogStream` :
- Taille minimale supérieure à `0x98` octets avec `p1` placé exactement à l'offset `+0x90` (validé par `static_assert(__builtin_offsetof(FakeLogStream, p1) == 0x90)`).
- `p1` pointe vers une zone mémoire `fP1Mem` (`0x1000` octets) dont l'offset `+0x58` pointe vers `fP2Mem` (`0x100` octets initialisés à 0).
- `CCLogStream::shouldLog` et `CCLogStream::log*` lisent ainsi `0x90 -> 0x58 -> 0`, constatent un niveau de log nul, et retournent proprement `false` sans tenter d'émettre sur un pipe inexistant.
- `FakeLogStream` étant un `OSObject`, les appels `retain()` et `release()` effectués par `RNGAgent` s'exécutent avec une fidélité parfaite.
2. Implémentation locale de `QualcommWifi::getActionFramePoolCapacity()` (slot 440) renvoyant `0x100` (256 frames).
3. Suppression de l'écriture directe `kIOPCIConfigCommand` sur le bus PCIe AMD pour éviter toute déstabilisation du root port.