Breakthroughs & Rétro-ingénierie : - Diagnostic de la boucle de redémarrage (boot loop / reset instantané sur logo Apple) : 1. Incompatibilité vtable IO80211Controller : le désassemblage du BootKernelExtensions.kc de macOS Tahoe 26.x a mis en évidence un appel virtuel slot 107 (attachToParent) et slot 35 sur le FaultReporter retourné. Nos en-têtes reconstitués présentaient un décalage de slots virtuels menant à un saut sur pointeur nul (0x0) en Ring 0, déclenchant un triple fault CPU. 2. Blocage synchrone du thread IOKit de démarrage : le transfert BHI de 6 Mo et les boucles IODelay (>15-20s) dans start() faisaient déclencher le watchdog boot du kernel macOS ou le MCE PCIe/IOMMU AMD Ryzen. 3. Saturation du segment __TEXT exécutable : les 8.4 Mo de firmware assemblés en code exécutable violaient les quotas mémoire de pré-démarrage du kernel boot.efi. - Solution architecturale : adoption de IOEthernetController (com.apple.iokit.IONetworkingFamily), approche éprouvée (similaire à itlwm) avec ABI publique pérenne (31 méthodes virtuelles stables), éliminant entièrement les classes instables privées IO80211WorkQueue et CCFaultReporter. - Déplacement des blobs firmware vers la section __DATA,__const, réduisant le segment exécutable __TEXT de 8.8 Mo à seulement 16 Ko. - start() ultraléger (< 2 ms) déportant le chargement BHI/MHI vers la méthode enable() appelée de façon asynchrone par la pile réseau de macOS. Modifications apportées : - QualcommWifi/Firmware_WCN7850_Wifi.s : relocalisation des sections d'assemblage en __DATA,__const au lieu de __TEXT,__const. - QualcommWifi/QualcommWifi.hpp : héritage direct depuis IOEthernetController, suppression des dépendances et stubs IO80211, déclarations des méthodes getHardwareAddress, enable, disable, outputPacket. - QualcommWifi/QualcommWifi.cpp : refonte complète en sous-classe IOEthernetController, start() sans blocage pour initialisation immédiate des BAR/IDs et publication de l'interface réseau, BHI firmware différé à enable(). - QualcommWifi/Info.plist : suppression de la dépendance com.apple.iokit.IO80211Family, conservation de IONetworkingFamily et IOPCIFamily, OSBundleRequired fixé à Root. - build_wifi.sh : alignement de la cible de compilation sur macos26.5 et résolution des chemins SDK. Validation : - Compilation sans aucun avertissement ni erreur via Clang/Clang++ 26. - Validation des symboles et dépendances réussie à 100% avec kmutil libraries contre BootKernelExtensions.kc. - Déploiement de QualcommWifi.kext dans /Volumes/EFI/EFI/OC/Kexts/. - Réactivation validée dans config.plist (Kernel.Add.14.Enabled = True). - QualcommWireless.kext (Bluetooth) préservé intact et 100% opérationnel.
727 lines
29 KiB
C++
727 lines
29 KiB
C++
#include "QualcommWifi.hpp"
|
|
#include <IOKit/IOLib.h>
|
|
#include <libkern/OSByteOrder.h>
|
|
#include <kern/task.h>
|
|
#include <string.h>
|
|
|
|
#define super IOEthernetController
|
|
OSDefineMetaClassAndStructors(QualcommWifi, IOEthernetController)
|
|
|
|
bool QualcommWifi::init(OSDictionary *dictionary) {
|
|
if (!super::init(dictionary)) {
|
|
return false;
|
|
}
|
|
fPciDevice = nullptr;
|
|
fBar0Map = nullptr;
|
|
fBar0Virt = nullptr;
|
|
fCurrWindow = 0xFFFFFFFF;
|
|
fInterruptSource = nullptr;
|
|
fNetif = nullptr;
|
|
bzero(&fMacAddress, sizeof(fMacAddress));
|
|
fLinkActive = false;
|
|
fCurrentChannel = 1;
|
|
fLastRssi = -50;
|
|
fDmaBuf = nullptr;
|
|
fDmaPhysAddr = 0;
|
|
fDmaVirtAddr = nullptr;
|
|
fDmaPrepared = false;
|
|
fFirmwareLoaded = false;
|
|
return true;
|
|
}
|
|
|
|
void QualcommWifi::free(void) {
|
|
super::free();
|
|
}
|
|
|
|
IOService *QualcommWifi::probe(IOService *provider, SInt32 *score) {
|
|
if (!super::probe(provider, score)) {
|
|
return nullptr;
|
|
}
|
|
|
|
IOPCIDevice *pciDev = OSDynamicCast(IOPCIDevice, provider);
|
|
if (!pciDev) {
|
|
return nullptr;
|
|
}
|
|
|
|
UInt16 vendorId = pciDev->configRead16(kIOPCIConfigVendorID);
|
|
UInt16 deviceId = pciDev->configRead16(kIOPCIConfigDeviceID);
|
|
|
|
if (vendorId == 0x17cb && deviceId == 0x1107) {
|
|
IOLog("QualcommWifi: Controleur Wi-Fi 7 Qualcomm WCN7850 detecte (0x%04x:0x%04x) !\n", vendorId, deviceId);
|
|
*score += 20000;
|
|
return this;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UInt32 QualcommWifi::read32(UInt32 offset) {
|
|
if (!fBar0Virt || offset >= 0x200000) {
|
|
return 0xFFFFFFFF;
|
|
}
|
|
return OSReadLittleInt32(fBar0Virt, offset);
|
|
}
|
|
|
|
void QualcommWifi::write32(UInt32 offset, UInt32 value) {
|
|
if (!fBar0Virt || offset >= 0x200000) {
|
|
return;
|
|
}
|
|
OSWriteLittleInt32(fBar0Virt, offset, value);
|
|
}
|
|
|
|
void QualcommWifi::selectWindow(UInt32 offset) {
|
|
UInt32 window = (offset >> ATH12K_PCI_WINDOW_SHIFT) & ATH12K_PCI_WINDOW_VALUE_MASK;
|
|
if (window != fCurrWindow) {
|
|
write32(ATH12K_PCI_WINDOW_REG_ADDRESS, ATH12K_PCI_WINDOW_ENABLE_BIT | window);
|
|
(void)read32(ATH12K_PCI_WINDOW_REG_ADDRESS);
|
|
fCurrWindow = window;
|
|
}
|
|
}
|
|
|
|
UInt32 QualcommWifi::socRead32(UInt32 offset) {
|
|
if (offset < ATH12K_PCI_WINDOW_START) {
|
|
return read32(offset);
|
|
}
|
|
selectWindow(offset);
|
|
return read32(ATH12K_PCI_WINDOW_START + (offset & ATH12K_PCI_WINDOW_RANGE_MASK));
|
|
}
|
|
|
|
void QualcommWifi::socWrite32(UInt32 offset, UInt32 value) {
|
|
if (offset < ATH12K_PCI_WINDOW_START) {
|
|
write32(offset, value);
|
|
return;
|
|
}
|
|
selectWindow(offset);
|
|
write32(ATH12K_PCI_WINDOW_START + (offset & ATH12K_PCI_WINDOW_RANGE_MASK), value);
|
|
}
|
|
|
|
// Équivalent de dma_alloc_coherent() avec DMA_BIT_MASK(36) sous Linux
|
|
bool QualcommWifi::allocDmaContext(IOByteCount size) {
|
|
fDmaBuf = IOBufferMemoryDescriptor::inTaskWithPhysicalMask(
|
|
kernel_task,
|
|
static_cast<IOOptionBits>(kIODirectionInOut) | static_cast<IOOptionBits>(kIOMemoryPhysicallyContiguous),
|
|
size,
|
|
ATH12K_DMA_MASK_36BIT_PAGE
|
|
);
|
|
|
|
if (!fDmaBuf) {
|
|
IOLog("QualcommWifi: Erreur - Impossible d'allouer le descripteur DMA 36-bit.\n");
|
|
return false;
|
|
}
|
|
|
|
if (fDmaBuf->prepare(kIODirectionInOut) != kIOReturnSuccess) {
|
|
IOLog("QualcommWifi: Erreur - Impossible de verrouiller (prepare) les pages DMA.\n");
|
|
OSSafeReleaseNULL(fDmaBuf);
|
|
return false;
|
|
}
|
|
fDmaPrepared = true;
|
|
|
|
fDmaVirtAddr = fDmaBuf->getBytesNoCopy();
|
|
IOByteCount segLen = 0;
|
|
fDmaPhysAddr = fDmaBuf->getPhysicalSegment(0, &segLen);
|
|
|
|
if (!fDmaVirtAddr || !fDmaPhysAddr || segLen < size) {
|
|
IOLog("QualcommWifi: Erreur - Segment physique DMA invalide (segLen=%llu).\n", (uint64_t)segLen);
|
|
freeDmaContext();
|
|
return false;
|
|
}
|
|
|
|
// Remise à zéro de la zone DMA (comme kzalloc / dma_alloc_coherent)
|
|
bzero(fDmaVirtAddr, size);
|
|
|
|
IOLog("QualcommWifi: Tampon DMA 36-bit pret ! Phys: 0x%llx, Taille: %llu octets (Contigu: %llu)\n",
|
|
(uint64_t)fDmaPhysAddr, (uint64_t)size, (uint64_t)segLen);
|
|
return true;
|
|
}
|
|
|
|
void QualcommWifi::freeDmaContext(void) {
|
|
if (fDmaBuf) {
|
|
if (fDmaPrepared) {
|
|
fDmaBuf->complete(kIODirectionInOut);
|
|
fDmaPrepared = false;
|
|
}
|
|
OSSafeReleaseNULL(fDmaBuf);
|
|
}
|
|
fDmaVirtAddr = nullptr;
|
|
fDmaPhysAddr = 0;
|
|
}
|
|
|
|
void QualcommWifi::interruptOccurred(OSObject *owner, IOInterruptEventSource *src, int count) {
|
|
IOLog("QualcommWifi: Interruption materielle MSI recue (count=%d) !\n", count);
|
|
}
|
|
|
|
bool QualcommWifi::loadBhiFirmware(void) {
|
|
if (!fBar0Virt || !fPciDevice) {
|
|
return false;
|
|
}
|
|
|
|
UInt32 bhiOff = read32(ATH12K_MHI_BHIOFF);
|
|
UInt32 bhieOff = read32(ATH12K_MHI_BHIEOFF);
|
|
if (bhiOff == 0xFFFFFFFF || bhiOff >= 0x200000 || bhieOff == 0xFFFFFFFF || bhieOff >= 0x200000) {
|
|
IOLog("QualcommWifi: Erreur - BHI/BHIe Offsets invalides (BHI: 0x%08x, BHIe: 0x%08x)\n", bhiOff, bhieOff);
|
|
setProperty("BHI_Load_Error", "Invalid_Offsets");
|
|
return false;
|
|
}
|
|
|
|
// Désactivation d'ASPM PCIe pour stabiliser le transfert firmware (recommandation ath12k)
|
|
UInt8 pciCapOffset = 0;
|
|
if (fPciDevice->findPCICapability(kIOPCIPCIExpressCapability, &pciCapOffset) && pciCapOffset != 0) {
|
|
UInt16 linkCtrl = fPciDevice->configRead16(pciCapOffset + 0x10);
|
|
linkCtrl &= ~0x3; // Désactive ASPM L0s / L1
|
|
fPciDevice->configWrite16(pciCapOffset + 0x10, linkCtrl);
|
|
IOLog("QualcommWifi: ASPM PCIe désactivé pour stabilité du transfert firmware (LinkCtrl: 0x%04x)\n", linkCtrl);
|
|
}
|
|
|
|
// Étape 1 : Réinitialisation MHI et effacement des vecteurs BHIe (ath12k_mhi_clear_vector)
|
|
write32(bhieOff + ATH12K_BHIE_TXVECDB, 0);
|
|
write32(bhieOff + ATH12K_BHIE_TXVECSTATUS, 0);
|
|
write32(bhieOff + ATH12K_BHIE_RXVECDB, 0);
|
|
write32(bhieOff + ATH12K_BHIE_RXVECSTATUS, 0);
|
|
|
|
write32(ATH12K_MHI_MHICTRL, ATH12K_MHICTRL_RESET_MASK); // MHICTRL_RESET
|
|
IODelay(10000); // 10 ms
|
|
|
|
size_t fwSize = get_wcn7850_amss_size();
|
|
size_t sblSize = 0x80000; // 512 Ko pour le Secondary Boot Loader (SBL)
|
|
if (fwSize < sblSize) {
|
|
IOLog("QualcommWifi: Erreur - Taille AMSS trop petite (%lu < 512 Ko)\n", fwSize);
|
|
return false;
|
|
}
|
|
|
|
IOLog("QualcommWifi: Initialisation du chargeur FBC 2-étapes (SBL: 512 Ko, Image totale: %lu octets)...\n", fwSize);
|
|
|
|
// Étape 2 : Allocation DMA contigu pour l'image AMSS complète
|
|
IOBufferMemoryDescriptor *fwDma = IOBufferMemoryDescriptor::inTaskWithPhysicalMask(
|
|
kernel_task,
|
|
static_cast<IOOptionBits>(kIODirectionInOut) | static_cast<IOOptionBits>(kIOMemoryPhysicallyContiguous),
|
|
fwSize,
|
|
ATH12K_DMA_MASK_36BIT_PAGE
|
|
);
|
|
|
|
if (!fwDma || fwDma->prepare(kIODirectionInOut) != kIOReturnSuccess) {
|
|
IOLog("QualcommWifi: Erreur - Echec allocation/prepare DMA AMSS\n");
|
|
setProperty("BHI_Load_Error", "DMA_Alloc_Failed");
|
|
if (fwDma) OSSafeReleaseNULL(fwDma);
|
|
return false;
|
|
}
|
|
|
|
void *virt = fwDma->getBytesNoCopy();
|
|
IOByteCount segLen = 0;
|
|
IOPhysicalAddress phys = fwDma->getPhysicalSegment(0, &segLen);
|
|
|
|
if (!virt || !phys || segLen < fwSize) {
|
|
IOLog("QualcommWifi: Erreur - Segment DMA AMSS incomplet\n");
|
|
fwDma->complete(kIODirectionInOut);
|
|
OSSafeReleaseNULL(fwDma);
|
|
return false;
|
|
}
|
|
|
|
memcpy(virt, wcn7850_amss_bin, fwSize);
|
|
setProperty("AMSS_DMA_Phys", (uint64_t)phys, 64);
|
|
|
|
// Étape 3 : Chargement du SBL (512 Ko) via BHI en mode PBL
|
|
write32(bhiOff + ATH12K_BHI_STATUS, 0);
|
|
write32(bhiOff + ATH12K_BHI_IMGADDR_LOW, (UInt32)(phys & 0xFFFFFFFF));
|
|
write32(bhiOff + ATH12K_BHI_IMGADDR_HIGH, (UInt32)((phys >> 32) & 0xFFFFFFFF));
|
|
write32(bhiOff + ATH12K_BHI_IMGSIZE, (UInt32)sblSize);
|
|
|
|
// Déclenchement de la sonnette PBL avec Session ID = 1 (NON-NUL exigé par PBL)
|
|
write32(bhiOff + ATH12K_BHI_IMGTXDB, 0x1);
|
|
IOLog("QualcommWifi: Sonnette BHI_IMGTXDB déclenchée (Session ID = 1, SBL 512 Ko)...\n");
|
|
|
|
UInt32 bhiStatus = 0;
|
|
UInt32 timeoutMs = 3000;
|
|
bool sblSuccess = false;
|
|
|
|
while (timeoutMs > 0) {
|
|
bhiStatus = read32(bhiOff + ATH12K_BHI_STATUS);
|
|
UInt32 state = (bhiStatus & ATH12K_BHI_STATUS_MASK) >> ATH12K_BHI_STATUS_SHIFT;
|
|
if (state == ATH12K_BHI_STATUS_SUCCESS) {
|
|
sblSuccess = true;
|
|
break;
|
|
} else if (state == ATH12K_BHI_STATUS_ERROR) {
|
|
UInt32 err = read32(bhiOff + ATH12K_BHI_ERRCODE);
|
|
IOLog("QualcommWifi: Erreur BHI SBL (code=0x%08x)\n", err);
|
|
setProperty("BHI_ErrorCode", err, 32);
|
|
break;
|
|
}
|
|
IODelay(1000); // 1 ms
|
|
timeoutMs--;
|
|
}
|
|
|
|
setProperty("BHI_FinalStatus", bhiStatus, 32);
|
|
setProperty("BHI_Status_Raw", bhiStatus, 32);
|
|
setProperty("BHI_Status_State", (bhiStatus & ATH12K_BHI_STATUS_MASK) >> ATH12K_BHI_STATUS_SHIFT, 32);
|
|
|
|
// Attente de l'exécution du SBL (BHI_EXECENV == 1) et de la disponibilité MHI (READY)
|
|
timeoutMs = 2000;
|
|
UInt32 ee = 0;
|
|
while (timeoutMs > 0) {
|
|
ee = read32(bhiOff + ATH12K_BHI_EXECENV);
|
|
UInt32 mhiStat = read32(ATH12K_MHI_MHISTATUS);
|
|
if (ee == ATH12K_MHI_EE_SBL || ee == ATH12K_MHI_EE_AMSS || (mhiStat & ATH12K_MHISTATUS_READY_MASK)) {
|
|
break;
|
|
}
|
|
IODelay(1000);
|
|
timeoutMs--;
|
|
}
|
|
|
|
setProperty("BHI_ExecEnv_AfterLoad", ee, 32);
|
|
setProperty("MHI_Status_After_SBL", read32(ATH12K_MHI_MHISTATUS), 32);
|
|
setProperty("MHI_Ctrl_After_SBL", read32(ATH12K_MHI_MHICTRL), 32);
|
|
IOLog("QualcommWifi: SBL BHI terminé (Status=0x%08x, State=%u, ExecEnv=0x%x, SBL_Succes=%d)\n",
|
|
bhiStatus, (bhiStatus & ATH12K_BHI_STATUS_MASK) >> ATH12K_BHI_STATUS_SHIFT, ee, sblSuccess);
|
|
|
|
// Étape 4 : Configuration des contextes MHI MMIO et transition vers M0 (mhi_ready_state_transition selon Linux ath12k)
|
|
bool inM0 = false;
|
|
bool amssSuccess = false;
|
|
uint32_t processedEvents = 0;
|
|
|
|
if (sblSuccess && fDmaPhysAddr != 0) {
|
|
// Attente de la confirmation READY dans MHISTATUS
|
|
timeoutMs = 3000;
|
|
while (timeoutMs > 0) {
|
|
UInt32 mstat = read32(ATH12K_MHI_MHISTATUS);
|
|
if (mstat & ATH12K_MHISTATUS_READY_MASK) {
|
|
break;
|
|
}
|
|
IODelay(1000);
|
|
timeoutMs--;
|
|
}
|
|
|
|
uint64_t chan_ctxt_phys = fDmaPhysAddr + 0x0000;
|
|
uint64_t er_ctxt_phys = fDmaPhysAddr + 0x1000;
|
|
uint64_t cmd_ctxt_phys = fDmaPhysAddr + 0x2000;
|
|
uint64_t cmd_ring_phys = fDmaPhysAddr + 0x3000;
|
|
uint64_t er0_ring_phys = fDmaPhysAddr + 0x4000;
|
|
uint64_t er1_ring_phys = fDmaPhysAddr + 0x5000;
|
|
|
|
uint32_t cmd_ring_len = 64 * sizeof(struct mhi_ring_element); // 1024 octets (64 TREs)
|
|
uint32_t er0_ring_len = 32 * sizeof(struct mhi_ring_element); // 512 octets (32 TREs) selon ath12k WCN7850
|
|
uint32_t er1_ring_len = 256 * sizeof(struct mhi_ring_element); // 4096 octets (256 TREs)
|
|
|
|
// 1. Initialisation des 64 contextes de canaux (désactivés au départ)
|
|
struct mhi_chan_ctxt *chan_ctxt = (struct mhi_chan_ctxt *)((uint8_t *)fDmaVirtAddr + 0x0000);
|
|
bzero(chan_ctxt, 64 * sizeof(struct mhi_chan_ctxt));
|
|
|
|
// 2. Initialisation du contexte d'anneau de commandes
|
|
struct mhi_cmd_ctxt *cmd_ctxt = (struct mhi_cmd_ctxt *)((uint8_t *)fDmaVirtAddr + 0x2000);
|
|
bzero(cmd_ctxt, sizeof(struct mhi_cmd_ctxt));
|
|
cmd_ctxt[0].rbase = OSSwapHostToLittleInt64(cmd_ring_phys);
|
|
cmd_ctxt[0].rlen = OSSwapHostToLittleInt64(cmd_ring_len);
|
|
cmd_ctxt[0].rp = OSSwapHostToLittleInt64(cmd_ring_phys);
|
|
cmd_ctxt[0].wp = OSSwapHostToLittleInt64(cmd_ring_phys);
|
|
|
|
// 3. Initialisation des contextes d'anneau d'événements (2 anneaux selon WCN7850 ath12k)
|
|
struct mhi_event_ctxt *evCtxt = (struct mhi_event_ctxt *)((uint8_t *)fDmaVirtAddr + 0x1000);
|
|
bzero(evCtxt, 2 * sizeof(struct mhi_event_ctxt));
|
|
|
|
// Event Ring 0: CTRL ring, 32 TREs
|
|
evCtxt[0].intmod = 0;
|
|
evCtxt[0].ertype = OSSwapHostToLittleInt32(1); // MHI_ER_TYPE_VALID
|
|
evCtxt[0].msivec = 0;
|
|
evCtxt[0].rbase = OSSwapHostToLittleInt64(er0_ring_phys);
|
|
evCtxt[0].rlen = OSSwapHostToLittleInt64(er0_ring_len);
|
|
evCtxt[0].rp = OSSwapHostToLittleInt64(er0_ring_phys);
|
|
evCtxt[0].wp = OSSwapHostToLittleInt64(er0_ring_phys);
|
|
|
|
// Event Ring 1: DATA ring, 256 TREs
|
|
evCtxt[1].intmod = OSSwapHostToLittleInt32(1);
|
|
evCtxt[1].ertype = OSSwapHostToLittleInt32(1); // MHI_ER_TYPE_VALID
|
|
evCtxt[1].msivec = 0;
|
|
evCtxt[1].rbase = OSSwapHostToLittleInt64(er1_ring_phys);
|
|
evCtxt[1].rlen = OSSwapHostToLittleInt64(er1_ring_len);
|
|
evCtxt[1].rp = OSSwapHostToLittleInt64(er1_ring_phys);
|
|
evCtxt[1].wp = OSSwapHostToLittleInt64(er1_ring_phys);
|
|
|
|
bzero((uint8_t *)fDmaVirtAddr + 0x3000, cmd_ring_len);
|
|
bzero((uint8_t *)fDmaVirtAddr + 0x4000, er0_ring_len);
|
|
bzero((uint8_t *)fDmaVirtAddr + 0x5000, er1_ring_len);
|
|
|
|
// 4. Programmation des registres MMIO MHI
|
|
write32(ATH12K_MHI_CCABAP_HIGHER, (UInt32)((chan_ctxt_phys >> 32) & 0xFFFFFFFF));
|
|
write32(ATH12K_MHI_CCABAP_LOWER, (UInt32)(chan_ctxt_phys & 0xFFFFFFFF));
|
|
write32(ATH12K_MHI_ECABAP_HIGHER, (UInt32)((er_ctxt_phys >> 32) & 0xFFFFFFFF));
|
|
write32(ATH12K_MHI_ECABAP_LOWER, (UInt32)(er_ctxt_phys & 0xFFFFFFFF));
|
|
write32(ATH12K_MHI_CRCBAP_HIGHER, (UInt32)((cmd_ctxt_phys >> 32) & 0xFFFFFFFF));
|
|
write32(ATH12K_MHI_CRCBAP_LOWER, (UInt32)(cmd_ctxt_phys & 0xFFFFFFFF));
|
|
|
|
// MHICFG (0x10): total_ev_rings = 2, hw_ev_rings = 0 (tous logiciels pour WCN7850)
|
|
write32(ATH12K_MHI_MHICFG, (2 << 16) | (0 << 24));
|
|
|
|
write32(ATH12K_MHI_MHICTRLBASE_HIGHER, 0);
|
|
write32(ATH12K_MHI_MHICTRLBASE_LOWER, 0);
|
|
write32(ATH12K_MHI_MHIDATABASE_HIGHER, 0);
|
|
write32(ATH12K_MHI_MHIDATABASE_LOWER, 0);
|
|
|
|
write32(ATH12K_MHI_MHICTRLLIMIT_HIGHER, 0x0F);
|
|
write32(ATH12K_MHI_MHICTRLLIMIT_LOWER, 0xFFFFFFFF);
|
|
write32(ATH12K_MHI_MHIDATALIMIT_HIGHER, 0x0F);
|
|
write32(ATH12K_MHI_MHIDATALIMIT_LOWER, 0xFFFFFFFF);
|
|
|
|
// 5. Armement des Event Ring Doorbells (ERDB)
|
|
uint64_t er0_armed_wp = er0_ring_phys + er0_ring_len - sizeof(struct mhi_ring_element);
|
|
evCtxt[0].wp = OSSwapHostToLittleInt64(er0_armed_wp);
|
|
|
|
uint64_t er1_armed_wp = er1_ring_phys + er1_ring_len - sizeof(struct mhi_ring_element);
|
|
evCtxt[1].wp = OSSwapHostToLittleInt64(er1_armed_wp);
|
|
|
|
UInt32 erdbOff = read32(ATH12K_MHI_ERDBOFF);
|
|
if (erdbOff != 0 && erdbOff < 0x200000) {
|
|
write32(erdbOff + 4, (UInt32)((er0_armed_wp >> 32) & 0xFFFFFFFF));
|
|
write32(erdbOff + 0, (UInt32)(er0_armed_wp & 0xFFFFFFFF));
|
|
|
|
write32(erdbOff + 8 + 4, (UInt32)((er1_armed_wp >> 32) & 0xFFFFFFFF));
|
|
write32(erdbOff + 8 + 0, (UInt32)(er1_armed_wp & 0xFFFFFFFF));
|
|
|
|
IOLog("QualcommWifi: Event Ring Doorbells armées (ER0 @ 0x%08x WP=0x%llx, ER1 @ 0x%08x WP=0x%llx) !\n",
|
|
erdbOff, er0_armed_wp, erdbOff + 8, er1_armed_wp);
|
|
setProperty("ERDB_Offset", erdbOff, 32);
|
|
}
|
|
|
|
// 6. Bascule vers l'état M0
|
|
UInt32 mhiCtrlVal = read32(ATH12K_MHI_MHICTRL);
|
|
mhiCtrlVal &= ~(UInt32)ATH12K_MHICTRL_MHISTATE_MASK;
|
|
mhiCtrlVal |= (ATH12K_MHI_STATE_M0 << ATH12K_MHICTRL_MHISTATE_SHIFT);
|
|
write32(ATH12K_MHI_MHICTRL, mhiCtrlVal);
|
|
IOLog("QualcommWifi: Transition MHI vers M0 demandée (MHICTRL: 0x%08x)...\n", mhiCtrlVal);
|
|
}
|
|
|
|
// Étape 5 : Téléversement FBC complet du firmware via BHIe (mhi_download_amss_image sous SBL actif)
|
|
const size_t segSize = 0x80000; // Segments de 512 Ko (SZ_512K exigé par ath12k)
|
|
size_t numVecEntries = (fwSize + segSize - 1) / segSize;
|
|
|
|
IOBufferMemoryDescriptor *vecDma = IOBufferMemoryDescriptor::inTaskWithPhysicalMask(
|
|
kernel_task,
|
|
static_cast<IOOptionBits>(kIODirectionInOut) | static_cast<IOOptionBits>(kIOMemoryPhysicallyContiguous),
|
|
numVecEntries * sizeof(struct bhi_vec_entry),
|
|
ATH12K_DMA_MASK_36BIT_PAGE
|
|
);
|
|
|
|
if (vecDma) {
|
|
if (vecDma->prepare(kIODirectionInOut) == kIOReturnSuccess) {
|
|
struct bhi_vec_entry *vecTable = (struct bhi_vec_entry *)vecDma->getBytesNoCopy();
|
|
IOByteCount vecLen = 0;
|
|
IOPhysicalAddress vecPhys = vecDma->getPhysicalSegment(0, &vecLen);
|
|
|
|
if (vecTable && vecPhys) {
|
|
size_t curRem = fwSize;
|
|
|
|
for (size_t i = 0; i < numVecEntries; i++) {
|
|
size_t chunk = (curRem > segSize) ? segSize : curRem;
|
|
vecTable[i].dma_addr = OSSwapHostToLittleInt64((uint64_t)phys + (uint64_t)i * segSize);
|
|
vecTable[i].size = OSSwapHostToLittleInt64(chunk);
|
|
curRem -= chunk;
|
|
}
|
|
|
|
IOLog("QualcommWifi: Table vectorielle BHIe FBC prête (%zu segments, Phys: 0x%llx). Programmation BHIe...\n",
|
|
numVecEntries, (uint64_t)vecPhys);
|
|
|
|
write32(bhieOff + ATH12K_BHIE_TXVECADDR_LOW, (UInt32)(vecPhys & 0xFFFFFFFF));
|
|
write32(bhieOff + ATH12K_BHIE_TXVECADDR_HIGH, (UInt32)((vecPhys >> 32) & 0xFFFFFFFF));
|
|
write32(bhieOff + ATH12K_BHIE_TXVECSIZE, (UInt32)(numVecEntries * sizeof(struct bhi_vec_entry)));
|
|
write32(bhieOff + ATH12K_BHIE_TXVECSTATUS, 0);
|
|
|
|
// Sonnette BHIe avec Sequence ID = 1
|
|
write32(bhieOff + ATH12K_BHIE_TXVECDB, 0x1);
|
|
|
|
UInt32 bhieStatus = 0;
|
|
timeoutMs = 10000; // 10 secondes pour le transfert DMA complet de 6 Mo
|
|
while (timeoutMs > 0) {
|
|
bhieStatus = read32(bhieOff + ATH12K_BHIE_TXVECSTATUS);
|
|
UInt32 bhieState = (bhieStatus & ATH12K_BHIE_TXVECSTATUS_STATUS_MASK) >> ATH12K_BHIE_TXVECSTATUS_STATUS_SHIFT;
|
|
if (bhieState == ATH12K_BHIE_TXVECSTATUS_XFER_COMPL) {
|
|
amssSuccess = true;
|
|
break;
|
|
}
|
|
IODelay(1000);
|
|
timeoutMs--;
|
|
}
|
|
|
|
setProperty("BHIE_FinalStatus", bhieStatus, 32);
|
|
setProperty("BHIE_Status_Raw", bhieStatus, 32);
|
|
setProperty("BHIE_Status_State", (bhieStatus & ATH12K_BHIE_TXVECSTATUS_STATUS_MASK) >> ATH12K_BHIE_TXVECSTATUS_STATUS_SHIFT, 32);
|
|
IOLog("QualcommWifi: BHIe DMA terminé (Status=0x%08x) ! Dépouillement des événements MHI...\n", bhieStatus);
|
|
|
|
// Étape 6 : Dépouillement des événements MHI émis par SBL / AMSS sur l'anneau 0
|
|
struct mhi_ring_element *erElements = (struct mhi_ring_element *)((uint8_t *)fDmaVirtAddr + 0x4000);
|
|
|
|
timeoutMs = 5000;
|
|
while (timeoutMs > 0) {
|
|
UInt32 mstat = read32(ATH12K_MHI_MHISTATUS);
|
|
UInt32 mstate = (mstat & ATH12K_MHISTATUS_MHISTATE_MASK) >> ATH12K_MHISTATUS_MHISTATE_SHIFT;
|
|
|
|
while (processedEvents < 32) {
|
|
struct mhi_ring_element *el = &erElements[processedEvents];
|
|
UInt32 dw1 = OSSwapLittleToHostInt32(el->dword[1]);
|
|
UInt32 dw0 = OSSwapLittleToHostInt32(el->dword[0]);
|
|
if (dw1 == 0 && dw0 == 0) {
|
|
break;
|
|
}
|
|
|
|
UInt8 evType = (dw1 >> 16) & 0xFF;
|
|
UInt8 evCode = (dw0 >> 24) & 0xFF;
|
|
IOLog("QualcommWifi: Événement MHI reçu #%u: Type=0x%02x, Code=0x%02x, PTR=0x%llx\n",
|
|
processedEvents, evType, evCode, OSSwapLittleToHostInt64(el->ptr));
|
|
|
|
char evKey[32];
|
|
snprintf(evKey, sizeof(evKey), "MHI_Event_%u_Type", processedEvents);
|
|
setProperty(evKey, evType, 8);
|
|
snprintf(evKey, sizeof(evKey), "MHI_Event_%u_Code", processedEvents);
|
|
setProperty(evKey, evCode, 8);
|
|
snprintf(evKey, sizeof(evKey), "MHI_Event_%u_PTR", processedEvents);
|
|
setProperty(evKey, OSSwapLittleToHostInt64(el->ptr), 64);
|
|
|
|
if (evType == ATH12K_MHI_PKT_TYPE_STATE_CHANGE_EVENT) {
|
|
IOLog("QualcommWifi: -> Notification changement d'état MHI: State=%u\n", evCode);
|
|
if (evCode == ATH12K_MHI_STATE_M0) {
|
|
inM0 = true;
|
|
}
|
|
} else if (evType == ATH12K_MHI_PKT_TYPE_EE_EVENT) {
|
|
IOLog("QualcommWifi: -> Notification environnement d'exécution (EE): 0x%x\n", evCode);
|
|
ee = evCode;
|
|
if (ee == ATH12K_MHI_EE_AMSS || ee == ATH12K_MHI_EE_WFW) {
|
|
amssSuccess = true;
|
|
}
|
|
}
|
|
processedEvents++;
|
|
}
|
|
|
|
if (mstate == ATH12K_MHI_STATE_M0 || inM0 || ee == ATH12K_MHI_EE_AMSS || ee == ATH12K_MHI_EE_WFW) {
|
|
inM0 = true;
|
|
IOLog("QualcommWifi: MHI en mode Mission M0 (MHISTATUS: 0x%08x, EE: 0x%x) !\n", mstat, ee);
|
|
break;
|
|
}
|
|
|
|
IODelay(1000);
|
|
timeoutMs--;
|
|
}
|
|
|
|
setProperty("MHI_Events_Processed", processedEvents, 32);
|
|
setProperty("MHI_Status_In_M0", read32(ATH12K_MHI_MHISTATUS), 32);
|
|
setProperty("MHI_In_M0", inM0);
|
|
setProperty("BHI_ExecEnv_Final", ee, 32);
|
|
}
|
|
|
|
vecDma->complete(kIODirectionInOut);
|
|
}
|
|
OSSafeReleaseNULL(vecDma);
|
|
}
|
|
|
|
if (inM0 || ee == ATH12K_MHI_EE_AMSS || ee == ATH12K_MHI_EE_WFW) {
|
|
IOLog("QualcommWifi: SUCCÈS MAJEUR ! Firmware WCN7850 actif en mode Mission (ExecEnv=0x%x, M0=%d) !\n", ee, inM0);
|
|
setProperty("Driver_Status", "AMSS_Loaded_Success");
|
|
} else if (sblSuccess) {
|
|
IOLog("QualcommWifi: SBL actif (ExecEnv=0x%x, BHIe=0x%x)\n", ee, read32(bhieOff + ATH12K_BHIE_TXVECSTATUS));
|
|
setProperty("Driver_Status", "SBL_Loaded_Success");
|
|
} else {
|
|
IOLog("QualcommWifi: Échec chargement complet (BHI=0x%x, ExecEnv=0x%x)\n", bhiStatus, ee);
|
|
setProperty("Driver_Status", "AMSS_Load_Timeout_Or_Error");
|
|
}
|
|
|
|
fwDma->complete(kIODirectionInOut);
|
|
OSSafeReleaseNULL(fwDma);
|
|
|
|
return (sblSuccess || amssSuccess);
|
|
}
|
|
|
|
bool QualcommWifi::start(IOService *provider) {
|
|
if (!super::start(provider)) {
|
|
IOLog("QualcommWifi: Erreur - Echec de IOEthernetController::start(provider) !\n");
|
|
return false;
|
|
}
|
|
|
|
fPciDevice = OSDynamicCast(IOPCIDevice, provider);
|
|
if (!fPciDevice) {
|
|
return false;
|
|
}
|
|
fPciDevice->retain();
|
|
|
|
if (!fPciDevice->open(this)) {
|
|
OSSafeReleaseNULL(fPciDevice);
|
|
return false;
|
|
}
|
|
|
|
UInt16 pciCommand = fPciDevice->configRead16(kIOPCIConfigCommand);
|
|
pciCommand |= (kIOPCICommandMemorySpace | kIOPCICommandBusMaster);
|
|
fPciDevice->configWrite16(kIOPCIConfigCommand, pciCommand);
|
|
|
|
fBar0Map = fPciDevice->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0);
|
|
if (!fBar0Map) {
|
|
fPciDevice->close(this);
|
|
OSSafeReleaseNULL(fPciDevice);
|
|
return false;
|
|
}
|
|
fBar0Virt = (volatile void *)fBar0Map->getVirtualAddress();
|
|
|
|
UInt32 socHwVer = socRead32(TCSR_SOC_HW_VERSION);
|
|
UInt32 major = (socHwVer & TCSR_SOC_HW_VERSION_MAJOR_MASK) >> TCSR_SOC_HW_VERSION_MAJOR_SHIFT;
|
|
UInt32 minor = (socHwVer & TCSR_SOC_HW_VERSION_MINOR_MASK);
|
|
IOLog("QualcommWifi: WCN7850 HW v%u.%u detecte (0x%08x)\n", major, minor, socHwVer);
|
|
|
|
setProperty("SoC_Version", socHwVer, 32);
|
|
setProperty("SoC_Major", major, 32);
|
|
setProperty("SoC_Minor", minor, 32);
|
|
|
|
// Lecture des registres de contrôle MHI et pointeurs BHI
|
|
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;
|
|
if (bhiOff != 0xFFFFFFFF && bhiOff < 0x200000) {
|
|
UInt32 bhiMajor = read32(bhiOff + ATH12K_BHI_BHIVERSION_MAJOR);
|
|
UInt32 bhiMinor = read32(bhiOff + ATH12K_BHI_BHIVERSION_MINOR);
|
|
UInt32 bhiEe = read32(bhiOff + ATH12K_BHI_EXECENV);
|
|
UInt32 bhiStat = read32(bhiOff + ATH12K_BHI_STATUS);
|
|
bhiSerial = read32(bhiOff + ATH12K_BHI_SERIALNU);
|
|
IOLog("QualcommWifi: BHI v%u.%u: ExecEnv=0x%x, Status=0x%x, Serial=0x%08x\n",
|
|
bhiMajor, bhiMinor, bhiEe, bhiStat, bhiSerial);
|
|
|
|
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);
|
|
}
|
|
|
|
// Configuration de l'adresse MAC (OUI Qualcomm 00:03:7F + BHI serial)
|
|
fMacAddress.bytes[0] = 0x00;
|
|
fMacAddress.bytes[1] = 0x03;
|
|
fMacAddress.bytes[2] = 0x7F;
|
|
fMacAddress.bytes[3] = (bhiSerial >> 16) & 0xFF;
|
|
fMacAddress.bytes[4] = (bhiSerial >> 8) & 0xFF;
|
|
fMacAddress.bytes[5] = (bhiSerial) & 0xFF;
|
|
|
|
IOLog("QualcommWifi: Adresse MAC physique : %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
fMacAddress.bytes[0], fMacAddress.bytes[1], fMacAddress.bytes[2],
|
|
fMacAddress.bytes[3], fMacAddress.bytes[4], fMacAddress.bytes[5]);
|
|
|
|
// Configuration et publication des médias supportés (Auto / 1Gbps)
|
|
OSDictionary *mediumDict = OSDictionary::withCapacity(1);
|
|
if (mediumDict) {
|
|
IONetworkMedium *medium = IONetworkMedium::medium(kIOMediumEthernetAuto, 1000000000ULL);
|
|
if (medium) {
|
|
IONetworkMedium::addMedium(mediumDict, medium);
|
|
publishMediumDictionary(mediumDict);
|
|
setSelectedMedium(medium);
|
|
medium->release();
|
|
}
|
|
mediumDict->release();
|
|
}
|
|
|
|
// Attachement de l'interface réseau native (IOEthernetInterface)
|
|
if (attachInterface((IONetworkInterface **)&fNetif, true)) {
|
|
IOLog("QualcommWifi: Interface reseau (IOEthernetInterface) attachee avec succes !\n");
|
|
setProperty("Network_Interface_Attached", true);
|
|
} else {
|
|
IOLog("QualcommWifi: Echec de attachInterface IOEthernetInterface.\n");
|
|
setProperty("Network_Interface_Attached", false);
|
|
}
|
|
|
|
registerService();
|
|
return true;
|
|
}
|
|
|
|
void QualcommWifi::stop(IOService *provider) {
|
|
IOLog("QualcommWifi: Arret du pilote, liberation interface reseau, MSI, DMA et BAR0.\n");
|
|
|
|
if (fNetif) {
|
|
detachInterface(fNetif);
|
|
OSSafeReleaseNULL(fNetif);
|
|
}
|
|
|
|
IOWorkLoop *workLoop = getWorkLoop();
|
|
if (fInterruptSource) {
|
|
fInterruptSource->disable();
|
|
if (workLoop) {
|
|
workLoop->removeEventSource(fInterruptSource);
|
|
}
|
|
OSSafeReleaseNULL(fInterruptSource);
|
|
}
|
|
|
|
// Déverrouillage et libération du tampon DMA avant la fermeture PCIe
|
|
freeDmaContext();
|
|
|
|
fBar0Virt = nullptr;
|
|
if (fBar0Map) {
|
|
OSSafeReleaseNULL(fBar0Map);
|
|
}
|
|
|
|
if (fPciDevice) {
|
|
fPciDevice->close(this);
|
|
OSSafeReleaseNULL(fPciDevice);
|
|
}
|
|
|
|
super::stop(provider);
|
|
}
|
|
|
|
IOReturn QualcommWifi::getHardwareAddress(IOEthernetAddress *addrP) {
|
|
if (!addrP) return kIOReturnBadArgument;
|
|
bcopy(&fMacAddress, addrP, sizeof(IOEthernetAddress));
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
IOReturn QualcommWifi::enable(IONetworkInterface *netif) {
|
|
IOLog("QualcommWifi: Interface reseau activee par macOS.\n");
|
|
|
|
if (!fFirmwareLoaded) {
|
|
// Allocation du tampon DMA 36-bit pour les anneaux MHI / BHI
|
|
if (allocDmaContext(ATH12K_DMA_TEST_RING_SIZE)) {
|
|
setProperty("DMA_PhysAddr", (uint64_t)fDmaPhysAddr, 64);
|
|
|
|
int msiIndex = -1;
|
|
for (int i = 0; ; i++) {
|
|
int intrType = 0;
|
|
if (fPciDevice->getInterruptType(i, &intrType) != kIOReturnSuccess) {
|
|
break;
|
|
}
|
|
if (intrType & kIOInterruptTypePCIMessaged) {
|
|
msiIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
IOWorkLoop *workLoop = getWorkLoop();
|
|
if (msiIndex >= 0 && workLoop && !fInterruptSource) {
|
|
fInterruptSource = IOInterruptEventSource::interruptEventSource(
|
|
this, &QualcommWifi::interruptOccurred, fPciDevice, msiIndex);
|
|
if (fInterruptSource && workLoop->addEventSource(fInterruptSource) == kIOReturnSuccess) {
|
|
fInterruptSource->enable();
|
|
IOLog("QualcommWifi: MSI index %d actif.\n", msiIndex);
|
|
}
|
|
}
|
|
|
|
// Chargement du firmware radio AMSS via le protocole BHI
|
|
if (loadBhiFirmware()) {
|
|
fFirmwareLoaded = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const IONetworkMedium *medium = getSelectedMedium();
|
|
setLinkStatus(kIONetworkLinkValid | kIONetworkLinkActive, medium);
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
IOReturn QualcommWifi::disable(IONetworkInterface *netif) {
|
|
IOLog("QualcommWifi: Interface reseau desactivee par macOS.\n");
|
|
setLinkStatus(kIONetworkLinkValid, nullptr);
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
UInt32 QualcommWifi::outputPacket(mbuf_t m, void *param) {
|
|
if (m) {
|
|
freePacket(m);
|
|
}
|
|
return kIOReturnOutputDropped;
|
|
}
|