/* * Copyright (C) 2009-2012 by Matthias Ringwald * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * 4. Any redistribution, use, or modification is done solely for * personal benefit and not for any commercial purpose or for * monetary gain. * * THIS SOFTWARE IS PROVIDED BY MATTHIAS RINGWALD AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Please inquire about commercial licensing options at btstack@ringwald.ch * */ /* * bt_control_iphone.c * * control Bluetooth module using BlueTool * * Created by Matthias Ringwald on 5/19/09. * PowerManagement implementation by Jens David, DG1KJD on 20110801. * * Bluetooth Toggle by BigBoss */ #include "btstack-config.h" #include "bt_control_iphone.h" #include "hci_transport.h" #include "hci.h" #include "debug.h" #include #include // open #include // system, random, srandom #include // sscanf, printf #include // strcpy, strcat, strncmp #include // uname #include #include #include #include "../SpringBoardAccess/SpringBoardAccess.h" // minimal IOKit #include #include #include // compile issue fix #undef NSEC_PER_USEC #undef USEC_PER_SEC #undef NSEC_PER_SEC // end of fix #include #define IOKIT #include // costants #define sys_iokit err_system(0x38) #define sub_iokit_common err_sub(0) #define iokit_common_msg(message) (UInt32)(sys_iokit|sub_iokit_common|message) #define kIOMessageCanDevicePowerOff iokit_common_msg(0x200) #define kIOMessageDeviceWillPowerOff iokit_common_msg(0x210) #define kIOMessageDeviceWillNotPowerOff iokit_common_msg(0x220) #define kIOMessageDeviceHasPoweredOn iokit_common_msg(0x230) #define kIOMessageCanSystemPowerOff iokit_common_msg(0x240) #define kIOMessageSystemWillPowerOff iokit_common_msg(0x250) #define kIOMessageSystemWillNotPowerOff iokit_common_msg(0x260) #define kIOMessageCanSystemSleep iokit_common_msg(0x270) #define kIOMessageSystemWillSleep iokit_common_msg(0x280) #define kIOMessageSystemWillNotSleep iokit_common_msg(0x290) #define kIOMessageSystemHasPoweredOn iokit_common_msg(0x300) #define kIOMessageSystemWillRestart iokit_common_msg(0x310) #define kIOMessageSystemWillPowerOn iokit_common_msg(0x320) // types typedef io_object_t io_connect_t; typedef io_object_t io_service_t; typedef kern_return_t IOReturn; typedef struct IONotificationPort * IONotificationPortRef; // prototypes kern_return_t IOMasterPort( mach_port_t bootstrapPort, mach_port_t * masterPort ); CFMutableDictionaryRef IOServiceNameMatching(const char * name ); CFTypeRef IORegistryEntrySearchCFProperty(mach_port_t entry, const io_name_t plane, CFStringRef key, CFAllocatorRef allocator, UInt32 options ); mach_port_t IOServiceGetMatchingService(mach_port_t masterPort, CFDictionaryRef matching ); kern_return_t IOObjectRelease(mach_port_t object); typedef void (*IOServiceInterestCallback)(void * refcon, io_service_t service, uint32_t messageType, void * messageArgument); io_connect_t IORegisterForSystemPower (void * refcon, IONotificationPortRef * thePortRef, IOServiceInterestCallback callback, io_object_t * notifier ); IOReturn IODeregisterForSystemPower (io_object_t *notifier); CFRunLoopSourceRef IONotificationPortGetRunLoopSource(IONotificationPortRef notify ); IOReturn IOAllowPowerChange ( io_connect_t kernelPort, long notificationID ); IOReturn IOCancelPowerChange ( io_connect_t kernelPort, long notificationID ); // local globals static io_connect_t root_port = 0; // a reference to the Root Power Domain IOService static int power_notification_pipe_fds[2]; static data_source_t power_notification_ds; static void (*power_notification_callback)(POWER_NOTIFICATION_t event) = NULL; #define BUFF_LEN 80 static char buffer[BUFF_LEN+1]; // local mac address and default transport speed from IORegistry static bd_addr_t local_mac_address; static uint32_t transport_speed; static int power_management_active = 0; static char *os3xBlueTool = "BlueTool"; static char *os4xBlueTool = "/usr/local/bin/BlueToolH4"; static NSTimeInterval lastPowerOffTime = 0.0; static NSTimeInterval getTime(void){ NSDate * now = [[NSDate alloc] init]; double result = [now timeIntervalSince1970]; [now release]; return result; } /** * check OS version for >= 4.0 */ static int iphone_os_at_least_40(){ return kCFCoreFoundationVersionNumber >= 550.32; } /** * check OS version for >= 6.0 */ static int iphone_os_at_least_60(){ return kCFCoreFoundationVersionNumber >= 788.0; } /** * get machine name */ static struct utsname unmae_info; static char *get_machine_name(void){ uname(&unmae_info); return unmae_info.machine; } /** * on iPhone/iPod touch */ static int iphone_valid(void *config){ char * machine = get_machine_name(); if (!strncmp("iPod1", machine, strlen("iPod1"))) return 0; // 1st gen touch no BT return 1; } static const char * iphone_name(void *config){ return get_machine_name(); } // Get BD_ADDR from IORegistry static void ioregistry_get_info() { mach_port_t mp; IOMasterPort(MACH_PORT_NULL,&mp); CFMutableDictionaryRef bt_matching = IOServiceNameMatching("bluetooth"); mach_port_t bt_service = IOServiceGetMatchingService(mp, bt_matching); // local-mac-address CFTypeRef local_mac_address_ref = IORegistryEntrySearchCFProperty(bt_service,"IODeviceTree",CFSTR("local-mac-address"), kCFAllocatorDefault, 1); CFDataGetBytes(local_mac_address_ref,CFRangeMake(0,CFDataGetLength(local_mac_address_ref)),local_mac_address); // buffer needs to be unsigned char // transport-speed CFTypeRef transport_speed_ref = IORegistryEntrySearchCFProperty(bt_service,"IODeviceTree",CFSTR("transport-speed"), kCFAllocatorDefault, 1); int transport_speed_len = CFDataGetLength(transport_speed_ref); CFDataGetBytes(transport_speed_ref,CFRangeMake(0,transport_speed_len), (uint8_t*) &transport_speed); // buffer needs to be unsigned char IOObjectRelease(bt_service); // dump info log_info("local-mac-address: %s\n", bd_addr_to_str(local_mac_address)); log_info("transport-speed: %u\n", transport_speed); } static int iphone_has_csr(){ // construct script path from device name char *machine = get_machine_name(); if (strncmp(machine, "iPhone1,", strlen("iPhone1,")) == 0) { return 1; } return 0; } static void iphone_write_string(int fd, char *string){ int len = strlen(string); write(fd, string, len); } static void iphone_csr_set_pskey(int fd, int key, int value){ int len = sprintf(buffer, "csr -p 0x%04x=0x%04x\n", key, value); write(fd, buffer, len); } static void iphone_csr_set_bd_addr(int fd){ int len = sprintf(buffer,"\ncsr -p 0x0001=0x00%.2x,0x%.2x%.2x,0x00%.2x,0x%.2x%.2x\n", local_mac_address[3], local_mac_address[4], local_mac_address[5], local_mac_address[2], local_mac_address[0], local_mac_address[1]); write(fd, buffer, len); } static void iphone_csr_set_baud(int fd, int baud){ // calculate baud rate (assume rate is multiply of 100) uint32_t baud_key = (4096 * (baud/100) + 4999) / 10000; iphone_csr_set_pskey(fd, 0x01be, baud_key); } static void iphone_bcm_set_bd_addr(int fd){ int len = sprintf(buffer, "bcm -a %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", local_mac_address[0], local_mac_address[1], local_mac_address[2], local_mac_address[3], local_mac_address[4], local_mac_address[5]); write(fd, buffer, len); } static void iphone_bcm_set_baud(int fd, int baud){ int len = sprintf(buffer, "bcm -b %u\n", baud); write(fd, buffer, len); } // OS 3.x static int iphone_write_initscript (int output, int baudrate){ // construct script path from device name strcpy(buffer, "/etc/bluetool/"); char *machine = get_machine_name(); strcat(buffer, machine); if (iphone_has_csr()){ strcat(buffer, ".init.script"); } else { strcat(buffer, ".boot.script"); } // open script int input = open(buffer, O_RDONLY); int pos = 0; int mirror = 0; int store = 1; while (1){ int chars = read(input, &buffer[pos], 1); // end-of-line if (chars == 0 || buffer[pos]=='\n' || buffer[pos]== '\r'){ if (store) { // stored characters write(output, buffer, pos+chars); } if (mirror) { write(output, "\n", 1); } pos = 0; mirror = 0; store = 1; if (chars) { continue; } else { break; } } // mirror if (mirror){ write(output, &buffer[pos], 1); } // store if (store) { pos++; } // iPhone - set BD_ADDR after "csr -i" if (store == 1 && pos == 6 && strncmp(buffer, "csr -i", 6) == 0) { store = 0; write(output, buffer, pos); // "csr -i" iphone_csr_set_bd_addr(output); } // iPod2,1 // check for "bcm -X" cmds if (store == 1 && pos == 6){ if (strncmp(buffer, "bcm -", 5) == 0) { store = 0; switch (buffer[5]){ case 'a': // BT Address iphone_bcm_set_bd_addr(output); mirror = 0; break; case 'b': // baud rate command OS 2.x case 'B': // baud rate command OS 3.x iphone_bcm_set_baud(output, baudrate); mirror = 0; break; case 's': // sleep mode - replace with "wake" command? iphone_write_string(output, "wake on\n"); mirror = 0; break; default: // other "bcm -X" command write(output, buffer, pos); mirror = 1; } } } // iPhone1,1 & iPhone 2,1: OS 3.x // check for "csr -B" and "csr -T" if (store == 1 && pos == 6){ if (strncmp(buffer, "csr -", 5) == 0) { switch(buffer[5]){ case 'T': // Transport Mode store = 0; break; case 'B': // Baud rate iphone_csr_set_baud(output, baudrate); store = 0; break; default: // wait for full command break; } } } // iPhone1,1 & iPhone 2,1: OS 2.x // check for "csr -p 0x1234=0x5678" (20) if (store == 1 && pos == 20) { int pskey, value; store = 0; if (sscanf(buffer, "csr -p 0x%x=0x%x", &pskey, &value) == 2){ switch (pskey) { case 0x01f9: // UART MODE case 0x01c1: // Configure H5 mode mirror = 0; break; case 0x01be: // PSKEY_UART_BAUD_RATE iphone_csr_set_baud(output, baudrate); mirror = 0; break; default: // anything else: dump buffer and start forwarding write(output, buffer, pos); mirror = 1; break; } } else { write(output, buffer, pos); mirror = 1; } } } // close input close(input); #ifdef USE_POWERMANAGEMENT if (iphone_has_csr()) { /* CSR BT module: deactivated since it didn't work on iPhone 3G, 3.1.3 the first few packets didn't get received when iPhone is sleeping. That's quite possible since Apple uses H5, but BTstack uses H4 H5's packet retransmission should take care of that */ // iphone_write_string(output, "msleep 50\n"); // iphone_write_string(output, "csr -p 0x01ca=0x0031\n"); // iphone_write_string(output, "msleep 50\n"); // iphone_write_string(output, "csr -p 0x01c7=0x0001,0x01f4,0x0005,0x0020\n"); } else { /* BCM BT module, deactivated since untested for now */ // iphone_write_string(output, "bcm -s 0x01,0x00,0x00,0x01,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x01\n"); // iphone_write_string(output, "msleep 50\n"); } #endif return 0; } // OS 4.x and higher static void iphone_write_configscript(int fd, int baudrate){ // check for os version >= 6.0 int os6x = iphone_os_at_least_60(); iphone_write_string(fd, "device -D -S\n"); if (os6x){ iphone_write_string(fd, "wake on\n"); } if (iphone_has_csr()) { iphone_csr_set_bd_addr(fd); if (baudrate) { iphone_csr_set_baud(fd, baudrate); } iphone_write_string(fd, "csr -r\n"); #ifdef USE_POWERMANAGEMENT /* CSR BT module: deactivated since untested, but it most likely won't work see comments in 3.x init sequence above */ // iphone_write_string(fd, "msleep 50\n"); // iphone_write_string(fd, "csr -p 0x01ca=0x0031\n"); // iphone_write_string(fd, "msleep 50\n"); // iphone_write_string(fd, "csr -p 0x01c7=0x0001,0x01f4,0x0005,0x0020\n"); #endif } else { iphone_bcm_set_bd_addr(fd); if (baudrate) { iphone_bcm_set_baud(fd, baudrate); iphone_write_string(fd, "msleep 200\n"); } #ifdef USE_POWERMANAGEMENT // power management only active on 4.x with BCM (iPhone 3GS and higher, all iPads, iPod touch 3G and higher) iphone_write_string(fd, "bcm -s 0x01,0x00,0x00,0x01,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x01\n"); iphone_write_string(fd, "msleep 50\n"); #endif } if (os6x){ iphone_write_string(fd, "wake off\n"); } iphone_write_string(fd, "quit\n"); } static int iphone_on (void *transport_config){ // hci_uart_config->baudrate_init == 0, if using native speed log_info("iphone_on: entered\n"); int err = 0; hci_uart_config_t * hci_uart_config = (hci_uart_config_t*) transport_config; // get local-mac-addr and transport-speed from IORegistry ioregistry_get_info(); #ifdef USE_RANDOM_BD_ADDR // While developing an app that emulates a Bluetooth HID keyboard, we've learnt // that in (some versions/driver combinations of) Windows XP, information about // a device with incorrect SDP descriptions are stored forever. // // To continue development, this option was added that picks a random BD_ADDR // on start to trick windows in giving us a fresh start on each try. // // Use with caution! srandom(time(NULL)); bt_store_32(local_mac_address, 0, random()); bt_store_16(local_mac_address, 4, random()); #endif if (iphone_system_bt_enabled()){ perror("iphone_on: System Bluetooth enabled!"); return 1; } // assert min 10 seconds time between power off and power on if (iphone_os_at_least_60()) { NSTimeInterval timeSinceLastPowerOff = getTime() - lastPowerOffTime; if (timeSinceLastPowerOff < 10.0 ){ int delay = 10 - timeSinceLastPowerOff; log_info("iphone_on: delay init for %u seconds", delay); sleep(delay); } } // unload BTServer and BlueTool log_info("iphone_on: unload BTServer\n"); err = system ("launchctl unload /System/Library/LaunchDaemons/com.apple.BTServer.plist"); if (iphone_os_at_least_60()) { err = system ("launchctl unload /System/Library/LaunchDaemons/com.apple.BTServer.le.plist"); err = system ("launchctl unload /System/Library/LaunchDaemons/com.apple.BlueTool.plist"); } // check for os version >= 4.0 int os4x = iphone_os_at_least_40(); // Use patched BlueTool on iOS 4.0 with CSR chipset char * bluetool = os3xBlueTool; if (iphone_has_csr() && os4x) { bluetool = os4xBlueTool; } // quick test if Bluetooth UART can be opened - and try to start cleanly int fd = open(hci_uart_config->device_name, O_RDWR | O_NOCTTY | O_NDELAY); if (fd > 0) { // everything's fine close(fd); } else { // no way! log_error( "bt_control.c:iphone_on(): Failed to open '%s', trying killall %s\n", hci_uart_config->device_name, bluetool); system("killall -9 BlueToolH4"); system("killall -9 BlueTool"); sleep(3); // try again fd = open(hci_uart_config->device_name, O_RDWR | O_NOCTTY | O_NDELAY); if (fd > 0){ close(fd); } else { log_error( "bt_control.c:iphone_on(): Failed to open '%s' again, trying killall BTServer and killall %s\n", hci_uart_config->device_name, bluetool); system("killall -9 BTServer"); system("killall -9 BlueToolH4"); system("killall -9 BlueTool"); sleep(3); } } // basic config on 4.0+ if (os4x) { sprintf(buffer, "%s -F boot", bluetool); system(buffer); sprintf(buffer, "%s -F init", bluetool); system(buffer); } // advanced config - set bd addr, use custom baud rate, enable deep sleep FILE * outputFile = popen(bluetool, "r+"); setvbuf(outputFile, NULL, _IONBF, 0); int output = fileno(outputFile); if (os4x) { // 4.x - send custom config iphone_write_configscript(output, hci_uart_config->baudrate_init); } else { // 3.x - modify original script on the fly iphone_write_initscript(output, hci_uart_config->baudrate_init); } // log output fflush(outputFile); int singlechar = 0; char linebuf[80]; int pos = 0; while (singlechar != EOF) { singlechar = fgetc(outputFile); linebuf[pos] = singlechar; if (singlechar != EOF && singlechar != '\n' && singlechar != '\r' && pos < 78) { pos++; continue; }; linebuf[pos] = 0; if (pos > 0) { log_info("%s", linebuf); } pos = 0; }; err = pclose(outputFile); #ifdef USE_POWERMANAGEMENT power_management_active = bt_control_iphone_power_management_supported(); // if baud == 0, we're using system default: set in transport config if (hci_uart_config->baudrate_init == 0) { hci_uart_config->baudrate_init = transport_speed; } #endif // if we sleep for about 3 seconds, we miss a strage packet... but we don't care // sleep(3); return err; } static int iphone_off (void *config){ // power off (all models) log_info("iphone_off: turn off using BlueTool\n"); // power off (all models < IOS 6.0) if (iphone_os_at_least_60()) { log_info("Skipping BlueTool invocation to turn off module power since IOS >= 6.0"); } else { log_info("iphone_off: turn off using BlueTool\n"); system ("echo \"power off\nquit\" | BlueTool"); } // reload BlueTool if (iphone_os_at_least_60()) { log_info("iphone_off: reload BlueTool\n"); system ("launchctl load /System/Library/LaunchDaemons/com.apple.BlueTool.plist"); system ("launchctl load /System/Library/LaunchDaemons/com.apple.BTServer.le.plist"); log_info("iphone_off: done\n"); } // reload BTServer log_info("iphone_off: reload BTServer\n"); system ("launchctl load /System/Library/LaunchDaemons/com.apple.BTServer.plist"); log_info("iphone_off: done\n"); lastPowerOffTime = getTime(); return 0; } static int iphone_sleep(void *config){ // will sleep by itself if (power_management_active) return 0; // put Bluetooth into deep sleep system ("echo \"wake off\nquit\" | BlueTool"); return 0; } static int iphone_wake(void *config){ // will wake by itself if (power_management_active) return 0; // wake up Bluetooth module system ("echo \"wake on\nquit\" | BlueTool"); return 0; } static void MySleepCallBack( void * refCon, io_service_t service, natural_t messageType, void * messageArgument ) { char data; log_info( "messageType %08lx, arg %08lx\n", (long unsigned int)messageType, (long unsigned int)messageArgument); switch ( messageType ) { case kIOMessageCanSystemSleep: /* Idle sleep is about to kick in. This message will not be sent for forced sleep. Applications have a chance to prevent sleep by calling IOCancelPowerChange. Most applications should not prevent idle sleep. Power Management waits up to 30 seconds for you to either allow or deny idle sleep. If you don't acknowledge this power change by calling either IOAllowPowerChange or IOCancelPowerChange, the system will wait 30 seconds then go to sleep. */ // Uncomment to cancel idle sleep // IOCancelPowerChange( root_port, (long)messageArgument ); // we will allow idle sleep IOAllowPowerChange( root_port, (long)messageArgument ); break; case kIOMessageSystemWillSleep: /* The system WILL go to sleep. If you do not call IOAllowPowerChange or IOCancelPowerChange to acknowledge this message, sleep will be delayed by 30 seconds. NOTE: If you call IOCancelPowerChange to deny sleep it returns kIOReturnSuccess, however the system WILL still go to sleep. */ // notify main thread data = POWER_WILL_SLEEP; write(power_notification_pipe_fds[1], &data, 1); // don't allow power change, even when power management is active // BTstack needs to disable discovery mode during sleep to save power // if (!power_management_active) break; // IOAllowPowerChange( root_port, (long)messageArgument ); break; case kIOMessageSystemWillPowerOn: data = POWER_WILL_WAKE_UP; write(power_notification_pipe_fds[1], &data, 1); break; case kIOMessageSystemHasPoweredOn: //System has finished waking up... break; default: break; } } static int power_notification_process(struct data_source *ds) { if (!power_notification_callback) return -1; // get token char token; int bytes_read = read(power_notification_pipe_fds[0], &token, 1); if (bytes_read != 1) return -1; log_info("power_notification_process: %u\n", token); power_notification_callback( (POWER_NOTIFICATION_t) token ); return 0; } /** * assumption: called only once, cb != null */ void iphone_register_for_power_notifications(void (*cb)(POWER_NOTIFICATION_t event)){ // handle IOKIT power notifications - http://developer.apple.com/library/mac/#qa/qa2004/qa1340.html IONotificationPortRef notifyPortRef = NULL; // notification port allocated by IORegisterForSystemPower io_object_t notifierObject = 0; // notifier object, used to deregister later root_port = IORegisterForSystemPower(NULL, ¬ifyPortRef, MySleepCallBack, ¬ifierObject); if (!root_port) { log_info("IORegisterForSystemPower failed\n"); return; } // add the notification port to this thread's runloop CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes); // power_notification_callback = cb; // create pipe to deliver power notification to main run loop pipe(power_notification_pipe_fds); // set up data source handler power_notification_ds.fd = power_notification_pipe_fds[0]; power_notification_ds.process = power_notification_process; run_loop_add_data_source(&power_notification_ds); } int bt_control_iphone_power_management_supported(void){ // only supported on Broadcom chipsets with iOS 4.0+ if ( iphone_has_csr()) return 0; if (!iphone_os_at_least_40()) return 0; return 1; } // direct access int bt_control_iphone_power_management_enabled(void){ return power_management_active; } // single instance bt_control_t bt_control_iphone = { .on = iphone_on, .off = iphone_off, .sleep = iphone_sleep, .wake = iphone_wake, .valid = iphone_valid, .name = iphone_name, .register_for_power_notifications = iphone_register_for_power_notifications }; int iphone_system_bt_enabled(void){ return SBA_getBluetoothEnabled(); } void iphone_system_bt_set_enabled(int enabled) { SBA_setBluetoothEnabled(enabled); sleep(2); // give change a chance } int iphone_system_has_csr(void){ return iphone_has_csr(); }