Files
Erwan a10e097a7e feat(tahoe): port QualcommWireless & QualcommWifi to macOS Tahoe 26.x (Darwin 25)
Breakthroughs & Reverse Engineering (macOS Tahoe 26.7.1 / Darwin 25.6.0):
- Reconstructed the complete 463-slot Skywalk vtable and class layout (0x128 / 296 bytes) of IO80211Controller (__ZTV17IO80211Controller at 0xffffff8002461148) and AppleBCMWLANCore from BootKernelExtensions.kc (IO80211Family 1200.13.1, source 1566.5).
- Verified IOUSBHostDevice::deviceRequest vtable slot in Tahoe's BootKernelExtensions.kc (__ZTV15IOUSBHostDevice at 0xffffff8002e72720): confirmed exact match at slot 374 (0x176, byte offset 0xbb0).
- Disassembled Tahoe's /usr/sbin/bluetoothd (FindTransportType at 0x10014c700, Found_USB_Dongle at 0x1000e9559, GetProductAndVendorID at 0x1000edf2a) confirming USB transport matching on 'Bluetooth USB Host Controller' and CSR 0x0a12 descriptor spoofing.

Code & Build Modifications (v1.1.0):
- IO80211Controller.h: Add full 463-slot Tahoe Skywalk vtable declaration (slots 394..462, offsets 0xc50..0xe70) and exact 0x80 filler size when __IO80211_TARGET >= 260000 while preserving legacy compatibility.
- QualcommWifi.hpp/.cpp: Implement Tahoe IO80211Controller pure virtual methods (isCommandProhibited, handleCardSpecific, getDRIVER_VERSION, getHARDWARE_VERSION, getCARD_CAPABILITIES, getPOWER, setPOWER, getCOUNTRY_CODE, setCOUNTRY_CODE, setGET_DEBUG_INFO, getLogger, getFaultReporterFromDriver) and guard un-reserved IONetworkController slots 6..7 on SDK 26.5.
- QualcommWireless.cpp: Add non-null vtable slot sanity check prior to installing the shadow vtable hook on IOUSBHostDevice::deviceRequest.
- project.pbxproj & Info.plist: Upgrade to Xcode 26.6 (MacOSX26.5.sdk), MACOSX_DEPLOYMENT_TARGET = 26.0, __IO80211_TARGET = 260000, and bump version to 1.1.0.
- docs & README: Document macOS Tahoe (Darwin 25) architecture, bluetoothd offsets, and Skywalk IO80211Controller ABI.
2026-10-16 02:41:25 +02:00

9.0 KiB

Diagnostic & Architecture Bluetooth (Qualcomm WCN7850)

Ce document décrit en profondeur le fonctionnement du sous-système Bluetooth de la puce Qualcomm FastConnect 7800 (WCN7850), l'analyse détaillée du crash au démarrage de session macOS, et la solution définitive.


1. Description du Contrôleur Bluetooth

  • Identifiants matériels :
    • Vendeur : 0x0489 (Foxconn / Hon Hai)
    • Produit : 0xe10d
    • Nom du chipset : Qualcomm WCN7850 Bluetooth 5.4 HCI
    • Bus : USB 2.0 (Interface 0 : Contrôle/Bulk HCI, Interface 1 : SCO Audio)
  • Rôle dans macOS :
    • Fournit la couche transport HCI (Host Controller Interface) au daemon système bluetoothd.
    • Gère les périphériques HID (claviers, souris, Magic Trackpad), les casques audio A2DP/HFP, et les fonctionnalités Continuity / AirDrop si la carte Wi-Fi est associée.

2. Analyse Post-Mortem du Kernel Panic au Login

2.1. Symptômes observés

  • Au démarrage, l'écran de verrouillage macOS s'affiche correctement.
  • Dès que le mot de passe utilisateur est validé et que la session commence à charger, l'écran se fige complètement puis la machine redémarre (Kernel Panic).

2.2. Traces du Kernel Panic (Kernel-2026-10-15-174229.panic)

