376 lines
14 KiB
C++
376 lines
14 KiB
C++
#include "QualcommWireless.hpp"
|
|
#include "Firmware_WCN7850.h"
|
|
|
|
#include <IOKit/IOLib.h>
|
|
#include <libkern/OSByteOrder.h>
|
|
#include <IOKit/usb/IOUSBHostFamily.h>
|
|
|
|
#define super IOService
|
|
OSDefineMetaClassAndStructors(QualcommWireless, IOService)
|
|
|
|
bool QualcommWireless::init(OSDictionary *dictionary) {
|
|
if (!super::init(dictionary)) return false;
|
|
fDevice = nullptr;
|
|
fInterface = nullptr;
|
|
fBulkOutPipe = nullptr;
|
|
fIntrInPipe = nullptr;
|
|
fBulkBuffer = nullptr;
|
|
return true;
|
|
}
|
|
|
|
void QualcommWireless::free(void) {
|
|
super::free();
|
|
}
|
|
|
|
IOService *QualcommWireless::probe(IOService *provider, SInt32 *score) {
|
|
if (!super::probe(provider, score)) return nullptr;
|
|
|
|
IOUSBHostDevice *dev = OSDynamicCast(IOUSBHostDevice, provider);
|
|
if (!dev) return nullptr;
|
|
|
|
const StandardUSB::DeviceDescriptor *desc = dev->getDeviceDescriptor();
|
|
if (!desc) return nullptr;
|
|
|
|
UInt16 vid = OSSwapLittleToHostInt16(desc->idVendor);
|
|
UInt16 pid = OSSwapLittleToHostInt16(desc->idProduct);
|
|
|
|
if (vid == 0x0489 && pid == 0xe10d) {
|
|
IOLog("QualcommWireless: Detecte controleur Bluetooth Foxconn/Qualcomm (0x%04x:0x%04x) !\n", vid, pid);
|
|
*score += 10000;
|
|
return this;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
uint8_t QualcommWireless::checkStatus(void) {
|
|
uint8_t status = 0;
|
|
uint32_t transferred = 0;
|
|
|
|
StandardUSB::DeviceRequest req;
|
|
req.bmRequestType = makeDeviceRequestbmRequestType(kRequestDirectionIn,
|
|
kRequestTypeVendor,
|
|
kRequestRecipientDevice);
|
|
req.bRequest = QCA_CHECK_STATUS;
|
|
req.wValue = 0;
|
|
req.wIndex = 0;
|
|
req.wLength = sizeof(status);
|
|
|
|
IOReturn kr = fDevice->deviceRequest(this, req, &status, transferred, 2000);
|
|
if (kr != kIOReturnSuccess) {
|
|
IOLog("QualcommWireless: Erreur checkStatus (0x%08x)\n", kr);
|
|
return 0;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool QualcommWireless::queryTargetVersion(struct qca_version *verOut) {
|
|
uint32_t transferred = 0;
|
|
|
|
StandardUSB::DeviceRequest req;
|
|
req.bmRequestType = makeDeviceRequestbmRequestType(kRequestDirectionIn,
|
|
kRequestTypeVendor,
|
|
kRequestRecipientDevice);
|
|
req.bRequest = QCA_GET_TARGET_VERSION;
|
|
req.wValue = 0;
|
|
req.wIndex = 0;
|
|
req.wLength = sizeof(struct qca_version);
|
|
|
|
IOReturn kr = fDevice->deviceRequest(this, req, verOut, transferred, 2000);
|
|
if (kr != kIOReturnSuccess || transferred < sizeof(struct qca_version)) {
|
|
IOLog("QualcommWireless: Erreur queryTargetVersion (0x%08x, transferred=%u)\n", kr, transferred);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QualcommWireless::downloadFirmware(const uint8_t *data, size_t size, size_t hdrSize) {
|
|
if (!fDevice || !fBulkOutPipe || !fBulkBuffer || size < hdrSize) return false;
|
|
|
|
// 1. Envoi du header via requête de contrôle vendeur DFU
|
|
uint32_t transferred = 0;
|
|
StandardUSB::DeviceRequest req;
|
|
req.bmRequestType = makeDeviceRequestbmRequestType(kRequestDirectionOut,
|
|
kRequestTypeVendor,
|
|
kRequestRecipientDevice);
|
|
req.bRequest = QCA_DFU_DOWNLOAD;
|
|
req.wValue = 0;
|
|
req.wIndex = 0;
|
|
req.wLength = (uint16_t)hdrSize;
|
|
|
|
IOReturn kr = fDevice->deviceRequest(this, req, (void *)data, transferred, 2000);
|
|
if (kr != kIOReturnSuccess) {
|
|
IOLog("QualcommWireless: Erreur envoi header DFU (0x%08x)\n", kr);
|
|
return false;
|
|
}
|
|
|
|
// Délai de 20ms pour permettre au contrôleur d'activer le mode DFU sur ep2
|
|
IOSleep(20);
|
|
|
|
// 2. Envoi du corps par paquets de 4096 octets max sur le pipe Bulk OUT
|
|
size_t sent = hdrSize;
|
|
size_t remaining = size - hdrSize;
|
|
void *bufPtr = fBulkBuffer->getBytesNoCopy();
|
|
|
|
while (remaining > 0) {
|
|
uint32_t chunk = (remaining > QCA_DFU_PACKET_LEN) ? QCA_DFU_PACKET_LEN : (uint32_t)remaining;
|
|
memcpy(bufPtr, data + sent, chunk);
|
|
|
|
uint32_t bytesWritten = 0;
|
|
kr = fBulkOutPipe->io(fBulkBuffer, chunk, bytesWritten, 3000);
|
|
if (kr != kIOReturnSuccess || bytesWritten != chunk) {
|
|
IOLog("QualcommWireless: Erreur io pipe bulk (offset %zu/%zu, kr=0x%08x)\n", sent, size, kr);
|
|
return false;
|
|
}
|
|
|
|
sent += chunk;
|
|
remaining -= chunk;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QualcommWireless::sendHciReset(void) {
|
|
uint8_t cmd[3] = {0x03, 0x0c, 0x00}; // HCI Reset
|
|
uint32_t transferred = 0;
|
|
|
|
StandardUSB::DeviceRequest req;
|
|
req.bmRequestType = makeDeviceRequestbmRequestType(kRequestDirectionOut,
|
|
kRequestTypeClass,
|
|
kRequestRecipientInterface);
|
|
req.bRequest = 0;
|
|
req.wValue = 0;
|
|
req.wIndex = 0;
|
|
req.wLength = sizeof(cmd);
|
|
|
|
IOReturn kr = fDevice->deviceRequest(this, req, cmd, transferred, 2000);
|
|
if (kr != kIOReturnSuccess) {
|
|
IOLog("QualcommWireless: Erreur envoi HCI Reset (0x%08x)\n", kr);
|
|
return false;
|
|
}
|
|
|
|
if (fIntrInPipe && fBulkBuffer) {
|
|
uint32_t bytesRead = 0;
|
|
kr = fIntrInPipe->io(fBulkBuffer, 256, bytesRead, 2000);
|
|
if (kr == kIOReturnSuccess && bytesRead > 0) {
|
|
uint8_t *evt = (uint8_t *)fBulkBuffer->getBytesNoCopy();
|
|
if (evt[0] == 0x0E && evt[5] == 0) {
|
|
IOLog("QualcommWireless: HCI Reset acquitte avec succes par le controleur !\n");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QualcommWireless::start(IOService *provider) {
|
|
if (!super::start(provider)) return false;
|
|
|
|
fDevice = OSDynamicCast(IOUSBHostDevice, provider);
|
|
if (!fDevice) return false;
|
|
fDevice->retain();
|
|
|
|
if (!fDevice->open(this)) {
|
|
IOLog("QualcommWireless: Impossible d'ouvrir le peripherique USB.\n");
|
|
OSSafeReleaseNULL(fDevice);
|
|
return false;
|
|
}
|
|
|
|
// Configuration 1
|
|
IOReturn kr = fDevice->setConfiguration(1);
|
|
if (kr != kIOReturnSuccess) {
|
|
IOLog("QualcommWireless: Erreur setConfiguration(1) -> 0x%08x\n", kr);
|
|
fDevice->close(this);
|
|
OSSafeReleaseNULL(fDevice);
|
|
return false;
|
|
}
|
|
|
|
struct qca_version ver;
|
|
memset(&ver, 0, sizeof(ver));
|
|
if (queryTargetVersion(&ver)) {
|
|
IOLog("QualcommWireless: QCA Version ROM=0x%08x Patch=0x%08x RAM=0x%08x\n",
|
|
OSSwapLittleToHostInt32(ver.rom_version),
|
|
OSSwapLittleToHostInt32(ver.patch_version),
|
|
OSSwapLittleToHostInt32(ver.ram_version));
|
|
}
|
|
|
|
uint8_t status = checkStatus();
|
|
IOLog("QualcommWireless: Statut initial QCA: 0x%02x (Patch: %s, Syscfg: %s)\n",
|
|
status, (status & 0x80) ? "OUI" : "NON", (status & 0x40) ? "OUI" : "NON");
|
|
|
|
bool needPatch = !(status & 0x80);
|
|
bool needNvm = !(status & 0x40);
|
|
|
|
if (needPatch || needNvm) {
|
|
// Recherche de l'interface 0 (avec réessais si l'énumération est en cours)
|
|
for (int retry = 0; retry < 10 && !fInterface; retry++) {
|
|
OSIterator *iterator = fDevice->getChildIterator(gIOServicePlane);
|
|
if (iterator) {
|
|
OSObject *candidate;
|
|
while ((candidate = iterator->getNextObject())) {
|
|
IOUSBHostInterface *intf = OSDynamicCast(IOUSBHostInterface, candidate);
|
|
if (intf && intf->getInterfaceDescriptor() && intf->getInterfaceDescriptor()->bInterfaceNumber == 0) {
|
|
fInterface = intf;
|
|
fInterface->retain();
|
|
break;
|
|
}
|
|
}
|
|
OSSafeReleaseNULL(iterator);
|
|
}
|
|
if (!fInterface) {
|
|
IOSleep(30);
|
|
}
|
|
}
|
|
|
|
if (!fInterface) {
|
|
IOLog("QualcommWireless: Erreur - Interface 0 non trouvee.\n");
|
|
fDevice->close(this);
|
|
OSSafeReleaseNULL(fDevice);
|
|
return false;
|
|
}
|
|
|
|
if (!fInterface->open(this)) {
|
|
IOLog("QualcommWireless: Impossible d'ouvrir l'interface 0.\n");
|
|
OSSafeReleaseNULL(fInterface);
|
|
fDevice->close(this);
|
|
OSSafeReleaseNULL(fDevice);
|
|
return false;
|
|
}
|
|
|
|
// Recherche des pipes (Bulk OUT 0x02 et Interrupt IN 0x81)
|
|
const StandardUSB::ConfigurationDescriptor *configDescriptor = fInterface->getConfigurationDescriptor();
|
|
const StandardUSB::InterfaceDescriptor *interfaceDescriptor = fInterface->getInterfaceDescriptor();
|
|
const EndpointDescriptor *endpointDescriptor = nullptr;
|
|
|
|
while ((endpointDescriptor = StandardUSB::getNextEndpointDescriptor(configDescriptor, interfaceDescriptor, endpointDescriptor))) {
|
|
uint8_t epDirection = StandardUSB::getEndpointDirection(endpointDescriptor);
|
|
uint8_t epType = StandardUSB::getEndpointType(endpointDescriptor);
|
|
uint8_t epAddress = StandardUSB::getEndpointAddress(endpointDescriptor);
|
|
|
|
if (epDirection == kEndpointDirectionOut && epType == kEndpointTypeBulk) {
|
|
fBulkOutPipe = fInterface->copyPipe(epAddress);
|
|
} else if (epDirection == kEndpointDirectionIn && epType == kEndpointTypeInterrupt) {
|
|
fIntrInPipe = fInterface->copyPipe(epAddress);
|
|
}
|
|
}
|
|
|
|
fBulkBuffer = fInterface->createIOBuffer(kIODirectionOut, QCA_DFU_PACKET_LEN);
|
|
|
|
if (fBulkOutPipe && fBulkBuffer) {
|
|
if (needPatch) {
|
|
IOLog("QualcommWireless: Televersement du RAMPATCH WCN7850 (%u octets)...\n", rampatch_usb_00190200_bin_len);
|
|
if (downloadFirmware(rampatch_usb_00190200_bin, rampatch_usb_00190200_bin_len, 40)) {
|
|
IOLog("QualcommWireless: RAMPATCH televerse avec succes !\n");
|
|
} else {
|
|
IOLog("QualcommWireless: Echec du televersement RAMPATCH !\n");
|
|
}
|
|
}
|
|
|
|
status = checkStatus();
|
|
if (needNvm) {
|
|
IOLog("QualcommWireless: Televersement du NVM WCN7850 (%u octets)...\n", nvm_usb_00190200_bin_len);
|
|
if (downloadFirmware(nvm_usb_00190200_bin, nvm_usb_00190200_bin_len, 4)) {
|
|
IOLog("QualcommWireless: NVM televerse avec succes !\n");
|
|
} else {
|
|
IOLog("QualcommWireless: Echec du televersement NVM !\n");
|
|
}
|
|
IOSleep(100);
|
|
}
|
|
|
|
sendHciReset();
|
|
|
|
// Délai de stabilisation post-reset (150 ms) pour permettre au processeur Bluetooth
|
|
// et aux tampons FIFO USB de re-calibrer leur état avant que bluetoothd n'initialise la pile.
|
|
IOSleep(150);
|
|
} else {
|
|
IOLog("QualcommWireless: Erreur initialisation pipes ou buffer.\n");
|
|
}
|
|
|
|
if (fIntrInPipe) {
|
|
fIntrInPipe->clearStall(false);
|
|
}
|
|
if (fBulkOutPipe) {
|
|
fBulkOutPipe->clearStall(false);
|
|
}
|
|
|
|
OSSafeReleaseNULL(fBulkBuffer);
|
|
OSSafeReleaseNULL(fBulkOutPipe);
|
|
OSSafeReleaseNULL(fIntrInPipe);
|
|
|
|
if (fInterface->isOpen(this)) {
|
|
fInterface->close(this);
|
|
}
|
|
OSSafeReleaseNULL(fInterface);
|
|
}
|
|
|
|
uint8_t finalStatus = checkStatus();
|
|
IOLog("QualcommWireless: Statut FINAL: 0x%02x (Patch: %s, Syscfg: %s) -> Pret pour macOS !\n",
|
|
finalStatus, (finalStatus & 0x80) ? "OUI" : "NON", (finalStatus & 0x40) ? "OUI" : "NON");
|
|
|
|
// 1. Propriétés sur le périphérique USB pour bluetoothd (macOS Monterey / Ventura / Sonoma / Sequoia)
|
|
// IMPORTANT: Ne JAMAIS nommer "Bluetooth USB Host Controller" sinon Found_USB_Dongle ignore le périphérique.
|
|
fDevice->setProperty("USB Product Name", "Qualcomm WCN7850 Bluetooth");
|
|
fDevice->setProperty("USB Vendor Name", "Qualcomm");
|
|
fDevice->setProperty("FirmwareLoaded", true);
|
|
fDevice->setProperty("kUSBHostPortType", (uint32_t)255, 32);
|
|
fDevice->setProperty("builtin", (uint32_t)1, 32);
|
|
|
|
// Spoof idVendor à 0x0a12 (CSR) dans IORegistry pour que bluetoothd le valide dans sa liste
|
|
// blanche de dongles tiers et active le chipset générique THIRD_PARTY_DONGLE (HCI Transport: USB).
|
|
fDevice->setProperty("idVendor", (uint32_t)0x0a12, 32);
|
|
fDevice->setName("Qualcomm WCN7850 Bluetooth");
|
|
|
|
// 2. Propriétés sur le port parent
|
|
IOService *hubPort = fDevice->getProvider();
|
|
if (hubPort) {
|
|
hubPort->setProperty("USBPortType", (uint32_t)255, 32);
|
|
hubPort->setProperty("port-type", (uint32_t)255, 32);
|
|
}
|
|
|
|
// 3. Injection sur toutes les interfaces enfants (HCI & SCO)
|
|
OSIterator *intfIter = fDevice->getChildIterator(gIOServicePlane);
|
|
if (intfIter) {
|
|
OSObject *candidate;
|
|
while ((candidate = intfIter->getNextObject())) {
|
|
IOUSBHostInterface *intf = OSDynamicCast(IOUSBHostInterface, candidate);
|
|
if (intf) {
|
|
intf->setProperty("USB Product Name", "Qualcomm WCN7850 Bluetooth");
|
|
intf->setProperty("USB Vendor Name", "Qualcomm");
|
|
intf->setProperty("idVendor", (uint32_t)0x0a12, 32);
|
|
}
|
|
}
|
|
OSSafeReleaseNULL(intfIter);
|
|
}
|
|
|
|
// Délai de 100ms avant de relâcher l'accès exclusif pour garantir que bluetoothd
|
|
// trouve le contrôleur dans un état parfaitement calme et stabilisé au démarrage.
|
|
IOSleep(100);
|
|
|
|
// Libération de l'accès exclusif afin que bluetoothd puisse ouvrir le contrôleur
|
|
fDevice->close(this);
|
|
|
|
registerService();
|
|
return true;
|
|
}
|
|
|
|
void QualcommWireless::stop(IOService *provider) {
|
|
IOLog("QualcommWireless: Arret du service.\n");
|
|
|
|
if (fInterface) {
|
|
if (fInterface->isOpen(this)) {
|
|
fInterface->close(this);
|
|
}
|
|
OSSafeReleaseNULL(fInterface);
|
|
}
|
|
|
|
if (fDevice) {
|
|
if (fDevice->isOpen(this)) {
|
|
fDevice->close(this);
|
|
}
|
|
OSSafeReleaseNULL(fDevice);
|
|
}
|
|
|
|
super::stop(provider);
|
|
}
|