/* * Copyright (C) 2009 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. * * 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. * */ /* * bt_control_iphone.c * * control Bluetooth module using BlueTool * * Created by Matthias Ringwald on 5/19/09. * * Bluetooth Toggle by BigBoss */ #include "../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 #ifdef USE_BLUETOOL #include "../SpringBoardAccess/SpringBoardAccess.h" // minimal IOKit #ifdef __APPLE__ #include #include #include #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0 // compile issue fix #undef NSEC_PER_USEC #undef USEC_PER_SEC #undef NSEC_PER_SEC // end of fix #include #define IOKIT #include #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; #endif #endif int iphone_system_bt_enabled(){ return SBA_getBluetoothEnabled(); } void iphone_system_bt_set_enabled(int enabled) { SBA_setBluetoothEnabled(enabled); sleep(2); // give change a chance } #endif 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; /** * 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() { #ifdef IOKIT 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_dbg("local-mac-address: "); print_bd_addr(local_mac_address); log_dbg("\ntransport-speed: %u\n", transport_speed); #else // use dummy addr if not on iphone/ipod touch int i = 0; for (i=0;i<6;i++) { local_mac_address[i] = i; } #endif } 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); } 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: // PSKET_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); return 0; } static void iphone_write_configscript(int fd, int baudrate){ iphone_write_string(fd, "device -D -S\n"); if (iphone_has_csr()) { iphone_csr_set_baud(fd, baudrate); iphone_csr_set_bd_addr(fd); iphone_write_string(fd, "csr -r\n"); } else { iphone_bcm_set_baud(fd, baudrate); iphone_write_string(fd, "msleep 200\n"); iphone_bcm_set_bd_addr(fd); iphone_write_string(fd, "msleep 50\n"); } iphone_write_string(fd, "quit\n"); } static char *os3xBlueTool = "BlueTool"; static char *os4xBlueTool = "/usr/local/bin/BlueToolH4"; static int iphone_on (void *transport_config){ log_dbg("iphone_on: entered\n"); int err = 0; hci_uart_config_t * hci_uart_config = (hci_uart_config_t*) transport_config; // get local0-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 baud == 0 use system default if (hci_uart_config->baudrate_init == 0) { hci_uart_config->baudrate_init = transport_speed; } #ifdef USE_BLUETOOL if (iphone_system_bt_enabled()){ perror("iphone_on: System Bluetooth enabled!"); return 1; } // unload BTServer log_dbg("iphone_on: unload BTServer\n"); err = system ("launchctl unload /System/Library/LaunchDaemons/com.apple.BTServer.plist"); #if 0 // use tmp file for testing on os 3.x int output = open("/tmp/bt.init", O_WRONLY | O_CREAT | O_TRUNC); iphone_write_initscript(hci_uart_config, output); close(output); err = system ("BlueTool < /tmp/bt.init"); #else // check for os version >= 4.0 int os4x = 1; NSAutoreleasePool * pool =[[NSAutoreleasePool alloc] init]; NSString * systemVersion = [[UIDevice currentDevice] systemVersion]; if ([systemVersion hasPrefix:@"2."]) os4x = 0; if ([systemVersion hasPrefix:@"3."]) os4x = 0; // NSLog(@"OS Version: %@, ox4x = %u", systemVersion, os4x); [pool release]; // OS 4.0 char * bluetool = os3xBlueTool; if (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_err( "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_err( "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 - use custom baud rate if (hci_uart_config->baudrate_init != transport_speed) { 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; while (1) { singlechar = fgetc(outputFile); if (singlechar == EOF) break; log_dbg("%c", singlechar); }; err = pclose(outputFile); } #endif // if we sleep for about 3 seconds, we miss a strage packet... but we don't care // sleep(3); #else // quick test if Bluetooth UART can be opened int fd = open(hci_uart_config->device_name, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("iphone_on: Unable to open port "); perror(hci_uart_config->device_name); return 1; } close(fd); #endif return err; } static int iphone_off (void *config){ /* char *machine = get_machine_name(); if (strncmp(machine, "iPad", strlen("iPad")) == 0) { // put iPad Bluetooth into deep sleep system ("echo \"wake off\n quit\" | BlueTool"); } else { // power off for iPhone and iPod system ("echo \"power off\n quit\" | BlueTool"); } */ // power off (all models) log_dbg("iphone_off: turn off using BlueTool\n"); system ("echo \"power off\nquit\" | BlueTool"); // kill Apple BTServer as it gets confused and fails to start anyway // system("killall BTServer"); // reload BTServer log_dbg("iphone_off: reload BTServer\n"); system ("launchctl load /System/Library/LaunchDaemons/com.apple.BTServer.plist"); log_dbg("iphone_off: done\n"); return 0; } static int iphone_sleep(void *config){ // put Bluetooth into deep sleep system ("echo \"wake off\nquit\" | BlueTool"); return 0; } static int iphone_wake(void *config){ // wake up Bluetooth module system ("echo \"wake on\nquit\" | BlueTool"); return 0; } #ifdef IOKIT static void MySleepCallBack( void * refCon, io_service_t service, natural_t messageType, void * messageArgument ) { char data; log_dbg( "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 to get the 30 second delay // 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_dbg("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_dbg("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); } #endif // 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, #ifdef IOKIT .register_for_power_notifications = iphone_register_for_power_notifications #else NULL // register_for_power_notifications #endif };