btstack/src/bt_control_iphone.m
2011-06-05 09:56:19 +00:00

694 lines
22 KiB
Objective-C

/*
* 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 <errno.h>
#include <fcntl.h> // open
#include <stdlib.h> // system, random, srandom
#include <stdio.h> // sscanf, printf
#include <string.h> // strcpy, strcat, strncmp
#include <sys/utsname.h> // uname
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#ifdef USE_BLUETOOL
#include "../SpringBoardAccess/SpringBoardAccess.h"
// minimal IOKit
#ifdef __APPLE__
#include <Availability.h>
#include <Foundation/Foundation.h>
#include <CoreFoundation/CoreFoundation.h>
#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 <mach/mach.h>
#define IOKIT
#include <device/device_types.h>
#include <UIKit/UIKit.h>
// 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, &notifyPortRef, MySleepCallBack, &notifierObject);
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
};