feat(wifi): implement NVRAM stage logging and safe crash-proof mode for WCN7850

### Breakthroughs & Rétro-ingénierie
- **Analyse des réinitialisations matérielles instantanées sur AMD Ryzen** :
  L'arrêt brutal (coupure d'alimentation instantanée de 5 secondes au boot) sur plateforme AMD Ryzen/AM4 provient d'un Machine Check / PCIe Unsupported Request / AER déclenché lors d'accès MMIO (BAR0) lorsque la carte WCN7850 est en état basse consommation D3hot (CurrentPowerState=2 dans IORegistry).
- **Traces persistantes dans la NVRAM matérielle (AppleEFINVRAM)** :
  Comme un reset matériel coupe l'alimentation avant l'écriture des journaux sur disque ou la génération d'un Kernel Panic (pas de /Library/Logs/DiagnosticReports), la fonction logNvramStage() écrit directement chaque jalon franchi dans l'entrée '/options' du plan d'arbre de périphériques gIODTPlane. Cette trace survit aux redémarrages et est consultable via 'nvram -p | grep qcom_wifi_log'.
- **Mode Safe / Diagnostic sans crash** :
  Introduction d'une barrière d'accès MMIO pilotée par l'argument de démarrage 'qcom_mmio'. Sans cet argument, le pilote active le contrôleur PCIe en D0 sans toucher aux registres MMIO sensibles, publiant les interfaces et services IO80211Controller pour validation visuelle sans aucun risque de boot loop.

### Modifications apportées
- **QualcommWifi/QualcommWifi.cpp** :
  - Ajout de l'include <IOKit/IODeviceTreeSupport.h> et implémentation de logNvramStage().
  - Instrumentation des méthodes init(), probe(), start(), createWorkQueue(), getHardwareAddress(), enable(), disable(), dataLinkLayerAttachComplete(), setPOWER() et handleApple80211Request().
  - Ajout du réveil formel en D0 via fPciDevice->enablePCIPowerManagement(kPCIPMCSPowerStateD0).
  - Conditionnement de l'accès BAR0/MHI/BHI sous PE_parse_boot_argn("qcom_mmio").
  - Suppression des reliquats de détachement d'interface ethernet.