panic(cpu 0 caller 0xffffff800ec4b86d): A kext releasing a(n) IOBluetoothL2CAPChannel has corrupted the registry. @OSObject.cpp:224
Panicked task 0xffffff8b71de3980: 235 threads: pid 0: kernel_task
Backtrace (CPU 0), panicked thread: 0xffffff903e00ab30, Frame : Return Address
0xfffffff2a9823bd0 : 0xffffff800edd07bd mach_kernel : _panic + 0x81
0xfffffff2a9823cc0 : 0xffffff800ec4b86d mach_kernel : __ZNK8OSObject13taggedReleaseEPKvi + 0x8d
0xfffffff2a9823cd0 : 0xffffff8010694464 com.apple.iokit.IOBluetoothFamily : __ZN17IOBluetoothDevice13removeChannelEP23IOBluetoothL2CAPChannel + 0xae
0xfffffff2a9823d70 : 0xffffff8010697629 com.apple.iokit.IOBluetoothFamily : __ZN23IOBluetoothL2CAPChannel6stopWLEv + 0x65
0xfffffff2a9823df0 : 0xffffff8010697583 com.apple.iokit.IOBluetoothFamily : __ZN23IOBluetoothL2CAPChannel4stopEP9IOService + 0x33
0xfffffff2a9823e90 : 0xffffff800ecc7dd6 mach_kernel : __ZN9IOService15terminateWorkerEj + 0x8b6
Kernel Extensions in backtrace:
   com.apple.iokit.IOBluetoothFamily(9.0)

2.3. Cause Racine (Root Cause)

  1. L'Intrusion de IntelBTPatcher.kext :
    • Le kext tiers IntelBTPatcher.kext (conçu spécifiquement pour les puces Intel AX200/AX210) était resté activé (Enabled: true) dans le fichier config.plist d'OpenCore.
    • IntelBTPatcher intercepte et patche globalement les méthodes virtuelles de IOBluetoothFamily dans le noyau pour contourner des anomalies de paquets HCI propres à Intel.
  2. Incompatibilité avec Darwin 24 (macOS 15 Sequoia) :
    • Sous macOS 15 Sequoia, Apple a profondément remanié la gestion mémoire d'IOKit, notamment la validation des taggedRelease dans OSObject.cpp:224.
    • IntelBTPatcher v2.5.0 (compilé pour Monterey/Ventura/Sonoma) n'est pas compatible avec cette sémantique et effectue une libération désynchronisée (double release ou tag invalide) lors de la fermeture d'un canal L2CAP.
  3. Le déclencheur au Login :
    • Dès la validation du mot de passe de session, loginwindow démarre bluetoothd.
    • Les périphériques Apple couplés (notamment la Magic Mouse 2 78:CA:39:F4:15:5A et le Magic Keyboard 78:CA:39:4C:A3:9A) établissent ou réinitialisent leurs canaux L2CAP (HID ACL).
    • Lors de la libération ou du renouvellement du canal L2CAP (IOBluetoothDevice::removeChannel), le hook corrompu d'IntelBTPatcher déclenche immédiatement l'assertion de corruption de registre du noyau (OSObject::taggedRelease), provoquant le kernel panic instantané.

3. Solution et Bonnes Pratiques

3.1. Désactivation définitive d'IntelBTPatcher

Pour les cartes Qualcomm FastConnect 7800, IntelBTPatcher ne doit JAMAIS être utilisé :

  • Dans /Volumes/EFI/EFI/OC/config.plist :
    <dict>
        <key>BundlePath</key>
        <string>IntelBTPatcher.kext</string>
        <key>Enabled</key>
        <false/>
    </dict>
    
  • Suppression physique du bundle IntelBTPatcher.kext du dossier EFI/OC/Kexts/.

3.2. Rôle de BlueToolFixup.kext

  • BlueToolFixup.kext (de la suite BrcmPatchRAM / Acidanthera) est, quant à lui, requis et compatible.
  • Il patche dynamiquement bluetoothd en espace utilisateur pour accepter les dongles tiers non-Apple et débloquer les commandes de bas niveau (HCI Vendor Commands).
  • Arguments de démarrage recommandés dans boot-args : -btlfxallowanyaddr -btlfxnvramcheck

4. Séquence de Chargement du Firmware USB

Le firmware Bluetooth Qualcomm nécessite deux étages binaires téléversés au démarrage :

