Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/README-zh_Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- [English](/README.md)
- **简体中文**

> [!IMPORTANT]
> 本分支中的 **LE Data Length Change 修复**——用于清除 bluetoothd 在 macOS Tahoe 上卡在 `LE_READY_WAIT_FOR_LENGTH` 的问题,使 BLE HID 设备得以绑定([#514](https://github.com/OpenIntelWireless/IntelBluetoothFirmware/issues/514))——由 **AI 助手 Claude 编写**。该修复按原样提供,不含任何担保;您需自行负责审查、测试,并自担风险使用。

## 简介

IntelBluetoothFirmware 是一个用于在 macOS 中启用原生蓝牙的固件上传驱动,固件的二进制文件来自 Linux。
Expand Down
170 changes: 159 additions & 11 deletions IntelBTPatcher/IntelBTPatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// Created by zxystd <zxystd@foxmail.com> on 2021/2/8.
//

#define __ACIDANTHERA_MAC_SDK 2600
#include <Headers/kern_api.hpp>
#include <Headers/kern_util.hpp>
#include <Headers/plugin_start.hpp>
Expand Down Expand Up @@ -66,6 +66,7 @@ static KernelPatcher::KextInfo IntelBTPatcher_IOUsbHostInfo {
KernelPatcher::KextInfo::Unloaded
};

void *CIntelBTPatcher::_hookPipeInstance = nullptr;
bool CIntelBTPatcher::_randomAddressInit = false;

bool CIntelBTPatcher::init()
Expand Down Expand Up @@ -128,6 +129,46 @@ void CIntelBTPatcher::processKext(KernelPatcher &patcher, size_t index, mach_vm_
SYSLOG(DRV_NAME, "failed to resolve %s, error = %d", hostDeviceRequest.symbol, patcher.getError());
patcher.clearError();
}

KernelPatcher::RouteRequest asyncIORequest {
"__ZN13IOUSBHostPipe2ioEP18IOMemoryDescriptorjP19IOUSBHostCompletionj",
newAsyncIO,
oldAsyncIO
};
patcher.routeMultiple(index, &asyncIORequest, 1, address, size);
if (patcher.getError() == KernelPatcher::Error::NoError) {
SYSLOG(DRV_NAME, "routed %s", asyncIORequest.symbol);
} else {
SYSLOG(DRV_NAME, "failed to resolve %s, error = %d", asyncIORequest.symbol, patcher.getError());
patcher.clearError();
}

// macOS 26.5+: third parameter became ConfigurationDescriptor (was SuperSpeedEndpointCompanionDescriptor)
KernelPatcher::RouteRequest initPipeConfigRequest {
"__ZN13IOUSBHostPipe28initWithDescriptorsAndOwnersEPKN11StandardUSB18EndpointDescriptorEPKNS0_23ConfigurationDescriptorEP22AppleUSBHostControllerP15IOUSBHostDeviceP18IOUSBHostInterfaceht",
newInitPipeConfig,
oldInitPipeConfig
};
patcher.routeMultiple(index, &initPipeConfigRequest, 1, address, size);
if (patcher.getError() == KernelPatcher::Error::NoError) {
SYSLOG(DRV_NAME, "routed %s", initPipeConfigRequest.symbol);
} else {
SYSLOG(DRV_NAME, "failed to resolve %s, error = %d", initPipeConfigRequest.symbol, patcher.getError());
patcher.clearError();
}

KernelPatcher::RouteRequest initPipeRequest {
"__ZN13IOUSBHostPipe28initWithDescriptorsAndOwnersEPKN11StandardUSB18EndpointDescriptorEPKNS0_37SuperSpeedEndpointCompanionDescriptorEP22AppleUSBHostControllerP15IOUSBHostDeviceP18IOUSBHostInterfaceht",
newInitPipe,
oldInitPipe
};
patcher.routeMultiple(index, &initPipeRequest, 1, address, size);
if (patcher.getError() == KernelPatcher::Error::NoError) {
SYSLOG(DRV_NAME, "routed %s", initPipeRequest.symbol);
} else {
SYSLOG(DRV_NAME, "failed to resolve %s, error = %d", initPipeRequest.symbol, patcher.getError());
patcher.clearError();
}
}
}
}
Expand All @@ -153,11 +194,13 @@ StandardUSB::DeviceRequest randomAddressRequest;
const uint8_t randomAddressHci[9] = {0x05, 0x20, 0x06, 0x94, 0x50, 0x64, 0xD0, 0x78, 0x6B};
IOBufferMemoryDescriptor *writeHCIDescriptor = nullptr;

