# 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 OSBundleRequired Network-Root ``` --- ### 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.