### Validation
- Compilation réussie avec clang 17 et SDK macOS Tahoe 26.x.
- Validation des dépendances et symboles à 100% via kmutil libraries contre BootKernelExtensions.kc.
- Déploiement du kext sur /Volumes/EFI/EFI/OC/Kexts/ et activation dans config.plist.
This commit is contained in:
Erwan committed 2026-10-16 16:53:58 +02:00
1 parent dcabc5d8df
commit 1a6562c229
1 file changed
+91 -37
+91 -37
View File
@@ -14,13 +14,38 @@ extern "C" {
void _ZN19IONetworkController29_RESERVEDIONetworkController7Ev(void) {} void _ZN19IONetworkController29_RESERVEDIONetworkController7Ev(void) {}
} }
#include <pexpert/pexpert.h>
#include <IOKit/IODeviceTreeSupport.h>
static char gLogBuffer[2048] = "QCOM_BOOT:";
static void logNvramStage(const char *tag) {
size_t curLen = strlen(gLogBuffer);
if (curLen + strlen(tag) + 2 < sizeof(gLogBuffer)) {
strcat(gLogBuffer, ">");
strcat(gLogBuffer, tag);
}
IORegistryEntry *options = IORegistryEntry::fromPath("/options", gIODTPlane);
if (options) {
OSString *val = OSString::withCString(gLogBuffer);
if (val) {
options->setProperty("qcom_wifi_log", val);
val->release();
}
}
IOLog("QualcommWifi: >>> [STAGE] %s <<<\n", tag);
}
#define super IO80211Controller #define super IO80211Controller
OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller) OSDefineMetaClassAndStructors(QualcommWifi, IO80211Controller)
bool QualcommWifi::init(OSDictionary *dictionary) { bool QualcommWifi::init(OSDictionary *dictionary) {
logNvramStage("init_enter");
if (!super::init(dictionary)) { if (!super::init(dictionary)) {
logNvramStage("init_super_failed");
return false; return false;
} }
logNvramStage("init_super_ok");
fPciDevice = nullptr; fPciDevice = nullptr;
fBar0Map = nullptr; fBar0Map = nullptr;
fBar0Virt = nullptr; fBar0Virt = nullptr;
@@ -56,12 +81,15 @@ void QualcommWifi::free(void) {
} }
IOService *QualcommWifi::probe(IOService *provider, SInt32 *score) { IOService *QualcommWifi::probe(IOService *provider, SInt32 *score) {
logNvramStage("probe_enter");
if (!super::probe(provider, score)) { if (!super::probe(provider, score)) {
logNvramStage("probe_super_failed");
return nullptr; return nullptr;
} }
IOPCIDevice *pciDev = OSDynamicCast(IOPCIDevice, provider); IOPCIDevice *pciDev = OSDynamicCast(IOPCIDevice, provider);
if (!pciDev) { if (!pciDev) {
logNvramStage("probe_not_pci");
return nullptr; return nullptr;
} }
@@ -71,9 +99,11 @@ IOService *QualcommWifi::probe(IOService *provider, SInt32 *score) {
if (vendorId == 0x17cb && deviceId == 0x1107) { if (vendorId == 0x17cb && deviceId == 0x1107) {
IOLog("QualcommWifi: Controleur Wi-Fi 7 Qualcomm WCN7850 detecte (0x%04x:0x%04x) !\n", vendorId, deviceId); IOLog("QualcommWifi: Controleur Wi-Fi 7 Qualcomm WCN7850 detecte (0x%04x:0x%04x) !\n", vendorId, deviceId);
*score += 20000; *score += 20000;
logNvramStage("probe_match_ok");
return this; return this;
} }
logNvramStage("probe_no_match");
return nullptr; return nullptr;
} }
@@ -513,64 +543,78 @@ bool QualcommWifi::loadBhiFirmware(void) {
} }
bool QualcommWifi::start(IOService *provider) { bool QualcommWifi::start(IOService *provider) {
logNvramStage("start_enter");
IOLog("QualcommWifi: Demarrage du pilote Wi-Fi natif (IO80211Controller)...\n"); IOLog("QualcommWifi: Demarrage du pilote Wi-Fi natif (IO80211Controller)...\n");
if (!super::start(provider)) { if (!super::start(provider)) {
logNvramStage("super_start_failed");
IOLog("QualcommWifi: Erreur - IO80211Controller::start(provider) a echoue !\n"); IOLog("QualcommWifi: Erreur - IO80211Controller::start(provider) a echoue !\n");
return false; return false;
} }
logNvramStage("super_start_ok");
fPciDevice = OSDynamicCast(IOPCIDevice, provider); fPciDevice = OSDynamicCast(IOPCIDevice, provider);
if (!fPciDevice) { if (!fPciDevice) {
logNvramStage("start_no_pci");
return false; return false;
} }
fPciDevice->retain(); fPciDevice->retain();
logNvramStage("pci_open");
if (!fPciDevice->open(this)) { if (!fPciDevice->open(this)) {
logNvramStage("pci_open_failed");
OSSafeReleaseNULL(fPciDevice); OSSafeReleaseNULL(fPciDevice);
return false; return false;
} }
logNvramStage("pci_open_ok");
// Réveil formel du contrôleur PCIe vers l'état d'alimentation D0
logNvramStage("pci_enable_d0");
fPciDevice->enablePCIPowerManagement(kPCIPMCSPowerStateD0);
UInt16 pciCommand = fPciDevice->configRead16(kIOPCIConfigCommand); UInt16 pciCommand = fPciDevice->configRead16(kIOPCIConfigCommand);
pciCommand |= kIOPCICommandMemorySpace; pciCommand |= kIOPCICommandMemorySpace;
pciCommand &= ~kIOPCICommandBusMaster; // Protection absolue contre tout accès DMA non configuré sur AMD-Vi pciCommand &= ~kIOPCICommandBusMaster; // Protection absolue contre tout accès DMA non configuré sur AMD-Vi
logNvramStage("pci_cmd_write");
fPciDevice->configWrite16(kIOPCIConfigCommand, pciCommand); fPciDevice->configWrite16(kIOPCIConfigCommand, pciCommand);
fBar0Map = fPciDevice->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0); int allow_mmio = 0;
if (!fBar0Map) { PE_parse_boot_argn("qcom_mmio", &allow_mmio, sizeof(allow_mmio));
fPciDevice->close(this);
OSSafeReleaseNULL(fPciDevice);
return false;
}
fBar0Virt = (volatile void *)fBar0Map->getVirtualAddress();
UInt32 mhiStatus = read32(ATH12K_MHI_MHISTATUS);
UInt32 mhiCtrl = read32(ATH12K_MHI_MHICTRL);
UInt32 bhiOff = read32(ATH12K_MHI_BHIOFF);
UInt32 bhieOff = read32(ATH12K_MHI_BHIEOFF);
IOLog("QualcommWifi: MHI STATUS=0x%08x, CTRL=0x%08x, BHIOFF=0x%08x, BHIEOFF=0x%08x\n",
mhiStatus, mhiCtrl, bhiOff, bhieOff);
setProperty("MHI_Status", mhiStatus, 32);
setProperty("MHI_Ctrl", mhiCtrl, 32);
setProperty("BHI_Offset", bhiOff, 32);
setProperty("BHIE_Offset", bhieOff, 32);
UInt32 bhiSerial = 0x123456; UInt32 bhiSerial = 0x123456;
if (bhiOff != 0xFFFFFFFF && bhiOff < 0x200000) { if (allow_mmio) {
UInt32 bhiMajor = read32(bhiOff + ATH12K_BHI_BHIVERSION_MAJOR); logNvramStage("bar0_map");
UInt32 bhiMinor = read32(bhiOff + ATH12K_BHI_BHIVERSION_MINOR); fBar0Map = fPciDevice->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0);
UInt32 bhiEe = read32(bhiOff + ATH12K_BHI_EXECENV); if (fBar0Map) {
UInt32 bhiStat = read32(bhiOff + ATH12K_BHI_STATUS); fBar0Virt = (volatile void *)fBar0Map->getVirtualAddress();
bhiSerial = read32(bhiOff + ATH12K_BHI_SERIALNU); logNvramStage("read_mhi");
IOLog("QualcommWifi: BHI v%u.%u: ExecEnv=0x%x, Status=0x%x, Serial=0x%08x\n", UInt32 mhiStatus = read32(ATH12K_MHI_MHISTATUS);
bhiMajor, bhiMinor, bhiEe, bhiStat, bhiSerial); UInt32 mhiCtrl = read32(ATH12K_MHI_MHICTRL);
UInt32 bhiOff = read32(ATH12K_MHI_BHIOFF);
UInt32 bhieOff = read32(ATH12K_MHI_BHIEOFF);
setProperty("MHI_Status", mhiStatus, 32);
setProperty("MHI_Ctrl", mhiCtrl, 32);
setProperty("BHI_Offset", bhiOff, 32);
setProperty("BHIE_Offset", bhieOff, 32);
setProperty("BHI_Major", bhiMajor, 32); if (bhiOff != 0xFFFFFFFF && bhiOff > 0 && bhiOff < 0x200000) {
setProperty("BHI_Minor", bhiMinor, 32); logNvramStage("read_bhi");
setProperty("BHI_ExecEnv", bhiEe, 32); UInt32 bhiMajor = read32(bhiOff + ATH12K_BHI_BHIVERSION_MAJOR);
setProperty("BHI_Status", bhiStat, 32); UInt32 bhiMinor = read32(bhiOff + ATH12K_BHI_BHIVERSION_MINOR);
setProperty("BHI_Serial", bhiSerial, 32); UInt32 bhiEe = read32(bhiOff + ATH12K_BHI_EXECENV);
UInt32 bhiStat = read32(bhiOff + ATH12K_BHI_STATUS);
bhiSerial = read32(bhiOff + ATH12K_BHI_SERIALNU);
setProperty("BHI_Major", bhiMajor, 32);
setProperty("BHI_Minor", bhiMinor, 32);
setProperty("BHI_ExecEnv", bhiEe, 32);
setProperty("BHI_Status", bhiStat, 32);
setProperty("BHI_Serial", bhiSerial, 32);
}
}
} else {
logNvramStage("safe_mode_active");
setProperty("QualcommWifi_SafeMode", "Active");
} }
// Configuration de l'adresse MAC (OUI Qualcomm 00:03:7F + BHI serial) // Configuration de l'adresse MAC (OUI Qualcomm 00:03:7F + BHI serial)
@@ -586,6 +630,7 @@ bool QualcommWifi::start(IOService *provider) {
fMacAddress.bytes[3], fMacAddress.bytes[4], fMacAddress.bytes[5]); fMacAddress.bytes[3], fMacAddress.bytes[4], fMacAddress.bytes[5]);
// Propriétés Wi-Fi AirPort pour macOS et airportd // Propriétés Wi-Fi AirPort pour macOS et airportd
logNvramStage("set_airport_props");
setProperty("IO80211AirPort", true); setProperty("IO80211AirPort", true);
setProperty("AirPort", true); setProperty("AirPort", true);
setProperty("IO80211Locale", "ETSI"); setProperty("IO80211Locale", "ETSI");
@@ -594,6 +639,7 @@ bool QualcommWifi::start(IOService *provider) {
setProperty("IO80211RadioState", (UInt64)1, 32); setProperty("IO80211RadioState", (UInt64)1, 32);
// Configuration et publication des médiums réseau (IEEE 802.11) // Configuration et publication des médiums réseau (IEEE 802.11)
logNvramStage("pub_mediums");
OSDictionary *mediumDict = OSDictionary::withCapacity(2); OSDictionary *mediumDict = OSDictionary::withCapacity(2);
if (mediumDict) { if (mediumDict) {
IONetworkMedium *medium = IONetworkMedium::medium(kIOMediumIEEE80211, 54000000); IONetworkMedium *medium = IONetworkMedium::medium(kIOMediumIEEE80211, 54000000);
@@ -608,7 +654,9 @@ bool QualcommWifi::start(IOService *provider) {
} }
// Enregistrement IOKit du contrôleur Wi-Fi natif (déclenche le matching airportd) // Enregistrement IOKit du contrôleur Wi-Fi natif (déclenche le matching airportd)
logNvramStage("register_service");
registerService(); registerService();
logNvramStage("start_done");
IOLog("QualcommWifi: Pilote Wi-Fi natif (IO80211Controller) demarre avec succes !\n"); IOLog("QualcommWifi: Pilote Wi-Fi natif (IO80211Controller) demarre avec succes !\n");
return true; return true;
} }
@@ -616,11 +664,6 @@ bool QualcommWifi::start(IOService *provider) {
void QualcommWifi::stop(IOService *provider) { void QualcommWifi::stop(IOService *provider) {
IOLog("QualcommWifi: Arret du pilote Wi-Fi natif, liberation MSI, DMA et BAR0.\n"); IOLog("QualcommWifi: Arret du pilote Wi-Fi natif, liberation MSI, DMA et BAR0.\n");
if (fNetif) {
IOEthernetController::detachInterface(fNetif);
OSSafeReleaseNULL(fNetif);
}
IOWorkLoop *workLoop = getWorkLoop(); IOWorkLoop *workLoop = getWorkLoop();
if (fInterruptSource) { if (fInterruptSource) {
fInterruptSource->disable(); fInterruptSource->disable();
@@ -646,14 +689,17 @@ void QualcommWifi::stop(IOService *provider) {
} }
bool QualcommWifi::createWorkQueue(void) { bool QualcommWifi::createWorkQueue(void) {
logNvramStage("createWorkQueue");
if (fWorkQueue) { if (fWorkQueue) {
return true; return true;
} }
fWorkQueue = IO80211WorkQueue::workQueue(); fWorkQueue = IO80211WorkQueue::workQueue();
if (!fWorkQueue) { if (!fWorkQueue) {
logNvramStage("workQueue_failed");
IOLog("QualcommWifi: Erreur - Impossible d'allouer IO80211WorkQueue !\n"); IOLog("QualcommWifi: Erreur - Impossible d'allouer IO80211WorkQueue !\n");
return false; return false;
} }
logNvramStage("workQueue_ok");
IOLog("QualcommWifi: IO80211WorkQueue allouee avec succes (0x%p) !\n", fWorkQueue); IOLog("QualcommWifi: IO80211WorkQueue allouee avec succes (0x%p) !\n", fWorkQueue);
return true; return true;
} }
@@ -671,12 +717,14 @@ IOWorkLoop* QualcommWifi::getWorkLoop(void) const {
} }
IOReturn QualcommWifi::getHardwareAddress(IOEthernetAddress *addrP) { IOReturn QualcommWifi::getHardwareAddress(IOEthernetAddress *addrP) {
logNvramStage("getHardwareAddress");
if (!addrP) return kIOReturnBadArgument; if (!addrP) return kIOReturnBadArgument;
bcopy(&fMacAddress, addrP, sizeof(IOEthernetAddress)); bcopy(&fMacAddress, addrP, sizeof(IOEthernetAddress));
return kIOReturnSuccess; return kIOReturnSuccess;
} }
IOReturn QualcommWifi::enable(IONetworkInterface *netif) { IOReturn QualcommWifi::enable(IONetworkInterface *netif) {
logNvramStage("netif_enable");
IOLog("QualcommWifi: Interface reseau activee par macOS.\n"); IOLog("QualcommWifi: Interface reseau activee par macOS.\n");
const IONetworkMedium *medium = getSelectedMedium(); const IONetworkMedium *medium = getSelectedMedium();
setLinkStatus(kIONetworkLinkValid | kIONetworkLinkActive, medium); setLinkStatus(kIONetworkLinkValid | kIONetworkLinkActive, medium);
@@ -684,6 +732,7 @@ IOReturn QualcommWifi::enable(IONetworkInterface *netif) {
} }
IOReturn QualcommWifi::disable(IONetworkInterface *netif) { IOReturn QualcommWifi::disable(IONetworkInterface *netif) {
logNvramStage("netif_disable");
IOLog("QualcommWifi: Interface reseau desactivee par macOS.\n"); IOLog("QualcommWifi: Interface reseau desactivee par macOS.\n");
setLinkStatus(kIONetworkLinkValid, nullptr); setLinkStatus(kIONetworkLinkValid, nullptr);
return kIOReturnSuccess; return kIOReturnSuccess;
@@ -697,6 +746,7 @@ UInt32 QualcommWifi::outputPacket(mbuf_t m, void *param) {
} }
void QualcommWifi::dataLinkLayerAttachComplete(void) { void QualcommWifi::dataLinkLayerAttachComplete(void) {
logNvramStage("dataLinkAttachDone");
IOLog("QualcommWifi: dataLinkLayerAttachComplete (Skywalk Tahoe).\n"); IOLog("QualcommWifi: dataLinkLayerAttachComplete (Skywalk Tahoe).\n");
} }
@@ -721,6 +771,7 @@ IOReturn QualcommWifi::getPOWER(IO80211SkywalkInterface *iface, apple80211_power
} }
IOReturn QualcommWifi::setPOWER(IO80211SkywalkInterface *iface, apple80211_power_data *pwr) { IOReturn QualcommWifi::setPOWER(IO80211SkywalkInterface *iface, apple80211_power_data *pwr) {
logNvramStage("setPOWER");
int val = (pwr && pwr->power_state[0]) ? 1 : 0; int val = (pwr && pwr->power_state[0]) ? 1 : 0;
fLinkActive = (val == 1); fLinkActive = (val == 1);
IOLog("QualcommWifi: setPOWER invoqué par airportd (val=%d, fLinkActive=%d)\n", val, fLinkActive); IOLog("QualcommWifi: setPOWER invoqué par airportd (val=%d, fLinkActive=%d)\n", val, fLinkActive);
@@ -732,6 +783,9 @@ IOReturn QualcommWifi::getCOUNTRY_CODE(IO80211SkywalkInterface *iface, apple8021
} }
SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val, void* req_data) { SInt32 QualcommWifi::handleApple80211Request(unsigned int req_type, int req_val, void* req_data) {
char iocTag[32];
snprintf(iocTag, sizeof(iocTag), "ioc_%u", req_type);
logNvramStage(iocTag);
switch (req_type) { switch (req_type) {
case APPLE80211_IOC_CARD_CAPABILITIES: { case APPLE80211_IOC_CARD_CAPABILITIES: {
apple80211_capability_data *cap = (apple80211_capability_data *)req_data; apple80211_capability_data *cap = (apple80211_capability_data *)req_data;