#define MAX_HCI_BUF_LEN 255
#define HCI_OP_RESET 0x0c03
#define HCI_OP_LE_SET_SCAN_PARAM 0x200B
#define HCI_OP_LE_SET_SCAN_ENABLE 0x200C
#define MAX_HCI_BUF_LEN 255
#define HCI_OP_RESET 0x0c03
#define HCI_OP_LE_SET_SCAN_PARAM 0x200B
#define HCI_OP_LE_SET_SCAN_ENABLE 0x200C
#define HCI_OP_LE_READ_REMOTE_FEATURES 0x2016

int remoteReadSCount = 0;
IOReturn CIntelBTPatcher::newHostDeviceRequest(void *that, IOService *provider, StandardUSB::DeviceRequest &request, void *data, IOMemoryDescriptor *descriptor, unsigned int &length, IOUSBHostCompletion *completion, unsigned int timeout)
{
HciCommandHdr *hdr = nullptr;
Expand All @@ -175,7 +218,7 @@ IOReturn CIntelBTPatcher::newHostDeviceRequest(void *that, IOService *provider,
descriptor->complete(kIODirectionOut);
}
hdr = (HciCommandHdr *)hciBuf;
if (hdr->opcode == HCI_OP_LE_SET_SCAN_PARAM) {
if (hdr->opcode == HCI_OP_LE_SET_SCAN_PARAM && hdr->data[5] == 0x1) {
if (!_randomAddressInit) {
randomAddressRequest.bmRequestType = makeDeviceRequestbmRequestType(kRequestDirectionOut, kRequestTypeClass, kRequestRecipientInterface);
randomAddressRequest.bRequest = 0xE0;
Expand All @@ -184,18 +227,24 @@ IOReturn CIntelBTPatcher::newHostDeviceRequest(void *that, IOService *provider,
randomAddressRequest.wLength = 9;
length = 9;
if (writeHCIDescriptor == nullptr)
writeHCIDescriptor = IOBufferMemoryDescriptor::withBytes(randomAddressHci, 9, kIODirectionOut);
writeHCIDescriptor->prepare(kIODirectionOut);
writeHCIDescriptor = IOBufferMemoryDescriptor::withBytes(randomAddressHci, 9, kIODirectionInOut);
writeHCIDescriptor->prepare(kIODirectionInOut);
IOReturn ret = FunctionCast(newHostDeviceRequest, callbackIBTPatcher->oldHostDeviceRequest)(that, provider, randomAddressRequest, nullptr, writeHCIDescriptor, length, nullptr, timeout);
writeHCIDescriptor->complete();
writeHCIDescriptor->complete(kIODirectionInOut);
const char *randAddressDump = _hexDumpHCIData((uint8_t *)randomAddressHci, 9);
if (randAddressDump) {
SYSLOG(DRV_NAME, "[PATCH] Sending Random Address HCI %lld %s", ret, randAddressDump);
SYSLOG(DRV_NAME, "[PATCH] Sending Random Address HCI %d %s", ret, randAddressDump);
IOFree((void *)randAddressDump, 9 * 3 + 1);
}
_randomAddressInit = true;
SYSLOG(DRV_NAME, "[PATCH] Resend LE SCAN PARAM HCI %lld", ret);
SYSLOG(DRV_NAME, "[PATCH] Resend LE SCAN PARAM HCI %d", ret);
}
} else if (hdr->opcode == HCI_OP_LE_READ_REMOTE_FEATURES) {
// #514: send TWO extra reads -> two duplicate 0x04 events to rewrite
// (1st dup -> fake PHY Update Complete, 2nd dup -> fake LE Data Length Change).
remoteReadSCount += 2;
FunctionCast(newHostDeviceRequest, callbackIBTPatcher->oldHostDeviceRequest)(that, provider, request, nullptr, descriptor, length, nullptr, timeout);
FunctionCast(newHostDeviceRequest, callbackIBTPatcher->oldHostDeviceRequest)(that, provider, request, nullptr, descriptor, length, nullptr, timeout);
}
} else {
hdr = (HciCommandHdr *)data;
Expand All @@ -218,3 +267,102 @@ IOReturn CIntelBTPatcher::newHostDeviceRequest(void *that, IOService *provider,
}
return FunctionCast(newHostDeviceRequest, callbackIBTPatcher->oldHostDeviceRequest)(that, provider, request, data, descriptor, length, completion, timeout);
}

#define HCI_EVT_LE_META 0x3E
#define HCI_EVT_LE_META_READ_REMOTE_FEATURES_COMPLETE 0x04

uint8_t fakePhyUpdateCompleteEvent[8] = {0x3E, 0x06, 0x0C, 0x00, 0x00, 0x00, 0x02, 0x02};
// #514: LE Data Length Change (LE Meta subevent 0x07, no status byte -> handle at [3][4]).
// bluetoothd stalls at LE_READY_WAIT_FOR_LENGTH after PHY until it sees this. 27 octets / 328us tx+rx.
// Made by Claude.
uint8_t fakeDataLengthChangeEvent[13] = {0x3E, 0x0B, 0x07, 0x00, 0x00, 0x1B, 0x00, 0x48, 0x01, 0x1B, 0x00, 0x48, 0x01};
//uint8_t remoteFeaturesStatus[6] = {0x0F, 0x04, 0x00, 0x01, 0x16, 0x20};

static void asyncIOCompletion(void* owner, void* parameter, IOReturn status, uint32_t bytesTransferred)
{
AsyncOwnerData *asyncOwner = (AsyncOwnerData *)owner;
IOMemoryDescriptor* dataBuffer = asyncOwner->dataBuffer;
// #514 3-stage rewrite of the repeated LE Read Remote Features Complete (0x04) events:
// stage 0 = the real one (pass through), 1 = rewrite to fake PHY Update Complete,
// 2 = rewrite to fake LE Data Length Change (clears bluetoothd's LE_READY_WAIT_FOR_LENGTH).
static int leMetaStage = 0;
uint32_t allocLen = bytesTransferred;

if (dataBuffer && bytesTransferred) {
void *buffer = IOMalloc(allocLen);
if (buffer) {
if (dataBuffer->getLength() > 0 && (getKernelVersion() < KernelVersion::Sequoia || !dataBuffer->prepare(kIODirectionInOut))) {
dataBuffer->readBytes(0, buffer, allocLen);
HciEventHdr *hdr = (HciEventHdr *)buffer;
if (hdr->evt == HCI_EVT_LE_META && hdr->data[0] == HCI_EVT_LE_META_READ_REMOTE_FEATURES_COMPLETE) {
if (leMetaStage == 0) {
leMetaStage = 1; // real features complete -> pass through
} else if (leMetaStage == 1) { // 1st duplicate -> fake PHY Update Complete
fakePhyUpdateCompleteEvent[4] = hdr->data[2];
fakePhyUpdateCompleteEvent[5] = hdr->data[3];
dataBuffer->writeBytes(0, fakePhyUpdateCompleteEvent, 8);
bytesTransferred = 8;
leMetaStage = 2;
remoteReadSCount--;
} else { // 2nd duplicate -> fake LE Data Length Change
fakeDataLengthChangeEvent[3] = hdr->data[2];
fakeDataLengthChangeEvent[4] = hdr->data[3];
dataBuffer->writeBytes(0, fakeDataLengthChangeEvent, 13);
bytesTransferred = 13;
leMetaStage = 0;
remoteReadSCount--;
}
}
if (getKernelVersion() >= KernelVersion::Sequoia) dataBuffer->complete(kIODirectionInOut);
}
IOFree(buffer, allocLen);
}
}
if (asyncOwner->action)
asyncOwner->action(asyncOwner->owner, parameter, status, bytesTransferred);
delete asyncOwner;
}

IOReturn CIntelBTPatcher::
newAsyncIO(void *that, IOMemoryDescriptor* dataBuffer, uint32_t bytesTransferred, IOUSBHostCompletion* completion, uint32_t completionTimeoutMs)
{
if (remoteReadSCount > 0 && that == _hookPipeInstance && completion) {
AsyncOwnerData *ownerData = new AsyncOwnerData;
ownerData->action = completion->action;
ownerData->owner = completion->owner;
ownerData->dataBuffer = dataBuffer;
completion->action = asyncIOCompletion;
completion->owner = ownerData;
}
return FunctionCast(newAsyncIO, callbackIBTPatcher->oldAsyncIO)(that, dataBuffer, bytesTransferred, completion, completionTimeoutMs);
}

#define VENDOR_USB_INTEL 0x8087

void CIntelBTPatcher::captureIntelInterruptPipe(void *that, StandardUSB::EndpointDescriptor const *descriptor, IOUSBHostDevice *device)
{
if (!device)
return;
const StandardUSB::DeviceDescriptor *deviceDescriptor = device->getDeviceDescriptor();
if (deviceDescriptor && deviceDescriptor->idVendor == VENDOR_USB_INTEL) {
uint8_t epType = StandardUSB::getEndpointType(descriptor);
if (epType == kIOUSBEndpointTypeInterrupt)
CIntelBTPatcher::_hookPipeInstance = that;
}
}

int CIntelBTPatcher::
newInitPipe(void *that, StandardUSB::EndpointDescriptor const *descriptor, StandardUSB::SuperSpeedEndpointCompanionDescriptor const *superDescriptor, AppleUSBHostController *controller, IOUSBHostDevice *device, IOUSBHostInterface *interface, unsigned char a7, unsigned short a8)
{
int ret = FunctionCast(newInitPipe, callbackIBTPatcher->oldInitPipe)(that, descriptor, superDescriptor, controller, device, interface, a7, a8);
captureIntelInterruptPipe(that, descriptor, device);
return ret;
}

int CIntelBTPatcher::
newInitPipeConfig(void *that, StandardUSB::EndpointDescriptor const *descriptor, StandardUSB::ConfigurationDescriptor const *configDescriptor, AppleUSBHostController *controller, IOUSBHostDevice *device, IOUSBHostInterface *interface, unsigned char a7, unsigned short a8)
{
int ret = FunctionCast(newInitPipeConfig, callbackIBTPatcher->oldInitPipeConfig)(that, descriptor, configDescriptor, controller, device, interface, a7, a8);
captureIntelInterruptPipe(that, descriptor, device);
return ret;
}
18 changes: 10 additions & 8 deletions IntelBTPatcher/IntelBTPatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,8 @@ typedef struct __attribute__((packed))
{
uint8_t evt;
uint8_t len;
} HciEventHdr;

typedef struct __attribute__((packed))
{
HciEventHdr evt;
uint8_t numCommands;
uint16_t opcode;
uint8_t data[];
} HciResponse;
} HciEventHdr;

const char *requestDirectionNames[] = {
"OUT",
Expand Down Expand Up @@ -82,12 +75,21 @@ class CIntelBTPatcher {
static IOReturn newFindQueueRequest(void *that, unsigned short arg1, void *addr, unsigned short arg2, bool arg3, void **hciRequestPtr);

static IOReturn newHostDeviceRequest(void *that, IOService *provider, StandardUSB::DeviceRequest &request, void *data, IOMemoryDescriptor *descriptor, unsigned int &length,IOUSBHostCompletion *completion, unsigned int timeout);
static IOReturn newAsyncIO(void *that, IOMemoryDescriptor* dataBuffer, uint32_t dataBufferLength, IOUSBHostCompletion* completion, uint32_t completionTimeoutMs);
static int newInitPipe(void *that, StandardUSB::EndpointDescriptor const *descriptor, StandardUSB::SuperSpeedEndpointCompanionDescriptor const *superDescriptor,AppleUSBHostController *controller, IOUSBHostDevice *device, IOUSBHostInterface *interface, unsigned char, unsigned short);
static int newInitPipeConfig(void *that, StandardUSB::EndpointDescriptor const *descriptor, StandardUSB::ConfigurationDescriptor const *configDescriptor, AppleUSBHostController *controller, IOUSBHostDevice *device, IOUSBHostInterface *interface, unsigned char, unsigned short);


mach_vm_address_t oldFindQueueRequest {};
mach_vm_address_t oldHostDeviceRequest {};
mach_vm_address_t oldAsyncIO {};
mach_vm_address_t oldInitPipe {};
mach_vm_address_t oldInitPipeConfig {};

private:
static void captureIntelInterruptPipe(void *that, StandardUSB::EndpointDescriptor const *descriptor, IOUSBHostDevice *device);
static void *_hookPipeInstance;
static AsyncOwnerData *_interruptPipeAsyncOwner;
static bool _randomAddressInit;
};

Expand Down
4 changes: 2 additions & 2 deletions IntelBluetoothFirmware.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MODULE_VERSION = 2.5.0;
MODULE_VERSION = 2.5.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
Expand Down Expand Up @@ -725,7 +725,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MODULE_VERSION = 2.5.0;
MODULE_VERSION = 2.5.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
Expand Down
2 changes: 1 addition & 1 deletion IntelBluetoothFirmware/IntelBluetoothOpsGen1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ patching(OSData *fwData, const uint8_t **fw_ptr, bool *disablePatch)
* the firmware file is corrupted and it should stop the patching
* process.
*/
if (remain > HCI_COMMAND_HDR_SIZE && *fw_ptr[0] != 0x01) {
if (remain < HCI_COMMAND_HDR_SIZE || *fw_ptr[0] != 0x01) {
XYLog("Intel fw corrupted: invalid cmd read\n");
return false;
}
Expand Down
Binary file modified IntelBluetoothFirmware/fw/ibt-0040-0041.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0040-0041.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0040-1020.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0041-0041.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0180-0041.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0180-1050.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0180-4150.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0291-0291.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-0291-0291.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-1040-0041.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-1040-1020.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-11-5.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-12-16.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-17-16-1.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-17-16-1.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-18-16-1.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-18-16-1.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-19-0-4.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-19-0-4.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-20-0-3.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-20-0-3.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-20-1-3.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-20-1-3.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-20-1-4.ddc
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-20-1-4.sfi
Binary file not shown.
Binary file modified IntelBluetoothFirmware/fw/ibt-hw-37.8.10-fw-22.50.19.14.f.bseq
Binary file not shown.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- **English**
- [简体中文](/.github/README-zh_Hans.md)

> [!IMPORTANT]
> The **LE Data Length Change fix** in this fork — which clears bluetoothd's `LE_READY_WAIT_FOR_LENGTH` stall so BLE HID devices bind on macOS Tahoe ([#514](https://github.com/OpenIntelWireless/IntelBluetoothFirmware/issues/514)) — was **written by Claude, an AI assistant**. It is provided as-is, with no warranty: you are responsible for reviewing, testing, and using it at your own risk.

## Intro

IntelBluetoothFirmware is a Kernel Extension that uploads Intel Wireless Bluetooth Firmware to provide native Bluetooth in macOS.
Expand Down