sequenceDiagram
    autonumber
    participant K as QualcommWireless (Kext)
    participant USB as Contrôleur USB WCN7850
    participant D as bluetoothd (macOS)

    K->>USB: QCA_GET_TARGET_VERSION (Requête de contrôle vendeur)
    USB-->>K: Versions ROM / Patch / RAM
    K->>USB: QCA_CHECK_STATUS (Vérification statut)
    USB-->>K: Status 0x00 (RAMPATCH et Syscfg requis)
    K->>USB: EP0: Header DFU (40 octets)
    K->>USB: EP2 (Bulk OUT): RAMPATCH WCN7850 (paquets de 4096 octets)
    K->>USB: EP0: Header DFU (4 octets)
    K->>USB: EP2 (Bulk OUT): NVM WCN7850 (paquets de 4096 octets)
    K->>USB: HCI Reset (0x03, 0x0C, 0x00)
    USB-->>K: HCI Command Complete Event
    K->>K: Injection des propriétés IORegistry (spoofing CSR 0x0a12)
    K->>K: Libération de l'interface USB (close)
    Note over D,USB: bluetoothd prend la main en exclusivité
    D->>USB: Établissement transport HCI USB
    D->>USB: Connexion Magic Mouse 2 / Clavier (L2CAP)

Grâce à cette séquence, le contrôleur Bluetooth WCN7850 opère de manière parfaitement stable sous macOS Sequoia (Darwin 24) et macOS Tahoe (Darwin 25) sans aucun crash au démarrage de session.


5. Rétro-ingénierie de bluetoothd (macOS Sequoia & macOS Tahoe) & Détection du Transport

5.1. Algorithme de sélection du transport dans FindTransportType

Le désassemblage complet du binaire /usr/sbin/bluetoothd sous macOS Sequoia (0x100121555) et sous macOS Tahoe 26.7.1 (0x10014c700, Found_USB_Dongle à 0x1000e9559, GetProductAndVendorID à 0x1000edf2a) a révélé l'algorithme exact de détection du bus HCI :

  1. Test 1 : transport-encoding dans l'arbre matériel (IODeviceTree)

    • bluetoothd interroge la propriété transport-encoding sous IODeviceTree:/chosen ou les nœuds bluetooth.
    • S'il trouve 0x00000000, il force immédiatement le transport USB ("DeviceTree transport = 0x00000000").
  2. Test 2 : Matching IORegistry du service hôte USB (0x100121620 / 0x10014c7ca)

    • bluetoothd appelle :
      IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceNameMatching("Bluetooth USB Host Controller"));
      
    • Découverte majeure : Si un service quelconque dans l'IORegistry porte le nom "Bluetooth USB Host Controller", bluetoothd assigne immédiatement Transport = 0 (USB) et retourne sans jamais évaluer le board-id de la machine !
  3. Test 3 : Fallback par table statique des board-id (0x1001216c6 / 0x10014c86f)

    • Si aucun service "Bluetooth USB Host Controller" n'a été trouvé, bluetoothd lit le board-id dans l'arbre IODeviceTree ("sysBoardID %s").
    • Il compare l'ID à deux tables internes :
      • Table 1 : Modèles avec Bluetooth USB natif (iMac, Mac mini, MacBook Pro pré-T2).
      • Table 2 : Modèles avec Bluetooth UART natif (MacPro7,1 / Mac-27AD2F918AE68F61).
    • Sur SMBIOS MacPro7,1, le démon sélectionne par défaut le transport UART (H4BC) et ignore complètement tout périphérique USB, affichant Chipset: BCM_4350C2, Transport: UART, State: Off.

5.2. Résolution native et Hook Shadow VTable dans QualcommWireless

En appliquant les enseignements de la rétro-ingénierie :

  • QualcommWireless s'enregistre explicitement sous le nom "Bluetooth USB Host Controller".
  • fDevice->setName("Bluetooth USB Host Controller").
  • Injection de transport-encoding = <00 00 00 00> sur le périphérique et sur IODeviceTree:/chosen.
  • Spoofing de idVendor = 0x0a12 (CSR) validé nativement par Found_USB_Dongle.
  • Shadow VTable (IOUSBHostDevice::deviceRequest) :
    • L'index vtable de IOUSBHostDevice::deviceRequest(IOService*, StandardUSB::DeviceRequest&, void*, IOMemoryDescriptor*, uint32_t&, IOUSBHostCompletion*, uint32_t) est résolu dynamiquement à la compilation via DeviceRequestAccessor::getVtableIndex() : slot 374 (0x176, offset 0xbb0), identique sous macOS Sequoia (Darwin 24) et macOS Tahoe (Darwin 25.6.0, __ZTV15IOUSBHostDevice à 0xffffff8002e72720).
    • L'interception du descripteur de périphérique USB (GET_DESCRIPTOR DEVICE, 18 octets) remplace à la volée le Vendor ID 0x0489 par 0x0a12 (CSR) lors de la lecture directe par bluetoothd (GetProductAndVendorID).
  • Résultat : bluetoothd configure le contrôleur en USB de manière native et immédiate.