From b85c041bcefead34ebda03ce326f604b8fb8876e Mon Sep 17 00:00:00 2001 From: nico Date: Mon, 22 Aug 2005 19:56:19 +0000 Subject: [PATCH] add chan_bluetooth git-svn-id: svn://svn.openwrt.org/openwrt/trunk@1727 3c298f89-4303-0410-b956-a3cf2f4a3e73 --- openwrt/package/Makefile | 2 +- openwrt/package/asterisk/Config.in | 9 + openwrt/package/asterisk/Makefile | 11 + .../ipkg/asterisk-chan-bluetooth.conffiles | 1 + .../asterisk/ipkg/asterisk-chan-bluetooth.control | 7 + .../patches/asterisk-1.0.9-chan_bluetooth.patch | 3194 ++++++++++++++++++++ 6 files changed, 3223 insertions(+), 1 deletion(-) create mode 100644 openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.conffiles create mode 100644 openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.control create mode 100644 openwrt/package/asterisk/patches/asterisk-1.0.9-chan_bluetooth.patch diff --git a/openwrt/package/Makefile b/openwrt/package/Makefile index 199888673d..816b3760e4 100644 --- a/openwrt/package/Makefile +++ b/openwrt/package/Makefile @@ -228,7 +228,7 @@ vtun-compile: zlib-compile openssl-compile lzo-compile wificonf-compile: wireless-tools-compile nvram-compile wpa_supplicant-compile: openssl-compile -asterisk-compile: ncurses-compile openssl-compile +asterisk-compile: bluez-libs-compile ncurses-compile openssl-compile ifneq ($(BR2_PACKAGE_ASTERISK_CODEC_SPEEX),) asterisk-compile: speex-compile endif diff --git a/openwrt/package/asterisk/Config.in b/openwrt/package/asterisk/Config.in index a8c5d311c8..5b73567b20 100644 --- a/openwrt/package/asterisk/Config.in +++ b/openwrt/package/asterisk/Config.in @@ -13,6 +13,15 @@ config BR2_PACKAGE_ASTERISK http://www.asterisk.org/ +config BR2_PACKAGE_ASTERISK_CHAN_BLUETOOTH + prompt "...-chan-bluetooth - Bluetooth HandsFreeProfile support for Asterisk" + tristate + default m if CONFIG_DEVEL + depends BR2_PACKAGE_ASTERISK + select BR2_PACKAGE_BLUEZ_LIBS + help + The Bluetooth HandsFreeProfile support for Asterisk + config BR2_PACKAGE_ASTERISK_CODEC_ILBC prompt "...-codec-ilbc - Internet Low Bitrate Codec (ILBC) Translator" tristate diff --git a/openwrt/package/asterisk/Makefile b/openwrt/package/asterisk/Makefile index 8b65e60eff..f88b796c55 100644 --- a/openwrt/package/asterisk/Makefile +++ b/openwrt/package/asterisk/Makefile @@ -19,6 +19,7 @@ $(eval $(call PKG_template,ASTERISK_PGSQL,asterisk-pgsql,$(PKG_VERSION)-$(PKG_RE $(eval $(call PKG_template,ASTERISK_VOICEMAIL,asterisk-voicemail,$(PKG_VERSION)-$(PKG_RELEASE),$(ARCH))) $(eval $(call PKG_template,ASTERISK_SOUNDS,asterisk-sounds,$(PKG_VERSION)-$(PKG_RELEASE),$(ARCH))) $(eval $(call PKG_template,ASTERISK_CODEC_ILBC,asterisk-codec-ilbc,$(PKG_VERSION)-$(PKG_RELEASE),$(ARCH))) +$(eval $(call PKG_template,ASTERISK_CHAN_BLUETOOTH,asterisk-chan-bluetooth,$(PKG_VERSION)-$(PKG_RELEASE),$(ARCH))) $(eval $(call PKG_template,ASTERISK_CODEC_LPC10,asterisk-codec-lpc10,$(PKG_VERSION)-$(PKG_RELEASE),$(ARCH))) $(eval $(call PKG_template,ASTERISK_CODEC_SPEEX,asterisk-codec-speex,$(PKG_VERSION)-$(PKG_RELEASE),$(ARCH))) $(eval $(call PKG_template,ASTERISK_PBX_DUNDI,asterisk-pbx-dundi,$(PKG_VERSION)-$(PKG_RELEASE),$(ARCH))) @@ -85,12 +86,14 @@ $(IPKG_ASTERISK): *musiconhold* *zapateller* *jpeg*; \ rm -f {codec,format}_ilbc.so ; \ rm -f codec_lpc10.so ; \ + rm -f chan_bluetooth.so ; \ rm -f pbx_dundi.so ; \ ) (cd $(IDIR_ASTERISK)/etc/asterisk; \ rm -f *odbc* *mysql* *postgres* *pgsql* *voicemail* *adsi* *oss* *alsa* \ *festival* *modem* *meetme* *phone* *tds* *vofr* *rpt* *vpb* \ *zapata* *musiconhold*; \ + rm -f bluetooth.conf ; \ rm -f dundi.conf ; \ ) install -d -m0755 $(IDIR_ASTERISK)/etc/default @@ -134,6 +137,14 @@ $(IPKG_ASTERISK_VOICEMAIL): $(RSTRIP) $(IDIR_ASTERISK_VOICEMAIL) $(IPKG_BUILD) $(IDIR_ASTERISK_VOICEMAIL) $(PACKAGE_DIR) +$(IPKG_ASTERISK_CHAN_BLUETOOTH): + install -d -m0755 $(IDIR_ASTERISK_CHAN_BLUETOOTH)/etc/asterisk + install -m0644 $(PKG_BUILD_DIR)/configs/bluetooth.conf $(IDIR_ASTERISK_CHAN_BLUETOOTH)/etc/asterisk/bluetooth.conf + install -d -m0755 $(IDIR_ASTERISK_CHAN_BLUETOOTH)/usr/lib/asterisk/modules + install -m0755 $(PKG_BUILD_DIR)/channels/chan_bluetooth.so $(IDIR_ASTERISK_CHAN_BLUETOOTH)/usr/lib/asterisk/modules/ + $(RSTRIP) $(IDIR_ASTERISK_CHAN_BLUETOOTH) + $(IPKG_BUILD) $(IDIR_ASTERISK_CHAN_BLUETOOTH) $(PACKAGE_DIR) + $(IPKG_ASTERISK_CODEC_ILBC): install -d -m0755 $(IDIR_ASTERISK_CODEC_ILBC)/usr/lib/asterisk/modules install -m0755 $(PKG_BUILD_DIR)/codecs/codec_ilbc.so $(IDIR_ASTERISK_CODEC_ILBC)/usr/lib/asterisk/modules/ diff --git a/openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.conffiles b/openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.conffiles new file mode 100644 index 0000000000..40a085235e --- /dev/null +++ b/openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.conffiles @@ -0,0 +1 @@ +/etc/asterisk/bluetooth.conf diff --git a/openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.control b/openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.control new file mode 100644 index 0000000000..4f9f67c5f1 --- /dev/null +++ b/openwrt/package/asterisk/ipkg/asterisk-chan-bluetooth.control @@ -0,0 +1,7 @@ +Package: asterisk-chan-bluetooth +Priority: optional +Section: net +Maintainer: OpenWrt Developers Team +Source: http://openwrt.org/cgi-bin/viewcvs.cgi/openwrt/package/asterisk/ +Description: Bluetooth HandsFreeProfile support for Asterisk +Depends: asterisk, bluez-libs diff --git a/openwrt/package/asterisk/patches/asterisk-1.0.9-chan_bluetooth.patch b/openwrt/package/asterisk/patches/asterisk-1.0.9-chan_bluetooth.patch new file mode 100644 index 0000000000..6c77ae7c48 --- /dev/null +++ b/openwrt/package/asterisk/patches/asterisk-1.0.9-chan_bluetooth.patch @@ -0,0 +1,3194 @@ +diff -ruN asterisk-1.0.9-old/channels/Makefile asterisk-1.0.9-new/channels/Makefile +--- asterisk-1.0.9-old/channels/Makefile 2005-08-22 20:42:22.000000000 +0200 ++++ asterisk-1.0.9-new/channels/Makefile 2005-08-22 21:12:14.000000000 +0200 +@@ -37,6 +37,12 @@ + # + #CHANNEL_LIBS+=chan_vofr + ++# ++# Asterisk Bluetooth Support ++# http://www.crazygreek.co.uk/content/chan_bluetooth ++# ++CHANNEL_LIBS += chan_bluetooth.so ++ + ifeq (${OSARCH},OpenBSD) + MYSQLLIB=-L/usr/local/lib/mysql -lmysqlclient + CFLAGS+=-I/usr/local/include +@@ -202,6 +208,9 @@ + chan_h323.so: chan_h323.o h323/libchanh323.a + $(CC) $(SOLINK) -o $@ $< h323/libchanh323.a $(CHANH323LIB) -L$(PWLIBDIR)/lib $(PTLIB) -L$(OPENH323DIR)/lib $(H323LIB) -L/usr/lib -lcrypto -lssl -lexpat + ++chan_bluetooth.so: chan_bluetooth.o ++ $(CC) $(SOLINK) -o $@ $< $(LDFLAGS_EXTRA) -lbluetooth ++ + + #chan_modem.so : chan_modem.o + # $(CC) -rdynamic -shared -Xlinker -x -o $@ $< +diff -ruN asterisk-1.0.9-old/channels/chan_bluetooth.c asterisk-1.0.9-new/channels/chan_bluetooth.c +--- asterisk-1.0.9-old/channels/chan_bluetooth.c 1970-01-01 01:00:00.000000000 +0100 ++++ asterisk-1.0.9-new/channels/chan_bluetooth.c 2004-11-06 17:35:58.000000000 +0100 +@@ -0,0 +1,3127 @@ ++/* ++ * Asterisk -- A telephony toolkit for Linux. ++ * ++ * Asterisk Bluetooth Channel ++ * ++ * Author: Theo Zourzouvillys ++ * ++ * Adaptive Linux Solutions ++ * ++ * Copyright (C) 2004 Adaptive Linux Solutions ++ * ++ * This program is free software, distributed under the terms of ++ * the GNU General Public License ++ * ++ * ******************* NOTE NOTE NOTE NOTE NOTE ********************* ++ * ++ * This code is not at all tested, and only been developed with a ++ * HBH-200 headset and a Nokia 6310i right now. ++ * ++ * Expect it to crash, dial random numbers, and steal all your money. ++ * ++ * PLEASE try any headsets and phones, and let me know the results, ++ * working or not, along with all debug output! ++ * ++ * ------------------------------------------------------------------ ++ * ++ * Asterisk Bluetooth Support ++ * ++ * Well, here we go - Attempt to provide Handsfree profile support in ++ * both AG and HF modes, AG (AudioGateway) mode support for using ++ * headsets, and HF (Handsfree) mode for utilising mobile/cell phones ++ * ++ * It would be nice to also provide Headset support at some time in ++ * the future, however, a working Handsfree profile is nice for now, ++ * and as far as I can see, almost all new HS devices also support HF ++ * ++ * ------------------------------------------------------------------ ++ * INSTRUCTIONS ++ * ++ * You need to have bluez's bluetooth stack, along with user space ++ * tools (>=v2.10), and running hcid and sdsp. ++ * ++ * See bluetooth.conf for configuration details. ++ * ++ * - Ensure bluetooth subsystem is up and running. 'hciconfig' ++ * should show interface as UP. ++ * ++ * - If you're trying to use a headset/HS, start up asterisk, and try ++ * to pair it as you normally would. ++ * ++ * - If you're trying to use a Phone/AG, just make sure bluetooth is ++ * enabled on your phone, and start up asterisk. ++ * ++ * - 'bluetooth show peers' will show all bluetooth devices states. ++ * ++ * - Send a call out by using Dial(BLT/DevName/0123456). Call a HS ++ * with Dial(BLT/DevName) ++ * ++ * ------------------------------------------------------------------ ++ * BUGS ++ * ++ * - What should happen when an AG is paired with asterisk and ++ * someone uses the AG dalling a number manually? My test phone ++ * seems to try to open an SCO link. Perhaps an extension to ++ * route the call to, or maybe drop the RFCOM link all together? ++ * ++ * ------------------------------------------------------------------ ++ * COMPATIBILITY ++ * ++ * PLEASE email with the results of ANY ++ * device not listed in here (working or not), or if the device is ++ * listed and it doesn't work! Please also email full debug output ++ * for any device not working correctly or generating errors in log. ++ * ++ * HandsFree Profile: ++ * ++ * HS (HeadSet): ++ * - Ericsson HBH-200 ++ * ++ * AG (AudioGateway): ++ * - Nokia 6310i ++ * ++ * ------------------------------------------------------------------ ++ * ++ * Questions, bugs, or (preferably) patches to: ++ * ++ * ++ * ++ * ------------------------------------------------------------------ ++ */ ++ ++/* ---------------------------------- */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* --- Data types and definitions --- */ ++ ++#ifndef HANDSFREE_AUDIO_GW_SVCLASS_ID ++# define HANDSFREE_AUDIO_GW_SVCLASS_ID 0x111f ++#endif ++ ++#define BLUETOOTH_FORMAT AST_FORMAT_SLINEAR ++#define BLT_CHAN_NAME "BLT" ++#define BLT_CONFIG_FILE "bluetooth.conf" ++#define BLT_RDBUFF_MAX 1024 ++#define BLT_DEFAULT_HCI_DEV 0 ++#define BLT_SVN_REVISION "$Rev: 38 $" ++ ++/* ---------------------------------- */ ++ ++typedef enum { ++ BLT_ROLE_NONE = 0, // Unknown Device ++ BLT_ROLE_HS = 1, // Device is a Headset ++ BLT_ROLE_AG = 2 // Device is an Audio Gateway ++} blt_role_t; ++ ++/* State when we're in HS mode */ ++ ++typedef enum { ++ BLT_STATE_WANT_R = 0, ++ BLT_STATE_WANT_N = 1, ++ BLT_STATE_WANT_CMD = 2, ++ BLT_STATE_WANT_N2 = 3, ++} blt_state_t; ++ ++typedef enum { ++ BLT_STATUS_DOWN, ++ BLT_STATUS_CONNECTING, ++ BLT_STATUS_NEGOTIATING, ++ BLT_STATUS_READY, ++ BLT_STATUS_RINGING, ++ BLT_STATUS_IN_CALL, ++} blt_status_t; ++ ++/* ---------------------------------- */ ++ ++/* Default config settings */ ++ ++#define BLT_DEFAULT_CHANNEL_AG 5 ++#define BLT_DEFAULT_CHANNEL_HS 6 ++#define BLT_DEFAULT_ROLE BLT_ROLE_HS ++#define BLT_OBUF_LEN (48 * 25) ++ ++#define BUFLEN 4800 ++ ++/* ---------------------------------- */ ++ ++typedef struct blt_dev blt_dev_t; ++ ++// XXX:T: Tidy this lot up. ++struct blt_dev { ++ ++ blt_status_t status; /* Device Status */ ++ ++ struct ast_channel * owner; /* Channel we belong to, possibly NULL */ ++ blt_dev_t * dev; /* The bluetooth device channel is for */ ++ struct ast_frame fr; /* Recieved frame */ ++ ++ /* SCO Handler */ ++ int sco_pipe[2]; /* SCO alert pipe */ ++ int sco; /* SCO fd */ ++ int sco_handle; /* SCO Handle */ ++ int sco_mtu; /* SCO MTU */ ++ int sco_running; /* 1 when sCO thread should be running */ ++ pthread_t sco_thread; /* SCO thread */ ++ ast_mutex_t sco_lock; /* SCO lock */ ++ int sco_pos_in; /* Reader in position */ ++ int sco_pos_out; /* Reader out position */ ++ int sco_sending; /* Sending SCO packets */ ++ char buf[1024]; /* Incoming data buffer */ ++ char sco_buf_out[BUFLEN+1]; /* 24 chunks of 48 */ ++ char sco_buf_in[BUFLEN+1]; /* 24 chunks of 48 */ ++ ++ char dnid[1024]; /* Outgoi gncall dialed number */ ++ unsigned char * obuf[BLT_OBUF_LEN]; /* Outgoing data buffer */ ++ int obuf_len; /* Output Buffer Position */ ++ int obuf_wpos; /* Buffer Reader */ ++ ++ // device ++ int autoconnect; /* 1 for autoconnect */ ++ int outgoing_id; /* Outgoing connection scheduler id */ ++ char * name; /* Devices friendly name */ ++ blt_role_t role; /* Device role (HS or AG) */ ++ bdaddr_t bdaddr; /* remote address */ ++ int channel; /* remote channel */ ++ int rd; /* RFCOMM fd */ ++ int tmp_rd; /* RFCOMM fd */ ++ int call_cnt; /* Number of attempted calls */ ++ ast_mutex_t lock; /* RFCOMM socket lock */ ++ char rd_buff[BLT_RDBUFF_MAX]; /* RFCOMM input buffer */ ++ int rd_buff_pos; /* RFCOMM input buffer position */ ++ int ready; /* 1 When ready */ ++ ++ /* AG mode */ ++ char last_ok_cmd[BLT_RDBUFF_MAX]; /* Runtime[AG]: Last AT command that was OK */ ++ int cind; /* Runtime[AG]: Recieved +CIND */ ++ int call_pos, service_pos, callsetup_pos; /* Runtime[AG]: Positions in CIND/CMER */ ++ int call, service, callsetup; /* Runtime[AG]: Values */ ++ ++ /* HS mode */ ++ blt_state_t state; /* Runtime: Device state (AG mode only) */ ++ int ring_timer; /* Runtime:Ring Timer */ ++ char last_err_cmd[BLT_RDBUFF_MAX]; /* Runtime: Last AT command that was OK */ ++ void (*cb)(blt_dev_t * dev, char * str); /* Runtime: Callback when in HS mode */ ++ ++ int brsf; /* Runtime: Bluetooth Retrieve Supported Features */ ++ int bvra; /* Runtime: Bluetooth Voice Recognised Activation */ ++ int gain_speaker; /* Runtime: Gain Of Speaker */ ++ int clip; /* Runtime: Supports CLID */ ++ int colp; /* Runtime: Connected Line ID */ ++ int elip; /* Runtime: (Ericsson) Supports CLID */ ++ int eolp; /* Runtime: (Ericsson) Connected Line ID */ ++ int ringing; /* Runtime: Device is ringing */ ++ ++ blt_dev_t * next; /* Next in linked list */ ++ ++}; ++ ++typedef struct blt_atcb { ++ ++ /* The command */ ++ char * str; ++ ++ /* DTE callbacks: */ ++ int (*set)(blt_dev_t * dev, const char * arg, int len); ++ int (*read)(blt_dev_t * dev); ++ int (*execute)(blt_dev_t * dev, const char * data); ++ int (*test)(blt_dev_t * dev); ++ ++ /* DCE callbacks: */ ++ int (*unsolicited)(blt_dev_t * dev, const char * value); ++ ++} blt_atcb_t; ++ ++/* ---------------------------------- */ ++ ++static void rd_close(blt_dev_t * dev, int reconnect, int err); ++static int send_atcmd(blt_dev_t * device, const char * fmt, ...); ++static int sco_connect(blt_dev_t * dev); ++ ++/* ---------------------------------- */ ++ ++/* RFCOMM channel we listen on*/ ++static int rfcomm_channel_ag = BLT_DEFAULT_CHANNEL_AG; ++static int rfcomm_channel_hs = BLT_DEFAULT_CHANNEL_HS; ++ ++/* Address of local bluetooth interface */ ++static int hcidev_id; ++static bdaddr_t local_bdaddr; ++ ++/* All the current sockets */ ++AST_MUTEX_DEFINE_STATIC(iface_lock); ++static blt_dev_t * iface_head; ++static int ifcount = 0; ++ ++static int sdp_record_hs = -1; ++static int sdp_record_ag = -1; ++ ++/* RFCOMM listen socket */ ++static int rfcomm_sock_ag = -1; ++static int rfcomm_sock_hs = -1; ++static int sco_socket = -1; ++ ++static int monitor_pid = -1; ++ ++/* The socket monitoring thread */ ++static pthread_t monitor_thread = AST_PTHREADT_NULL; ++AST_MUTEX_DEFINE_STATIC(monitor_lock); ++ ++/* Cound how many times this module is currently in use */ ++static int usecnt = 0; ++AST_MUTEX_DEFINE_STATIC(usecnt_lock); ++ ++static struct sched_context * sched = NULL; ++ ++/* ---------------------------------- */ ++ ++static const char * ++role2str(blt_role_t role) ++{ ++ switch (role) { ++ case BLT_ROLE_HS: ++ return "HS"; ++ case BLT_ROLE_AG: ++ return "AG"; ++ case BLT_ROLE_NONE: ++ return "??"; ++ } ++} ++ ++static const char * ++status2str(blt_status_t status) ++{ ++ switch (status) { ++ case BLT_STATUS_DOWN: ++ return "Down"; ++ case BLT_STATUS_CONNECTING: ++ return "Connecting"; ++ case BLT_STATUS_NEGOTIATING: ++ return "Negotiating"; ++ case BLT_STATUS_READY: ++ return "Ready"; ++ case BLT_STATUS_RINGING: ++ return "Ringing"; ++ case BLT_STATUS_IN_CALL: ++ return "InCall"; ++ }; ++ return "Unknown"; ++} ++ ++int sock_err(int fd) ++{ ++ int ret; ++ int len = sizeof(ret); ++ getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len); ++ return ret; ++} ++ ++/* ---------------------------------- */ ++ ++static const char * ++parse_cind(const char * str, char * name, int name_len) ++{ ++ int c = 0; ++ ++ memset(name, 0, name_len); ++ ++ while (*str) { ++ if (*str == '(') { ++ if (++c == 1 && *(str+1) == '"') { ++ const char * start = str + 2; ++ int len = 0; ++ str += 2; ++ while (*str && *str != '"') { ++ len++; ++ str++; ++ } ++ if (len == 0) ++ return NULL; ++ strncpy(name, start, (len > name_len) ? name_len : len); ++ } ++ } else if (*str == ')') ++ c--; ++ else if (c == 0 && *str == ',') ++ return str + 1; ++ str++; ++ } ++ return NULL; ++} ++ ++static void ++set_cind(blt_dev_t * dev, int indicator, int val) ++{ ++ ++ ast_log(LOG_DEBUG, "CIND %d set to %d\n", indicator, val); ++ ++ if (indicator == dev->callsetup_pos) { ++ ++ // call progress ++ ++ dev->callsetup = val; ++ ++ switch (val) { ++ case 3: ++ // Outgoign ringing ++ if (dev->owner && dev->role == BLT_ROLE_AG) ++ ast_queue_control(dev->owner, AST_CONTROL_RINGING); ++ break; ++ case 2: ++ break; ++ case 1: ++ break; ++ case 0: ++ if (dev->owner && dev->role == BLT_ROLE_AG && dev->call == 0) ++ ast_queue_control(dev->owner, AST_CONTROL_CONGESTION); ++ break; ++ } ++ ++ } else if (indicator == dev->service_pos) { ++ ++ // Signal ++ ++ if (val == 0) ++ ast_log(LOG_NOTICE, "Audio Gateway %s lost signal\n", dev->name); ++ else if (dev->service == 0 && val > 0) ++ ast_log(LOG_NOTICE, "Audio Gateway %s got signal\n", dev->name); ++ ++ dev->service = val; ++ ++ } else if (indicator == dev->call_pos) { ++ ++ // Call ++ ++ dev->call = val; ++ ++ if (dev->owner) { ++ if (val == 1) { ++ ast_queue_control(dev->owner, AST_CONTROL_ANSWER); ++ } else if (val == 0) ++ ast_queue_control(dev->owner, AST_CONTROL_HANGUP); ++ } ++ ++ } ++ ++ ++} ++ ++/* ---------------------------------- */ ++ ++int ++set_buffer(char * ring, char * data, int circular_len, int * pos, int data_len) ++{ ++ int start_pos = *(pos); ++ int done = 0; ++ int copy; ++ ++ while (data_len) { ++ // Set can_do to the most we can do in this copy. ++ ++ copy = MIN(circular_len - start_pos, data_len); ++ memcpy(ring + start_pos, data + done, copy); ++ ++ done += copy; ++ start_pos += copy; ++ data_len -= copy; ++ ++ if (start_pos == circular_len) ++ start_pos = 0; ++ } ++ *(pos) = start_pos; ++ return 0; ++} ++ ++int ++get_buffer(char * dst, char * ring, int ring_size, int * head, int to_copy) ++{ ++ int copy; ++ ++ // |1|2|3|4|5|6|7|8|9| ++ // |-----| ++ ++ while (to_copy) { ++ ++ // Set can_do to the most we can do in this copy. ++ copy = MIN(ring_size - *head, to_copy); ++ ++ // ast_log(LOG_DEBUG, "Getting: %d bytes, From pos %d\n", copy, *head); ++ memcpy(dst, ring + *head, copy); ++ ++ dst += copy; ++ *head += copy; ++ to_copy -= copy; ++ ++ if (*head == ring_size ) ++ *head = 0; ++ ++ } ++ ++ return 0; ++} ++ ++/* Handle SCO audio sync. ++ * ++ * If we are the MASTER, then we control the timing, ++ * in 48 byte chunks. If we're the SLAVE, we send ++ * as and when we recieve a packet. ++ * ++ * Because of packet/timing nessecity, we ++ * start up a thread when we're passing audio, so ++ * that things are timed exactly right. ++ * ++ * sco_thread() is the function that handles it. ++ * ++ */ ++ ++static void * ++sco_thread(void * data) ++{ ++ blt_dev_t * dev = (blt_dev_t*)data; ++ int res; ++ struct pollfd pfd[2]; ++ int in_pos = 0; ++ int out_pos = 0; ++ char c = 1; ++ int sending; ++ char buf[1024]; ++ int len; ++ ++ // Avoid deadlock in odd circumstances ++ ++ ast_log(LOG_DEBUG, "SCO thread started on fd %d, pid %d\n", dev->sco, getpid()); ++ ++ // dev->status = BLT_STATUS_IN_CALL; ++ // ast_queue_control(dev->owner, AST_CONTROL_ANSWER); ++ // Set buffer to silence, just incase. ++ ++ ast_mutex_lock(&(dev->sco_lock)); ++ ++ memset(dev->sco_buf_in, 0x7f, BUFLEN); ++ memset(dev->sco_buf_out, 0x7f, BUFLEN); ++ ++ dev->sco_pos_in = 0; ++ dev->sco_pos_out = 0; ++ ++ ast_mutex_unlock(&(dev->sco_lock)); ++ ++ while (1) { ++ ++ ast_mutex_lock(&(dev->sco_lock)); ++ ++ if (dev->sco_running != 1) { ++ ast_log(LOG_DEBUG, "SCO stopped.\n"); ++ break; ++ } ++ ++ pfd[0].fd = dev->sco; ++ pfd[0].events = POLLIN; ++ ++ pfd[1].fd = dev->sco_pipe[1]; ++ pfd[1].events = POLLIN; ++ ++ ast_mutex_unlock(&(dev->sco_lock)); ++ ++ res = poll(pfd, 2, 50); ++ ++ if (res == -1 && errno != EINTR) { ++ ast_log(LOG_DEBUG, "SCO poll() error\n"); ++ break; ++ } ++ ++ if (res == 0) ++ continue; ++ ++ ast_mutex_lock(&(dev->sco_lock)); ++ ++ if (pfd[0].revents & POLLIN) { ++ ++ len = read(dev->sco, buf, 48); ++ ++ if (len) { ++ ast_mutex_lock(&(dev->lock)); ++ set_buffer(dev->sco_buf_in, buf, BUFLEN, &in_pos, len); ++ get_buffer(buf, dev->sco_buf_out, BUFLEN, &out_pos, len); ++ write(dev->sco, buf, len); ++ if (dev->owner && dev->owner->_state == AST_STATE_UP) ++ write(dev->sco_pipe[1], &c, 1); ++ ast_mutex_unlock(&(dev->lock)); ++ } ++ ++ ast_mutex_unlock(&(dev->sco_lock)); ++ ++ } else if (pfd[0].revents) { ++ ++ int e = sock_err(pfd[0].fd); ++ ast_log(LOG_ERROR, "SCO connection error: %s (errno %d)\n", strerror(e), e); ++ break; ++ ++ } else if (pfd[1].revents & POLLIN) { ++ ++ int len; ++ ++ len = read(pfd[1].fd, &c, 1); ++ sending = (sending) ? 0 : 1; ++ ++ ast_mutex_unlock(&(dev->sco_lock)); ++ ++ } else if (pfd[1].revents) { ++ ++ int e = sock_err(pfd[1].fd); ++ ast_log(LOG_ERROR, "SCO pipe connection event %d on pipe[1]=%d: %s (errno %d)\n", pfd[1].revents, pfd[1].fd, strerror(e), e); ++ break; ++ ++ } else { ++ ast_log(LOG_NOTICE, "Unhandled poll output\n"); ++ ast_mutex_unlock(&(dev->sco_lock)); ++ } ++ ++ } ++ ++ ast_mutex_lock(&(dev->lock)); ++ close(dev->sco); ++ dev->sco = -1; ++ dev->sco_running = -1; ++ ast_mutex_unlock(&(dev->sco_lock)); ++ if (dev->owner) ++ ast_queue_control(dev->owner, AST_CONTROL_HANGUP); ++ ast_mutex_unlock(&(dev->lock)); ++ ast_log(LOG_DEBUG, "SCO thread stopped\n"); ++ return NULL; ++} ++ ++/* Start SCO thread. Must be called with dev->lock */ ++ ++static int ++sco_start(blt_dev_t * dev, int fd) ++{ ++ ++ if (dev->sco_pipe[1] <= 0) { ++ ast_log(LOG_ERROR, "SCO pipe[1] == %d\n", dev->sco_pipe[1]); ++ return -1; ++ } ++ ++ ast_mutex_lock(&(dev->sco_lock)); ++ ++ if (dev->sco_running != -1) { ++ ast_log(LOG_ERROR, "Tried to start SCO thread while already running\n"); ++ ast_mutex_unlock(&(dev->sco_lock)); ++ return -1; ++ } ++ ++ if (dev->sco == -1) { ++ if (fd > 0) { ++ dev->sco = fd; ++ } else if (sco_connect(dev) != 0) { ++ ast_log(LOG_ERROR, "SCO fd invalid\n"); ++ ast_mutex_unlock(&(dev->sco_lock)); ++ return -1; ++ } ++ } ++ ++ dev->sco_running = 1; ++ ++ if (ast_pthread_create(&(dev->sco_thread), NULL, sco_thread, dev) < 0) { ++ ast_log(LOG_ERROR, "Unable to start SCO thread.\n"); ++ dev->sco_running = -1; ++ ast_mutex_unlock(&(dev->sco_lock)); ++ return -1; ++ } ++ ++ ast_mutex_unlock(&(dev->sco_lock)); ++ ++ return 0; ++} ++ ++/* Stop SCO thread. Must be called with dev->lock */ ++ ++static int ++sco_stop(blt_dev_t * dev) ++{ ++ ast_mutex_lock(&(dev->sco_lock)); ++ if (dev->sco_running == 1) ++ dev->sco_running = 0; ++ else ++ dev->sco_running = -1; ++ dev->sco_sending = 0; ++ ast_mutex_unlock(&(dev->sco_lock)); ++ return 0; ++} ++ ++/* ---------------------------------- */ ++ ++/* Answer the call. Call with lock held on device */ ++ ++static int ++answer(blt_dev_t * dev) ++{ ++ ++ if ( (!dev->owner) || (dev->ready != 1) || (dev->status != BLT_STATUS_READY && dev->status != BLT_STATUS_RINGING)) { ++ ast_log(LOG_ERROR, "Attempt to answer() in invalid state (owner=%p, ready=%d, status=%s)\n", ++ dev->owner, dev->ready, status2str(dev->status)); ++ return -1; ++ } ++ ++ // dev->sd = sco_connect(&local_bdaddr, &(dev->bdaddr), NULL, NULL, 0); ++ // dev->status = BLT_STATUS_IN_CALL; ++ // dev->owner->fds[0] = dev->sd; ++ // if we are answering (hitting button): ++ ast_queue_control(dev->owner, AST_CONTROL_ANSWER); ++ // if asterisk signals us to answer: ++ // ast_setstate(ast, AST_STATE_UP); ++ ++ /* Start SCO link */ ++ sco_start(dev, -1); ++ return 0; ++} ++ ++/* ---------------------------------- */ ++ ++static int ++blt_write(struct ast_channel * ast, struct ast_frame * frame) ++{ ++ blt_dev_t * dev = ast->pvt->pvt; ++ ++ /* Write a frame of (presumably voice) data */ ++ ++ if (frame->frametype != AST_FRAME_VOICE) { ++ ast_log(LOG_WARNING, "Don't know what to do with frame type '%d'\n", frame->frametype); ++ return 0; ++ } ++ ++ if (!(frame->subclass & BLUETOOTH_FORMAT)) { ++ ast_log(LOG_WARNING, "Cannot handle frames in format %d\n", frame->subclass); ++ return 0; ++ } ++ ++ if (ast->_state != AST_STATE_UP) { ++ return 0; ++ } ++ ++ ast_mutex_lock(&(dev->sco_lock)); ++ set_buffer(dev->sco_buf_out, frame->data, BUFLEN, &(dev->sco_pos_out), MIN(frame->datalen, BUFLEN)); ++ ast_mutex_unlock(&(dev->sco_lock)); ++ ++ return 0; ++ ++} ++ ++static struct ast_frame * ++blt_read(struct ast_channel * ast) ++{ ++ blt_dev_t * dev = ast->pvt->pvt; ++ char c = 1; ++ int len; ++ ++ /* Some nice norms */ ++ ++ dev->fr.datalen = 0; ++ dev->fr.samples = 0; ++ dev->fr.data = NULL; ++ dev->fr.src = BLT_CHAN_NAME; ++ dev->fr.offset = 0; ++ dev->fr.mallocd = 0; ++ dev->fr.delivery.tv_sec = 0; ++ dev->fr.delivery.tv_usec = 0; ++ ++ ast_mutex_lock(&(dev->sco_lock)); ++ dev->sco_sending = 1; ++ read(dev->sco_pipe[0], &c, 1); ++ len = get_buffer(dev->buf, dev->sco_buf_in, BUFLEN, &(dev->sco_pos_in), 48); ++ ast_mutex_unlock(&(dev->sco_lock)); ++ ++ dev->fr.data = dev->buf; ++ dev->fr.samples = len / 2; ++ dev->fr.datalen = len; ++ dev->fr.frametype = AST_FRAME_VOICE; ++ dev->fr.subclass = BLUETOOTH_FORMAT; ++ dev->fr.offset = 0; ++ ++ return &dev->fr; ++} ++ ++/* Escape Any '"' in str. Return malloc()ed string */ ++static char * ++escape_str(char * str) ++{ ++ char * ptr = str; ++ char * pret; ++ char * ret; ++ int len = 0; ++ ++ while (*ptr) { ++ if (*ptr == '"') ++ len++; ++ len++; ++ ptr++; ++ } ++ ++ ret = malloc(len + 1); ++ pret = memset(ret, 0, len + 1); ++ ++ ptr = str; ++ ++ while (*ptr) { ++ if (*ptr == '"') ++ *pret++ = '\\'; ++ *pret++ = *ptr++; ++ } ++ ++ return ret; ++} ++ ++static int ++ring_hs(blt_dev_t * dev) ++{ ++#if (ASTERISK_VERSION_NUM < 010100) ++ char tmp[AST_MAX_EXTENSION]; ++ char *name, *num; ++#endif ++ ++ ast_mutex_lock(&(dev->lock)); ++ ++ if (dev->owner == NULL) { ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++ } ++ ++ dev->ringing = 1; ++ dev->status = BLT_STATUS_RINGING; ++ ++ send_atcmd(dev, "RING"); ++ ++ dev->owner->rings++; ++ ++ // XXX:T: '"' needs to be escaped in ELIP. ++ ++#if (ASTERISK_VERSION_NUM < 010100) ++ ++ if (dev->owner->callerid) { ++ ++ memset(tmp, 0, sizeof(tmp)); ++ strncpy(tmp, dev->owner->callerid, sizeof(tmp)-1); ++ ++ if (!ast_callerid_parse(tmp, &name, &num)) { ++ ++ if (dev->clip && num) ++ send_atcmd(dev, "+CLIP: \"%s\",129", num); ++ ++ if (dev->elip && name) { ++ char * esc = escape_str(name); ++ send_atcmd(dev, "*ELIP: \"%s\"", esc); ++ free(esc); ++ } ++ } ++ } ++ ++ ++#else ++ ++ if (dev->clip && dev->owner->cid.cid_num) ++ send_atcmd(dev, "+CLIP: \"%s\",129", dev->owner->cid.cid_num); ++ ++ if (dev->elip && dev->owner->cid.cid_name) { ++ char * esc = escape_str(dev->owner->cid.cid_name); ++ send_atcmd(dev, "*ELIP: \"%s\"", esc); ++ free(esc); ++ } ++ ++#endif ++ ++ ast_mutex_unlock(&(dev->lock)); ++ ++ return 1; ++} ++ ++/* ++ * If the HS is already connected, then just send RING, otherwise, things get a ++ * little more sticky. We first have to find the channel for HS using SDP, ++ * then intiate the connection. Once we've done that, we can start the call. ++ */ ++ ++static int ++blt_call(struct ast_channel * ast, char * dest, int timeout) ++{ ++ blt_dev_t * dev = ast->pvt->pvt; ++ ++ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) { ++ ast_log(LOG_WARNING, "blt_call called on %s, neither down nor reserved\n", ast->name); ++ return -1; ++ } ++ ++ ast_log(LOG_DEBUG, "Calling %s on %s [t: %d]\n", dest, ast->name, timeout); ++ ++ if (ast_mutex_lock(&iface_lock)) { ++ ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); ++ return -1; ++ } ++ ++// ast_mutex_lock(&(dev->lock)); ++ ++ if (dev->ready == 0) { ++ ast_log(LOG_WARNING, "Tried to call a device not ready/connected.\n"); ++ ast_setstate(ast, AST_CONTROL_CONGESTION); ++// ast_mutex_unlock(&(dev->lock)); ++ ast_mutex_unlock(&iface_lock); ++ return 0; ++ } ++ ++ if (dev->role == BLT_ROLE_HS) { ++ ++ send_atcmd(dev, "+CIEV: 3,1"); ++ ++ dev->ring_timer = ast_sched_add(sched, 5000, AST_SCHED_CB(ring_hs), dev); ++ ++ ring_hs(dev); ++ ++ ast_setstate(ast, AST_STATE_RINGING); ++ ast_queue_control(ast, AST_CONTROL_RINGING); ++ ++ } else if (dev->role == BLT_ROLE_AG) { ++ ++ send_atcmd(dev, "ATD%s;", dev->dnid); ++ ++ } else { ++ ++ ast_setstate(ast, AST_CONTROL_CONGESTION); ++ ast_log(LOG_ERROR, "Unknown device role\n"); ++ ++ } ++ ++// ast_mutex_unlock(&(dev->lock)); ++ ast_mutex_unlock(&iface_lock); ++ ++ return 0; ++} ++ ++static int ++blt_hangup(struct ast_channel * ast) ++{ ++ blt_dev_t * dev = ast->pvt->pvt; ++ ++ ast_log(LOG_DEBUG, "blt_hangup(%s)\n", ast->name); ++ ++ if (!ast->pvt->pvt) { ++ ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); ++ return 0; ++ } ++ ++ if (ast_mutex_lock(&iface_lock)) { ++ ast_log(LOG_ERROR, "Failed to get iface_lock\n"); ++ return 0; ++ } ++ ++ ast_mutex_lock(&(dev->lock)); ++ ++ sco_stop(dev); ++ dev->sco_sending = 0; ++ ++ if (dev->role == BLT_ROLE_HS) { ++ ++ if (dev->ringing == 0) { ++ // Actual call in progress ++ send_atcmd(dev, "+CIEV: 2,0"); ++ } else { ++ ++ // Just ringing still ++ ++ if (dev->role == BLT_ROLE_HS) ++ send_atcmd(dev, "+CIEV: 3,0"); ++ ++ if (dev->ring_timer >= 0) ++ ast_sched_del(sched, dev->ring_timer); ++ ++ dev->ring_timer = -1; ++ dev->ringing = 0; ++ ++ } ++ ++ } else if (dev->role == BLT_ROLE_AG) { ++ ++ // Cancel call. ++ send_atcmd(dev, "AT+CHUP"); ++ ++ } ++ ++ if (dev->status == BLT_STATUS_IN_CALL || dev->status == BLT_STATUS_RINGING) ++ dev->status = BLT_STATUS_READY; ++ ++ ast->pvt->pvt = NULL; ++ dev->owner = NULL; ++ ast_mutex_unlock(&(dev->lock)); ++ ast_setstate(ast, AST_STATE_DOWN); ++ ast_mutex_unlock(&(iface_lock)); ++ ++ return 0; ++} ++ ++static int ++blt_indicate(struct ast_channel * c, int condition) ++{ ++ ast_log(LOG_DEBUG, "blt_indicate (%d)\n", condition); ++ ++ switch(condition) { ++ case AST_CONTROL_RINGING: ++ return -1; ++ default: ++ ast_log(LOG_WARNING, "Don't know how to condition %d\n", condition); ++ break; ++ } ++ return -1; ++} ++ ++static int ++blt_answer(struct ast_channel * ast) ++{ ++ blt_dev_t * dev = ast->pvt->pvt; ++ ++ ast_mutex_lock(&dev->lock); ++ ++ // if (dev->ring_timer >= 0) ++ // ast_sched_del(sched, dev->ring_timer); ++ // dev->ring_timer = -1; ++ ++ ast_log(LOG_DEBUG, "Answering interface\n"); ++ ++ if (ast->_state != AST_STATE_UP) { ++ send_atcmd(dev, "+CIEV: 2,1"); ++ send_atcmd(dev, "+CIEV: 3,0"); ++ sco_start(dev, -1); ++ ast_setstate(ast, AST_STATE_UP); ++ } ++ ++ ast_mutex_unlock(&dev->lock); ++ ++ return 0; ++} ++ ++static struct ast_channel * ++blt_new(blt_dev_t * dev, int state, const char * context, const char * number) ++{ ++ struct ast_channel * ast; ++ char c = 0; ++ ++ if ((ast = ast_channel_alloc(1)) == NULL) { ++ ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); ++ return NULL; ++ } ++ ++ snprintf(ast->name, sizeof(ast->name), "BLT/%s", dev->name); ++ ++ // ast->fds[0] = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); ++ ++ ast->nativeformats = BLUETOOTH_FORMAT; ++ ast->pvt->rawreadformat = BLUETOOTH_FORMAT; ++ ast->pvt->rawwriteformat = BLUETOOTH_FORMAT; ++ ast->writeformat = BLUETOOTH_FORMAT; ++ ast->readformat = BLUETOOTH_FORMAT; ++ ++ ast_setstate(ast, state); ++ ++ ast->type = BLT_CHAN_NAME; ++ ++ ast->pvt->pvt = dev; ++ ++ ast->pvt->call = blt_call; ++ ast->pvt->indicate = blt_indicate; ++ ast->pvt->hangup = blt_hangup; ++ ast->pvt->read = blt_read; ++ ast->pvt->write = blt_write; ++ ast->pvt->answer = blt_answer; ++ ++ strncpy(ast->context, context, sizeof(ast->context)-1); ++ strncpy(ast->exten, number, sizeof(ast->exten) - 1); ++ ++ ast->language[0] = '\0'; ++ ++ ast->fds[0] = dev->sco_pipe[0]; ++ write(dev->sco_pipe[1], &c, 1); ++ ++ dev->owner = ast; ++ ++ ast_mutex_lock(&usecnt_lock); ++ usecnt++; ++ ast_mutex_unlock(&usecnt_lock); ++ ++ ast_update_use_count(); ++ ++ if (state != AST_STATE_DOWN) { ++ if (ast_pbx_start(ast)) { ++ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast->name); ++ ast_hangup(ast); ++ } ++ } ++ ++ return ast; ++} ++ ++static struct ast_channel * ++#if (ASTERISK_VERSION_NUM < 010100) ++blt_request(char * type, int format, void * local_data) ++#else ++blt_request(const char * type, int format, void * local_data) ++#endif ++{ ++ char * data = (char*)local_data; ++ int oldformat; ++ blt_dev_t * dev = NULL; ++ struct ast_channel * ast = NULL; ++ char * number = data, * dname; ++ ++ dname = strsep(&number, "/"); ++ ++ oldformat = format; ++ ++ format &= BLUETOOTH_FORMAT; ++ ++ if (!format) { ++ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", oldformat); ++ return NULL; ++ } ++ ++ ast_log(LOG_DEBUG, "Dialing '%s' via '%s'\n", number, dname); ++ ++ if (ast_mutex_lock(&iface_lock)) { ++ ast_log(LOG_ERROR, "Unable to lock iface_list\n"); ++ return NULL; ++ } ++ ++ dev = iface_head; ++ ++ while (dev) { ++ if (strcmp(dev->name, dname) == 0) { ++ ast_mutex_lock(&(dev->lock)); ++ if (!dev->ready) { ++ ast_log(LOG_ERROR, "Device %s is not connected\n", dev->name); ++ ast_mutex_unlock(&(dev->lock)); ++ ast_mutex_unlock(&iface_lock); ++ return NULL; ++ } ++ break; ++ } ++ dev = dev->next; ++ } ++ ++ ast_mutex_unlock(&iface_lock); ++ ++ if (!dev) { ++ ast_log(LOG_WARNING, "Failed to find device named '%s'\n", dname); ++ return NULL; ++ } ++ ++ if (number && dev->role != BLT_ROLE_AG) { ++ ast_log(LOG_WARNING, "Tried to send a call out on non AG\n"); ++ ast_mutex_unlock(&(dev->lock)); ++ return NULL; ++ } ++ ++ if (dev->role == BLT_ROLE_AG) ++ strncpy(dev->dnid, number, sizeof(dev->dnid) - 1); ++ ++ ast = blt_new(dev, AST_STATE_DOWN, "bluetooth", "s"); ++ ++ ast_mutex_unlock(&(dev->lock)); ++ ++ return ast; ++} ++ ++/* ---------------------------------- */ ++ ++ ++/* ---- AT COMMAND SOCKET STUFF ---- */ ++ ++static int ++send_atcmd(blt_dev_t * dev, const char * fmt, ...) ++{ ++ char buf[1024]; ++ va_list ap; ++ int len; ++ ++ va_start(ap, fmt); ++ len = vsnprintf(buf, 1023, fmt, ap); ++ va_end(ap); ++ ++ if (option_verbose) ++ ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s < %s\n", role2str(dev->role), 10, dev->name, buf); ++ ++ write(dev->rd, "\r\n", 2); ++ len = write(dev->rd, buf, len); ++ write(dev->rd, "\r\n", 2); ++ return (len) ? 0 : -1; ++} ++ ++ ++static int ++send_atcmd_ok(blt_dev_t * dev, const char * cmd) ++{ ++ int len; ++ strncpy(dev->last_ok_cmd, cmd, BLT_RDBUFF_MAX - 1); ++ if (option_verbose) ++ ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s < OK\n", role2str(dev->role), 10, dev->name); ++ len = write(dev->rd, "\r\nOK\r\n", 6); ++ return (len) ? 0 : -1; ++} ++ ++static int ++send_atcmd_error(blt_dev_t * dev) ++{ ++ int len; ++ ++ if (option_verbose) ++ ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s < ERROR\n", role2str(dev->role), 10, dev->name); ++ ++// write(dev->rd, "\r\n", 2); ++// len = write(dev->rd, dev->last_ok_cmd, 5); ++ write(dev->rd, "\r\n", 2); ++ len = write(dev->rd, "ERROR", 5); ++ write(dev->rd, "\r\n", 2); ++ ++ return (len) ? 0 : -1; ++} ++ ++ ++/* ---------------------------------- */ ++ ++/* -- Handle negotiation when we're an AG -- */ ++ ++/* Bluetooth Support */ ++ ++static int ++atcmd_brsf_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ ast_log(LOG_DEBUG, "Device Supports: %s\n", arg); ++ dev->brsf = atoi(arg); ++ send_atcmd(dev, "+BRSF: %d", 23); ++ return 0; ++} ++ ++/* Bluetooth Voice Recognition */ ++ ++static int ++atcmd_bvra_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ ast_log(LOG_WARNING, "+BVRA Not Yet Supported\n"); ++ return -1; ++#if 0 ++ // XXX:T: Fix voice recognition somehow! ++ int action = atoi(arg); ++ ast_log(LOG_DEBUG, "Voice Recognition: %s\n", (a) ? "ACTIVATED" : "DEACTIVATED"); ++ if ((action == 0) & (dev->bvra == 1)) { ++ /* Disable it */ ++ dev->bvra = 0; ++ // XXX:T: Shutdown any active bvra channel ++ ast_log(LOG_DEBUG, "Voice Recognition: DISABLED\n"); ++ } else if ((action == 1) && (dev->bvra == 0)) { ++ /* Enable it */ ++ dev->bvra = 1; ++ // XXX:T: Schedule connection to voice recognition extension/application ++ ast_log(LOG_DEBUG, "Voice Recognition: ENABLED\n"); ++ } else { ++ ast_log(LOG_ERROR, "+BVRA out of sync (we think %d, but HS wants %d)\n", dev->bvra, action); ++ return -1; ++ } ++ return 0; ++#endif ++} ++ ++/* Clock */ ++ ++static int ++atcmd_cclk_read(blt_dev_t * dev) ++{ ++ struct tm t, *tp; ++ const time_t ti = time(0); ++ tp = localtime_r(&ti, &t); ++ send_atcmd(dev, "+CCLK: \"%02d/%02d/%02d,%02d:%02d:%02d+%02d\"", ++ (tp->tm_year % 100), (tp->tm_mon + 1), (tp->tm_mday), ++ tp->tm_hour, tp->tm_min, tp->tm_sec, ((tp->tm_gmtoff / 60) / 15)); ++ return 0; ++} ++ ++/* CHUP - Hangup Call */ ++ ++static int ++atcmd_chup_execute(blt_dev_t * dev, const char * data) ++{ ++ if (!dev->owner) { ++ ast_log(LOG_ERROR, "Request to hangup call when none in progress\n"); ++ return -1; ++ } ++ ast_log(LOG_DEBUG, "Hangup Call\n"); ++ ast_queue_control(dev->owner, AST_CONTROL_HANGUP); ++ return 0; ++} ++ ++/* CIND - Call Indicator */ ++ ++static int ++atcmd_cind_read(blt_dev_t * dev) ++{ ++ send_atcmd(dev, "+CIND: 1,0,0"); ++ return 0; ++} ++ ++static int ++atcmd_cind_test(blt_dev_t * dev) ++{ ++ send_atcmd(dev, "+CIND: (\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-4))"); ++ return 0; ++} ++ ++/* Set Language */ ++ ++static int ++atcmd_clan_read(blt_dev_t * dev) ++{ ++ send_atcmd(dev, "+CLAN: \"en\""); ++ return 0; ++} ++ ++/* Caller Id Presentation */ ++ ++static int ++atcmd_clip_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ dev->clip = atoi(arg); ++ return 0; ++} ++ ++/* Conneced Line Identification Presentation */ ++ ++static int ++atcmd_colp_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ dev->colp = atoi(arg); ++ return 0; ++} ++ ++/* CMER - Mobile Equipment Event Reporting */ ++ ++static int ++atcmd_cmer_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ dev->ready = 1; ++ dev->status = BLT_STATUS_READY; ++ return 0; ++} ++ ++/* PhoneBook Types: ++ * ++ * - FD - SIM Fixed Dialing Phone Book ++ * - ME - ME Phone book ++ * - SM - SIM Phone Book ++ * - DC - ME dialled-calls list ++ * - RC - ME recieved-calls lisr ++ * - MC - ME missed-calls list ++ * - MV - ME Voice Activated Dialing List ++ * - HP - Hierachial Phone Book ++ * - BC - Own Business Card (PIN2 required) ++ * ++ */ ++ ++/* Read Phone Book Entry */ ++ ++static int ++atcmd_cpbr_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ // XXX:T: Fix the phone book! ++ // * Maybe add res_phonebook or something? */ ++ send_atcmd(dev, "+CPBR: %d,\"%s\",128,\"%s\"", atoi(arg), arg, arg); ++ return 0; ++} ++ ++/* Select Phone Book */ ++ ++static int ++atcmd_cpbs_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ // XXX:T: I guess we'll just accept any? ++ return 0; ++} ++ ++static int ++atcmd_cscs_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ // XXX:T: Language ++ return 0; ++} ++ ++static int ++atcmd_eips_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ ast_log(LOG_DEBUG, "Identify Presentation Set: %s=%s\n", ++ (*(arg) == 49) ? "ELIP" : "EOLP", ++ (*(arg+2) == 49) ? "ON" : "OFF"); ++ ++ if (*(arg) == 49) ++ dev->eolp = (*(arg+2) == 49) ? 1 : 0; ++ else ++ dev->elip = (*(arg+2) == 49) ? 1 : 0; ++ ++ return 0; ++} ++ ++/* VGS - Speaker Volume Gain */ ++ ++static int ++atcmd_vgs_set(blt_dev_t * dev, const char * arg, int len) ++{ ++ dev->gain_speaker = atoi(arg); ++ return 0; ++} ++ ++/* Dial */ ++static int ++atcmd_dial_execute(blt_dev_t * dev, const char * data) ++{ ++ char * number = NULL; ++ ++ /* Make sure there is a ';' at the end of the line */ ++ if (*(data + (strlen(data) - 1)) != ';') { ++ ast_log(LOG_WARNING, "Can't dial non-voice right now: %s\n", data); ++ return -1; ++ } ++ ++ number = strndup(data, strlen(data) - 1); ++ ast_log(LOG_NOTICE, "Dial: [%s]\n", number); ++ ++ send_atcmd(dev, "+CIEV: 2,1"); ++ send_atcmd(dev, "+CIEV: 3,0"); ++ ++ sco_start(dev, -1); ++ ++ if (blt_new(dev, AST_STATE_UP, "bluetooth", number) == NULL) { ++ sco_stop(dev); ++ } ++ ++ free(number); ++ ++ return 0; ++} ++ ++/* Answer */ ++ ++static int ++atcmd_answer_execute(blt_dev_t * dev, const char * data) ++{ ++ ++ if (!dev->ringing || !dev->owner) { ++ ast_log(LOG_WARNING, "Can't answer non existant call\n"); ++ return -1; ++ } ++ ++ dev->ringing = 0; ++ ++ if (dev->ring_timer >= 0) ++ ast_sched_del(sched, dev->ring_timer); ++ ++ dev->ring_timer = -1; ++ ++ send_atcmd(dev, "+CIEV: 2,1"); ++ send_atcmd(dev, "+CIEV: 3,0"); ++ ++ return answer(dev); ++} ++ ++static int ++ag_unsol_ciev(blt_dev_t * dev, const char * data) ++{ ++ const char * orig = data; ++ int indicator; ++ int status; ++ ++ while (*(data) && *(data) == ' ') ++ data++; ++ ++ if (*(data) == 0) { ++ ast_log(LOG_WARNING, "Invalid value[1] for '+CIEV:%s'\n", orig); ++ return -1; ++ } ++ ++ indicator = *(data++) - 48; ++ ++ if (*(data++) != ',') { ++ ast_log(LOG_WARNING, "Invalid value[2] for '+CIEV:%s'\n", orig); ++ return -1; ++ } ++ ++ if (*(data) == 0) { ++ ast_log(LOG_WARNING, "Invalid value[3] for '+CIEV:%s'\n", orig); ++ return -1; ++ } ++ ++ status = *(data) - 48; ++ ++ set_cind(dev, indicator, status); ++ ++ return 0; ++} ++ ++static int ++ag_unsol_cind(blt_dev_t * dev, const char * data) ++{ ++ ++ while (*(data) && *(data) == ' ') ++ data++; ++ ++ ++ if (dev->cind == 0) ++ { ++ int pos = 1; ++ char name[1024]; ++ ++ while ((data = parse_cind(data, name, 1023)) != NULL) { ++ ast_log(LOG_DEBUG, "CIND: %d=%s\n", pos, name); ++ if (strcmp(name, "call") == 0) ++ dev->call_pos = pos; ++ else if (strcmp(name, "service") == 0) ++ dev->service_pos = pos; ++ else if (strcmp(name, "call_setup") == 0 || strcmp(name, "callsetup") == 0) ++ dev->callsetup_pos = pos; ++ pos++; ++ } ++ ++ ast_log(LOG_DEBUG, "CIND: %d=%s\n", pos, name); ++ ++ } else { ++ ++ int pos = 1, len = 0; ++ char val[128]; ++ const char * start = data; ++ ++ while (*data) { ++ if (*data == ',') { ++ memset(val, 0, 128); ++ strncpy(val, start, len); ++ set_cind(dev, pos, atoi(val)); ++ pos++; ++ len = 0; ++ data++; ++ start = data; ++ continue; ++ } ++ len++; ++ data++; ++ } ++ ++ memset(val, 0, 128); ++ strncpy(val, start, len); ++ ast_log(LOG_DEBUG, "CIND IND %d set to %d [%s]\n", pos, atoi(val), val); ++ ++ ++ } ++ ++ return 0; ++} ++ ++static blt_atcb_t ++atcmd_list[] = ++{ ++ { "A", NULL, NULL, atcmd_answer_execute, NULL, NULL }, ++ { "D", NULL, NULL, atcmd_dial_execute, NULL, NULL }, ++ { "+BRSF", atcmd_brsf_set, NULL, NULL, NULL, NULL }, ++ { "+BVRA", atcmd_bvra_set, NULL, NULL, NULL, NULL }, ++ { "+CCLK", NULL, atcmd_cclk_read, NULL, NULL, NULL }, ++ { "+CHUP", NULL, NULL, atcmd_chup_execute, NULL, NULL }, ++ { "+CIEV", NULL, NULL, NULL, NULL, ag_unsol_ciev }, ++ { "+CIND", NULL, atcmd_cind_read, NULL, atcmd_cind_test, ag_unsol_cind }, ++ { "+CLAN", NULL, atcmd_clan_read, NULL, NULL, NULL }, ++ { "+CLIP", atcmd_clip_set, NULL, NULL, NULL, NULL }, ++ { "+COLP", atcmd_colp_set, NULL, NULL, NULL, NULL }, ++ { "+CMER", atcmd_cmer_set, NULL, NULL, NULL, NULL }, ++ { "+CPBR", atcmd_cpbr_set, NULL, NULL, NULL, NULL }, ++ { "+CPBS", atcmd_cpbs_set, NULL, NULL, NULL, NULL }, ++ { "+CSCS", atcmd_cscs_set, NULL, NULL, NULL, NULL }, ++ { "*EIPS", atcmd_eips_set, NULL, NULL, NULL, NULL }, ++ { "+VGS", atcmd_vgs_set, NULL, NULL, NULL, NULL }, ++}; ++ ++#define ATCMD_LIST_LEN (sizeof(atcmd_list) / sizeof(blt_atcb_t)) ++ ++/* ---------------------------------- */ ++ ++/* -- Handle negotiation when we're a HS -- */ ++ ++void ++ag_unknown_response(blt_dev_t * dev, char * cmd) ++{ ++ ast_log(LOG_DEBUG, "Got UNKN response: %s\n", cmd); ++ ++ // DELAYED ++ // NO CARRIER ++ ++} ++ ++void ++ag_cgmi_response(blt_dev_t * dev, char * cmd) ++{ ++ // CGMM - Phone Model ++ // CGMR - Phone Revision ++ // CGSN - IMEI ++ // AT* ++ // VTS - send tone ++ // CREG ++ // CBC - BATTERY ++ // CSQ - SIGANL ++ // CSMS - SMS STUFFS ++ // CMGL ++ // CMGR ++ // CMGS ++ // CSCA - sms CENTER NUMBER ++ // CNMI - SMS INDICATION ++ // ast_log(LOG_DEBUG, "Manufacturer: %s\n", cmd); ++ dev->cb = ag_unknown_response; ++} ++ ++void ++ag_cgmi_valid_response(blt_dev_t * dev, char * cmd) ++{ ++ // send_atcmd(dev, "AT+WS46?"); ++ // send_atcmd(dev, "AT+CRC=1"); ++ // send_atcmd(dev, "AT+CNUM"); ++ ++ if (strcmp(cmd, "OK") == 0) { ++ send_atcmd(dev, "AT+CGMI"); ++ dev->cb = ag_cgmi_response; ++ } else { ++ dev->cb = ag_unknown_response; ++ } ++} ++ ++void ++ag_clip_response(blt_dev_t * dev, char * cmd) ++{ ++ send_atcmd(dev, "AT+CGMI=?"); ++ dev->cb = ag_cgmi_valid_response; ++} ++ ++void ++ag_cmer_response(blt_dev_t * dev, char * cmd) ++{ ++ dev->cb = ag_clip_response; ++ dev->ready = 1; ++ dev->status = BLT_STATUS_READY; ++ send_atcmd(dev, "AT+CLIP=1"); ++} ++ ++void ++ag_cind_status_response(blt_dev_t * dev, char * cmd) ++{ ++ // XXX:T: Handle response. ++ dev->cb = ag_cmer_response; ++ send_atcmd(dev, "AT+CMER=3,0,0,1"); ++ // Initiase SCO link! ++} ++ ++void ++ag_cind_response(blt_dev_t * dev, char * cmd) ++{ ++ dev->cb = ag_cind_status_response; ++ dev->cind = 1; ++ send_atcmd(dev, "AT+CIND?"); ++} ++ ++void ++ag_brsf_response(blt_dev_t * dev, char * cmd) ++{ ++ dev->cb = ag_cind_response; ++ ast_log(LOG_DEBUG, "Bluetooth features: %s\n", cmd); ++ dev->cind = 0; ++ send_atcmd(dev, "AT+CIND=?"); ++} ++ ++/* ---------------------------------- */ ++ ++static int ++sdp_register(sdp_session_t * session) ++{ ++ // XXX:T: Fix this horrible function so it makes some sense and is extensible! ++ sdp_list_t *svclass_id, *pfseq, *apseq, *root; ++ uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid; ++ sdp_profile_desc_t profile; ++ sdp_list_t *aproto, *proto[2]; ++ sdp_record_t record; ++ uint8_t u8 = rfcomm_channel_ag; ++ uint8_t u8_hs = rfcomm_channel_hs; ++ sdp_data_t *channel; ++ int ret = 0; ++ ++ memset((void *)&record, 0, sizeof(sdp_record_t)); ++ record.handle = 0xffffffff; ++ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); ++ root = sdp_list_append(0, &root_uuid); ++ sdp_set_browse_groups(&record, root); ++ ++ // Register as an AG ++ ++ sdp_uuid16_create(&svclass_uuid, HANDSFREE_AUDIO_GW_SVCLASS_ID); ++ svclass_id = sdp_list_append(0, &svclass_uuid); ++ sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); ++ svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); ++ sdp_set_service_classes(&record, svclass_id); ++ sdp_uuid16_create(&profile.uuid, 0x111f); ++ profile.version = 0x0100; ++ pfseq = sdp_list_append(0, &profile); ++ ++ sdp_set_profile_descs(&record, pfseq); ++ ++ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); ++ proto[0] = sdp_list_append(0, &l2cap_uuid); ++ apseq = sdp_list_append(0, proto[0]); ++ ++ sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); ++ proto[1] = sdp_list_append(0, &rfcomm_uuid); ++ channel = sdp_data_alloc(SDP_UINT8, &u8); ++ proto[1] = sdp_list_append(proto[1], channel); ++ apseq = sdp_list_append(apseq, proto[1]); ++ ++ aproto = sdp_list_append(0, apseq); ++ sdp_set_access_protos(&record, aproto); ++ ++ sdp_set_info_attr(&record, "Voice Gateway", 0, 0); ++ ++ if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { ++ ast_log(LOG_ERROR, "Service Record registration failed\n"); ++ ret = -1; ++ goto end; ++ } ++ ++ sdp_record_ag = record.handle; ++ ++ ast_log(LOG_NOTICE, "HeadsetAudioGateway service registered\n"); ++ ++ sdp_data_free(channel); ++ sdp_list_free(proto[0], 0); ++ sdp_list_free(proto[1], 0); ++ sdp_list_free(apseq, 0); ++ sdp_list_free(aproto, 0); ++ ++ // ------------- ++ ++ memset((void *)&record, 0, sizeof(sdp_record_t)); ++ record.handle = 0xffffffff; ++ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); ++ root = sdp_list_append(0, &root_uuid); ++ sdp_set_browse_groups(&record, root); ++ ++ // Register as an HS ++ ++ sdp_uuid16_create(&svclass_uuid, HANDSFREE_AUDIO_GW_SVCLASS_ID); ++ svclass_id = sdp_list_append(0, &svclass_uuid); ++ sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); ++ svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); ++ sdp_set_service_classes(&record, svclass_id); ++ sdp_uuid16_create(&profile.uuid, 0x111e); ++ profile.version = 0x0100; ++ pfseq = sdp_list_append(0, &profile); ++ sdp_set_profile_descs(&record, pfseq); ++ ++ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); ++ proto[0] = sdp_list_append(0, &l2cap_uuid); ++ apseq = sdp_list_append(0, proto[0]); ++ ++ sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); ++ proto[1] = sdp_list_append(0, &rfcomm_uuid); ++ channel = sdp_data_alloc(SDP_UINT8, &u8_hs); ++ proto[1] = sdp_list_append(proto[1], channel); ++ apseq = sdp_list_append(apseq, proto[1]); ++ ++ aproto = sdp_list_append(0, apseq); ++ sdp_set_access_protos(&record, aproto); ++ sdp_set_info_attr(&record, "Voice Gateway", 0, 0); ++ ++ if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) { ++ ast_log(LOG_ERROR, "Service Record registration failed\n"); ++ ret = -1; ++ goto end; ++ } ++ ++ sdp_record_hs = record.handle; ++ ++ ast_log(LOG_NOTICE, "HeadsetAudioGateway service registered\n"); ++ ++end: ++ sdp_data_free(channel); ++ sdp_list_free(proto[0], 0); ++ sdp_list_free(proto[1], 0); ++ sdp_list_free(apseq, 0); ++ sdp_list_free(aproto, 0); ++ ++ return ret; ++} ++ ++static int ++rfcomm_listen(bdaddr_t * bdaddr, int channel) ++{ ++ ++ int sock = -1; ++ struct sockaddr_rc loc_addr; ++ int on = 1; ++ ++ if ((sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { ++ ast_log(LOG_ERROR, "Can't create socket: %s (errno: %d)\n", strerror(errno), errno); ++ return -1; ++ } ++ ++ loc_addr.rc_family = AF_BLUETOOTH; ++ ++ /* Local Interface Address */ ++ bacpy(&loc_addr.rc_bdaddr, bdaddr); ++ ++ /* Channel */ ++ loc_addr.rc_channel = channel; ++ ++ if (bind(sock, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) { ++ ast_log(LOG_ERROR, "Can't bind socket: %s (errno: %d)\n", strerror(errno), errno); ++ close(sock); ++ return -1; ++ } ++ ++ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { ++ ast_log(LOG_ERROR, "Set socket SO_REUSEADDR option on failed: errno %d, %s", errno, strerror(errno)); ++ close(sock); ++ return -1; ++ } ++ ++ if (fcntl(sock, F_SETFL, O_RDWR|O_NONBLOCK) != 0) ++ ast_log(LOG_ERROR, "Can't set RFCOMM socket to NBIO\n"); ++ ++ if (listen(sock, 10) < 0) { ++ ast_log(LOG_ERROR,"Can not listen on the socket. %s(%d)\n", strerror(errno), errno); ++ close(sock); ++ return -1; ++ } ++ ++ ast_log(LOG_NOTICE, "Listening for RFCOMM channel %d connections on FD %d\n", channel, sock); ++ ++ return sock; ++} ++ ++ ++static int ++sco_listen(bdaddr_t * bdaddr) ++{ ++ int sock = -1; ++ int on = 1; ++ struct sockaddr_sco loc_addr; ++ ++ memset(&loc_addr, 0, sizeof(loc_addr)); ++ ++ if ((sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { ++ ast_log(LOG_ERROR, "Can't create SCO socket: %s (errno: %d)\n", strerror(errno), errno); ++ return -1; ++ } ++ ++ loc_addr.sco_family = AF_BLUETOOTH; ++ bacpy(&loc_addr.sco_bdaddr, BDADDR_ANY); ++ ++ if (bind(sock, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) { ++ ast_log(LOG_ERROR, "Can't bind SCO socket: %s (errno: %d)\n", strerror(errno), errno); ++ close(sock); ++ return -1; ++ } ++ ++ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { ++ ast_log(LOG_ERROR, "Set SCO socket SO_REUSEADDR option on failed: errno %d, %s", errno, strerror(errno)); ++ close(sock); ++ return -1; ++ } ++ ++ if (fcntl(sock, F_SETFL, O_RDWR|O_NONBLOCK) != 0) ++ ast_log(LOG_ERROR, "Can't set SCO socket to NBIO\n"); ++ ++ if (listen(sock, 10) < 0) { ++ ast_log(LOG_ERROR,"Can not listen on SCO socket: %s(%d)\n", strerror(errno), errno); ++ close(sock); ++ return -1; ++ } ++ ++ ast_log(LOG_NOTICE, "Listening for SCO connections on FD %d\n", sock); ++ ++ return sock; ++} ++ ++static int ++rfcomm_connect(bdaddr_t * src, bdaddr_t * dst, int channel, int nbio) ++{ ++ struct sockaddr_rc addr; ++ int s; ++ ++ if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { ++ return -1; ++ } ++ ++ memset(&addr, 0, sizeof(addr)); ++ addr.rc_family = AF_BLUETOOTH; ++ bacpy(&addr.rc_bdaddr, src); ++ addr.rc_channel = 0; ++ ++ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { ++ close(s); ++ return -1; ++ } ++ ++ memset(&addr, 0, sizeof(addr)); ++ addr.rc_family = AF_BLUETOOTH; ++ bacpy(&addr.rc_bdaddr, dst); ++ addr.rc_channel = channel; ++ ++ if (nbio) { ++ if (fcntl(s, F_SETFL, O_RDWR|O_NONBLOCK) != 0) ++ ast_log(LOG_ERROR, "Can't set RFCOMM socket to NBIO\n"); ++ } ++ ++ if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 && (nbio != 1 || (errno != EAGAIN))) { ++ close(s); ++ return -1; ++ } ++ ++ return s; ++} ++ ++/* Must be called with dev->lock held */ ++ ++static int ++sco_connect(blt_dev_t * dev) ++{ ++ struct sockaddr_sco addr; ++ // struct sco_conninfo conn; ++ // struct sco_options opts; ++ // int size; ++ // bdaddr_t * src = &local_bdaddr; ++ ++ int s; ++ bdaddr_t * dst = &(dev->bdaddr); ++ ++ if (dev->sco != -1) { ++ ast_log(LOG_ERROR, "SCO fd already open.\n"); ++ return -1; ++ } ++ ++ if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { ++ ast_log(LOG_ERROR, "Can't create SCO socket(): %s\n", strerror(errno)); ++ return -1; ++ } ++ ++ memset(&addr, 0, sizeof(addr)); ++ ++ addr.sco_family = AF_BLUETOOTH; ++ bacpy(&addr.sco_bdaddr, BDADDR_ANY); ++ ++ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { ++ ast_log(LOG_ERROR, "Can't bind() SCO socket: %s\n", strerror(errno)); ++ close(s); ++ return -1; ++ } ++ ++ memset(&addr, 0, sizeof(addr)); ++ addr.sco_family = AF_BLUETOOTH; ++ bacpy(&addr.sco_bdaddr, dst); ++ ++ if (fcntl(s, F_SETFL, O_RDWR|O_NONBLOCK) != 0) ++ ast_log(LOG_ERROR, "Can't set SCO socket to NBIO\n"); ++ ++ if ((connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) && (errno != EAGAIN)) { ++ ast_log(LOG_ERROR, "Can't connect() SCO socket: %s (errno %d)\n", strerror(errno), errno); ++ close(s); ++ return -1; ++ } ++ ++ //size = sizeof(conn); ++ ++ ++/* XXX:T: HERE, fix getting SCO conninfo. ++ ++ if (getsockopt(s, SOL_SCO, SCO_CONNINFO, &conn, &size) < 0) { ++ ast_log(LOG_ERROR, "Can't getsockopt SCO_CONNINFO on SCO socket: %s\n", strerror(errno)); ++ close(s); ++ return -1; ++ } ++ ++ size = sizeof(opts); ++ ++ if (getsockopt(s, SOL_SCO, SCO_OPTIONS, &opts, &size) < 0) { ++ ast_log(LOG_ERROR, "Can't getsockopt SCO_OPTIONS on SCO socket: %s\n", strerror(errno)); ++ close(s); ++ return -1; ++ } ++ ++ dev->sco_handle = conn.hci_handle; ++ dev->sco_mtu = opts.mtu; ++ ++*/ ++ ++ ast_log(LOG_DEBUG, "SCO: %d\n", s); ++ ++ dev->sco = s; ++ ++ return 0; ++} ++ ++ ++/* ---------------------------------- */ ++ ++/* Non blocking (async) outgoing bluetooth connection */ ++ ++static int ++try_connect(blt_dev_t * dev) ++{ ++ int fd; ++ ast_mutex_lock(&(dev->lock)); ++ ++ if (dev->status != BLT_STATUS_CONNECTING && dev->status != BLT_STATUS_DOWN) { ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++ } ++ ++ if (dev->rd != -1) { ++ ++ int ret; ++ struct pollfd pfd; ++ ++ if (dev->status != BLT_STATUS_CONNECTING) { ++ ast_mutex_unlock(&(dev->lock)); ++ dev->outgoing_id = -1; ++ return 0; ++ } ++ ++ // ret = connect(dev->rd, (struct sockaddr *)&(dev->addr), sizeof(struct sockaddr_rc)); // ++ ++ pfd.fd = dev->rd; ++ pfd.events = POLLIN | POLLOUT; ++ ++ ret = poll(&pfd, 1, 0); ++ ++ if (ret == -1) { ++ close(dev->rd); ++ dev->rd = -1; ++ dev->status = BLT_STATUS_DOWN; ++ dev->outgoing_id = ast_sched_add(sched, 10000, AST_SCHED_CB(try_connect), dev); ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++ } ++ ++ if (ret > 0) { ++ ++ int len = sizeof(ret); ++ getsockopt(dev->rd, SOL_SOCKET, SO_ERROR, &ret, &len); ++ ++ if (ret == 0) { ++ ++ ast_log(LOG_NOTICE, "Initialised bluetooth link to device %s\n", dev->name); ++ ++#if 0 ++ { ++ struct hci_conn_info_req * cr; ++ int dd; ++ char name[248]; ++ ++ cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); ++ dd = hci_open_dev(hcidev_id); ++ cr->type = ACL_LINK; ++ bacpy(&cr->bdaddr, &(dev->bdaddr)); ++ ++ if (ioctl(dd, HCIGETCONNINFO, (unsigned long)cr) < 0) { ++ ast_log(LOG_ERROR, "Failed to get connection info: %s\n", strerror(errno)); ++ } else { ++ ast_log(LOG_DEBUG, "HCI Handle: %d\n", cr->conn_info->handle); ++ } ++ ++ if (hci_read_remote_name(dd, &(dev->bdaddr), sizeof(name), name, 25000) == 0) ++ ast_log(LOG_DEBUG, "Remote Name: %s\n", name); ++ free(cr); ++ } ++#endif ++ ++ dev->status = BLT_STATUS_NEGOTIATING; ++ ++ /* If this device is a AG, we initiate the negotiation. */ ++ ++ if (dev->role == BLT_ROLE_AG) { ++ dev->cb = ag_brsf_response; ++ send_atcmd(dev, "AT+BRSF=23"); ++ } ++ ++ dev->outgoing_id = -1; ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++ ++ } else { ++ ++ if (ret != EHOSTDOWN) ++ ast_log(LOG_NOTICE, "Connect to device %s failed: %s (errno %d)\n", dev->name, strerror(ret), ret); ++ ++ close(dev->rd); ++ dev->rd = -1; ++ dev->status = BLT_STATUS_DOWN; ++ dev->outgoing_id = ast_sched_add(sched, (ret == EHOSTDOWN) ? 10000 : 2500, AST_SCHED_CB(try_connect), dev); ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++ ++ } ++ ++ } ++ ++ dev->outgoing_id = ast_sched_add(sched, 100, AST_SCHED_CB(try_connect), dev); ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++ } ++ ++ fd = rfcomm_connect(&local_bdaddr, &(dev->bdaddr), dev->channel, 1); ++ ++ if (fd == -1) { ++ ast_log(LOG_WARNING, "NBIO connect() to %s returned %d: %s\n", dev->name, errno, strerror(errno)); ++ dev->outgoing_id = ast_sched_add(sched, 5000, AST_SCHED_CB(try_connect), dev); ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++ } ++ ++ dev->rd = fd; ++ dev->status = BLT_STATUS_CONNECTING; ++ dev->outgoing_id = ast_sched_add(sched, 100, AST_SCHED_CB(try_connect), dev); ++ ast_mutex_unlock(&(dev->lock)); ++ return 0; ++} ++ ++ ++/* Called whenever a new command is recieved while we're the AG */ ++ ++ ++static int ++process_rfcomm_cmd(blt_dev_t * dev, char * cmd) ++{ ++ int i; ++ char * fullcmd = cmd; ++ ++ if (option_verbose) ++ ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, cmd); ++ ++ /* Read the 'AT' from the start of the string */ ++ if (strncmp(cmd, "AT", 2)) { ++ ast_log(LOG_WARNING, "Unknown command without 'AT': %s\n", cmd); ++ send_atcmd_error(dev); ++ return 0; ++ } ++ ++ cmd += 2; ++ ++ // Don't forget 'AT' on it's own is OK. ++ ++ if (strlen(cmd) == 0) { ++ send_atcmd_ok(dev, fullcmd); ++ return 0; ++ } ++ ++ for (i = 0 ; i < ATCMD_LIST_LEN ; i++) { ++ if (strncmp(atcmd_list[i].str, cmd, strlen(atcmd_list[i].str)) == 0) { ++ char * pos = (cmd + strlen(atcmd_list[i].str)); ++ if ((strncmp(pos, "=?", 2) == 0) && (strlen(pos) == 2)) { ++ /* TEST command */ ++ if (atcmd_list[i].test) { ++ if (atcmd_list[i].test(dev) == 0) ++ send_atcmd_ok(dev, fullcmd); ++ else ++ send_atcmd_error(dev); ++ } else { ++ send_atcmd_ok(dev, fullcmd); ++ } ++ } else if ((strncmp(pos, "?", 1) == 0) && (strlen(pos) == 1)) { ++ /* READ command */ ++ if (atcmd_list[i].read) { ++ if (atcmd_list[i].read(dev) == 0) ++ send_atcmd_ok(dev, fullcmd); ++ else ++ send_atcmd_error(dev); ++ } else { ++ ast_log(LOG_WARNING, "AT Command: '%s' missing READ function\n", fullcmd); ++ send_atcmd_error(dev); ++ } ++ } else if (strncmp(pos, "=", 1) == 0) { ++ /* SET command */ ++ if (atcmd_list[i].set) { ++ if (atcmd_list[i].set(dev, (pos + 1), (*(pos + 1)) ? strlen(pos + 1) : 0) == 0) ++ send_atcmd_ok(dev, fullcmd); ++ else ++ send_atcmd_error(dev); ++ } else { ++ ast_log(LOG_WARNING, "AT Command: '%s' missing SET function\n", fullcmd); ++ send_atcmd_error(dev); ++ } ++ } else { ++ /* EXECUTE command */ ++ if (atcmd_list[i].execute) { ++ if (atcmd_list[i].execute(dev, cmd + strlen(atcmd_list[i].str)) == 0) ++ send_atcmd_ok(dev, fullcmd); ++ else ++ send_atcmd_error(dev); ++ } else { ++ ast_log(LOG_WARNING, "AT Command: '%s' missing EXECUTE function\n", fullcmd); ++ send_atcmd_error(dev); ++ } ++ } ++ return 0; ++ } ++ } ++ ++ ast_log(LOG_WARNING, "Unknown AT Command: '%s' (%s)\n", fullcmd, cmd); ++ send_atcmd_error(dev); ++ ++ return 0; ++} ++ ++/* Called when a socket is incoming */ ++ ++static void ++handle_incoming(int fd, blt_role_t role) ++{ ++ blt_dev_t * dev; ++ struct sockaddr_rc addr; ++ int len = sizeof(addr); ++ ++ // Got a new incoming socket. ++ ast_log(LOG_DEBUG, "Incoming RFCOMM socket\n"); ++ ++ ast_mutex_lock(&iface_lock); ++ ++ fd = accept(fd, (struct sockaddr*)&addr, &len); ++ ++ dev = iface_head; ++ while (dev) { ++ if (bacmp(&(dev->bdaddr), &addr.rc_bdaddr) == 0) { ++ ast_log(LOG_DEBUG, "Connect from %s\n", dev->name); ++ ast_mutex_lock(&(dev->lock)); ++ /* Kill any outstanding connect attempt. */ ++ if (dev->outgoing_id > -1) { ++ ast_sched_del(sched, dev->outgoing_id); ++ dev->outgoing_id = -1; ++ } ++ ++ rd_close(dev, 0, 0); ++ ++ dev->status = BLT_STATUS_NEGOTIATING; ++ dev->rd = fd; ++ ++ if (dev->role == BLT_ROLE_AG) { ++ dev->cb = ag_brsf_response; ++ send_atcmd(dev, "AT+BRSF=23"); ++ } ++ ++ ast_mutex_unlock(&(dev->lock)); ++ break; ++ } ++ dev = dev->next; ++ } ++ ++ if (dev == NULL) { ++ ast_log(LOG_WARNING, "Connect from unknown device\n"); ++ close(fd); ++ } ++ ast_mutex_unlock(&iface_lock); ++ ++ return; ++} ++ ++static void ++handle_incoming_sco(int master) ++{ ++ ++ blt_dev_t * dev; ++ struct sockaddr_sco addr; ++ struct sco_conninfo conn; ++ struct sco_options opts; ++ int len = sizeof(addr); ++ int fd; ++ ++ ast_log(LOG_DEBUG, "Incoming SCO socket\n"); ++ ++ fd = accept(master, (struct sockaddr*)&addr, &len); ++ ++ if (fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK) != 0) { ++ ast_log(LOG_ERROR, "Can't set SCO socket to NBIO\n"); ++ close(fd); ++ return; ++ } ++ ++ len = sizeof(conn); ++ ++ if (getsockopt(fd, SOL_SCO, SCO_CONNINFO, &conn, &len) < 0) { ++ ast_log(LOG_ERROR, "Can't getsockopt SCO_CONNINFO on SCO socket: %s\n", strerror(errno)); ++ close(fd); ++ return; ++ } ++ ++ len = sizeof(opts); ++ ++ if (getsockopt(fd, SOL_SCO, SCO_OPTIONS, &opts, &len) < 0) { ++ ast_log(LOG_ERROR, "Can't getsockopt SCO_OPTIONS on SCO socket: %s\n", strerror(errno)); ++ close(fd); ++ return; ++ } ++ ++ ast_mutex_lock(&iface_lock); ++ dev = iface_head; ++ while (dev) { ++ if (bacmp(&(dev->bdaddr), &addr.sco_bdaddr) == 0) { ++ ast_log(LOG_DEBUG, "SCO Connect from %s\n", dev->name); ++ ast_mutex_lock(&(dev->lock)); ++ if (dev->sco_running != -1) { ++ ast_log(LOG_ERROR, "Incoming SCO socket, but SCO thread already running.\n"); ++ } else { ++ sco_start(dev, fd); ++ } ++ ast_mutex_unlock(&(dev->lock)); ++ break; ++ } ++ dev = dev->next; ++ } ++ ++ ast_mutex_unlock(&iface_lock); ++ ++ if (dev == NULL) { ++ ast_log(LOG_WARNING, "SCO Connect from unknown device\n"); ++ close(fd); ++ } else { ++ // XXX:T: We need to handle the fact we might have an outgoing connection attempt in progress. ++ ast_log(LOG_DEBUG, "SCO: %d, HCIHandle=%d, MUT=%d\n", fd, conn.hci_handle, opts.mtu); ++ } ++ ++ ++ ++ return; ++} ++ ++/* Called when there is data waiting on a socket */ ++ ++static int ++handle_rd_data(blt_dev_t * dev) ++{ ++ char c; ++ int ret; ++ ++ while ((ret = read(dev->rd, &c, 1)) == 1) { ++ ++ // log_buf[i++] = c; ++ ++ if (dev->role == BLT_ROLE_HS) { ++ ++ if (c == '\r') { ++ ret = process_rfcomm_cmd(dev, dev->rd_buff); ++ dev->rd_buff_pos = 0; ++ memset(dev->rd_buff, 0, BLT_RDBUFF_MAX); ++ return ret; ++ } ++ ++ if (dev->rd_buff_pos >= BLT_RDBUFF_MAX) ++ return 0; ++ ++ dev->rd_buff[dev->rd_buff_pos++] = c; ++ ++ } else if (dev->role == BLT_ROLE_AG) { ++ ++ switch (dev->state) { ++ ++ case BLT_STATE_WANT_R: ++ if (c == '\r') { ++ dev->state = BLT_STATE_WANT_N; ++ } else if (c == '+') { ++ dev->state = BLT_STATE_WANT_CMD; ++ dev->rd_buff[dev->rd_buff_pos++] = '+'; ++ } else { ++ ast_log(LOG_ERROR, "Device %s: Expected '\\r', got %d. state=BLT_STATE_WANT_R\n", dev->name, c); ++ return -1; ++ } ++ break; ++ ++ case BLT_STATE_WANT_N: ++ if (c == '\n') ++ dev->state = BLT_STATE_WANT_CMD; ++ else { ++ ast_log(LOG_ERROR, "Device %s: Expected '\\n', got %d. state=BLT_STATE_WANT_N\n", dev->name, c); ++ return -1; ++ } ++ break; ++ ++ case BLT_STATE_WANT_CMD: ++ if (c == '\r') ++ dev->state = BLT_STATE_WANT_N2; ++ else { ++ if (dev->rd_buff_pos >= BLT_RDBUFF_MAX) { ++ ast_log(LOG_ERROR, "Device %s: Buffer exceeded\n", dev->name); ++ return -1; ++ } ++ dev->rd_buff[dev->rd_buff_pos++] = c; ++ } ++ break; ++ ++ case BLT_STATE_WANT_N2: ++ if (c == '\n') { ++ ++ dev->state = BLT_STATE_WANT_R; ++ ++ if (dev->rd_buff[0] == '+') { ++ int i; ++ // find unsolicited ++ for (i = 0 ; i < ATCMD_LIST_LEN ; i++) { ++ if (strncmp(atcmd_list[i].str, dev->rd_buff, strlen(atcmd_list[i].str)) == 0) { ++ if (atcmd_list[i].unsolicited) ++ atcmd_list[i].unsolicited(dev, dev->rd_buff + strlen(atcmd_list[i].str) + 1); ++ else ++ ast_log(LOG_WARNING, "Device %s: Unhandled Unsolicited: %s\n", dev->name, dev->rd_buff); ++ break; ++ } ++ } ++ ++ if (option_verbose) ++ ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, dev->rd_buff); ++ ++ if (i == ATCMD_LIST_LEN) ++ ast_log(LOG_DEBUG, "Device %s: Got unsolicited message: %s\n", dev->name, dev->rd_buff); ++ ++ } else { ++ ++ if ( ++ strcmp(dev->rd_buff, "OK") != 0 && ++ strcmp(dev->rd_buff, "CONNECT") != 0 && ++ strcmp(dev->rd_buff, "RING") != 0 && ++ strcmp(dev->rd_buff, "NO CARRIER") != 0 && ++ strcmp(dev->rd_buff, "ERROR") != 0 && ++ strcmp(dev->rd_buff, "NO DIALTONE") != 0 && ++ strcmp(dev->rd_buff, "BUSY") != 0 && ++ strcmp(dev->rd_buff, "NO ANSWER") != 0 && ++ strcmp(dev->rd_buff, "DELAYED") != 0 ++ ){ ++ // It must be a multiline error ++ strncpy(dev->last_err_cmd, dev->rd_buff, 1023); ++ if (option_verbose) ++ ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, dev->rd_buff); ++ } else if (dev->cb) { ++ if (option_verbose) ++ ast_verbose(VERBOSE_PREFIX_1 "[%s] %*s > %s\n", role2str(dev->role), 10, dev->name, dev->rd_buff); ++ dev->cb(dev, dev->rd_buff); ++ } else { ++ ast_log(LOG_ERROR, "Device %s: Data on socket in HS mode, but no callback\n", dev->name); ++ } ++ ++ } ++ ++ dev->rd_buff_pos = 0; ++ memset(dev->rd_buff, 0, BLT_RDBUFF_MAX); ++ ++ } else { ++ ++ ast_log(LOG_ERROR, "Device %s: Expected '\\n' got %d. state = BLT_STATE_WANT_N2:\n", dev->name, c); ++ return -1; ++ ++ } ++ ++ break; ++ ++ default: ++ ast_log(LOG_ERROR, "Device %s: Unknown device state %d\n", dev->name, dev->state); ++ return -1; ++ ++ } ++ ++ } ++ ++ } ++ ++ return 0; ++} ++ ++/* Close the devices RFCOMM socket, and SCO if it exists. Must hold dev->lock */ ++ ++static void ++rd_close(blt_dev_t * dev, int reconnect, int e) ++{ ++ dev->ready = 0; ++ ++ if (dev->rd) ++ close(dev->rd); ++ ++ dev->rd = -1; ++ ++ dev->status = BLT_STATUS_DOWN; ++ ++ sco_stop(dev); ++ ++ if (dev->owner) { ++ ast_setstate(dev->owner, AST_STATE_DOWN); ++ ast_queue_control(dev->owner, AST_CONTROL_HANGUP); ++ } ++ ++ /* Schedule a reconnect */ ++ if (reconnect && dev->autoconnect) { ++ dev->outgoing_id = ast_sched_add(sched, 5000, AST_SCHED_CB(try_connect), dev); ++ ++ if (monitor_thread == pthread_self()) { ++ // Because we're not the monitor thread, we needd to inturrupt poll(). ++ pthread_kill(monitor_thread, SIGURG); ++ } ++ ++ if (e) ++ ast_log(LOG_NOTICE, "Device %s disconnected, scheduled reconnect in 5 seconds: %s (errno %d)\n", dev->name, strerror(e), e); ++ } else if (e) { ++ ast_log(LOG_NOTICE, "Device %s disconnected: %s (errno %d)\n", dev->name, strerror(e), e); ++ } ++ ++ return; ++} ++ ++/* ++ * Remember that we can only add to the scheduler from ++ * the do_monitor thread, as it calculates time to next one from ++ * this loop. ++ */ ++ ++static void * ++do_monitor(void * data) ++{ ++#define SRV_SOCK_CNT 3 ++ ++ int res = 0; ++ blt_dev_t * dev; ++ struct pollfd * pfds = malloc(sizeof(struct pollfd) * (ifcount + SRV_SOCK_CNT)); ++ ++ /* -- We start off by trying to connect all of our devices (non blocking) -- */ ++ ++ monitor_pid = getpid(); ++ ++ if (ast_mutex_lock(&iface_lock)) { ++ ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); ++ return NULL; ++ } ++ ++ dev = iface_head; ++ while (dev) { ++ ++ if (socketpair(PF_UNIX, SOCK_STREAM, 0, dev->sco_pipe) != 0) { ++ ast_log(LOG_ERROR, "Failed to create socket pair: %s (errno %d)\n", strerror(errno), errno); ++ ast_mutex_unlock(&iface_lock); ++ return NULL; ++ } ++ ++ if (dev->autoconnect && dev->status == BLT_STATUS_DOWN) ++ dev->outgoing_id = ast_sched_add(sched, 1500, AST_SCHED_CB(try_connect), dev); ++ dev = dev->next; ++ } ++ ast_mutex_unlock(&iface_lock); ++ ++ /* -- Now, Scan all sockets, and service scheduler -- */ ++ ++ pfds[0].fd = rfcomm_sock_ag; ++ pfds[0].events = POLLIN; ++ ++ pfds[1].fd = rfcomm_sock_hs; ++ pfds[1].events = POLLIN; ++ ++ pfds[2].fd = sco_socket; ++ pfds[2].events = POLLIN; ++ ++ while (1) { ++ int cnt = SRV_SOCK_CNT; ++ int i; ++ ++ /* -- Build pfds -- */ ++ ++ if (ast_mutex_lock(&iface_lock)) { ++ ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); ++ return NULL; ++ } ++ dev = iface_head; ++ while (dev) { ++ ast_mutex_lock(&(dev->lock)); ++ if (dev->rd > 0 && ((dev->status != BLT_STATUS_DOWN) && (dev->status != BLT_STATUS_CONNECTING))) { ++ pfds[cnt].fd = dev->rd; ++ pfds[cnt].events = POLLIN; ++ cnt++; ++ } ++ ast_mutex_unlock(&(dev->lock)); ++ dev = dev->next; ++ } ++ ast_mutex_unlock(&iface_lock); ++ ++ /* -- End Build pfds -- */ ++ ++ res = ast_sched_wait(sched); ++ res = poll(pfds, cnt, MAX(100, MIN(100, res))); ++ ++ if (res == 0) ++ ast_sched_runq(sched); ++ ++ if (pfds[0].revents) { ++ handle_incoming(rfcomm_sock_ag, BLT_ROLE_AG); ++ res--; ++ } ++ ++ if (pfds[1].revents) { ++ handle_incoming(rfcomm_sock_hs, BLT_ROLE_HS); ++ res--; ++ } ++ ++ if (pfds[2].revents) { ++ handle_incoming_sco(sco_socket); ++ res--; ++ } ++ ++ if (res == 0) ++ continue; ++ ++ for (i = SRV_SOCK_CNT ; i < cnt ; i++) { ++ ++ /* Optimise a little bit */ ++ if (res == 0) ++ break; ++ else if (pfds[i].revents == 0) ++ continue; ++ ++ /* -- Find the socket that has activity -- */ ++ ++ if (ast_mutex_lock(&iface_lock)) { ++ ast_log(LOG_ERROR, "Failed to get iface_lock.\n"); ++ return NULL; ++ } ++ ++ dev = iface_head; ++ ++ while (dev) { ++ if (pfds[i].fd == dev->rd) { ++ ast_mutex_lock(&(dev->lock)); ++ if (pfds[i].revents & POLLIN) { ++ if (handle_rd_data(dev) == -1) { ++ rd_close(dev, 0, 0); ++ } ++ } else { ++ rd_close(dev, 1, sock_err(dev->rd)); ++ } ++ ast_mutex_unlock(&(dev->lock)); ++ res--; ++ break; ++ } ++ dev = dev->next; ++ } ++ ++ if (dev == NULL) { ++ ast_log(LOG_ERROR, "Unhandled fd from poll()\n"); ++ close(pfds[i].fd); ++ } ++ ++ ast_mutex_unlock(&iface_lock); ++ ++ /* -- End find socket with activity -- */ ++ ++ } ++ ++ } ++ ++ return NULL; ++} ++ ++static int ++restart_monitor(void) ++{ ++ ++ if (monitor_thread == AST_PTHREADT_STOP) ++ return 0; ++ ++ if (ast_mutex_lock(&monitor_lock)) { ++ ast_log(LOG_WARNING, "Unable to lock monitor\n"); ++ return -1; ++ } ++ ++ if (monitor_thread == pthread_self()) { ++ ast_mutex_unlock(&monitor_lock); ++ ast_log(LOG_WARNING, "Cannot kill myself\n"); ++ return -1; ++ } ++ ++ if (monitor_thread != AST_PTHREADT_NULL) { ++ ++ /* Just signal it to be sure it wakes up */ ++ pthread_cancel(monitor_thread); ++ pthread_kill(monitor_thread, SIGURG); ++ ast_log(LOG_DEBUG, "Waiting for monitor thread to join...\n"); ++ pthread_join(monitor_thread, NULL); ++ ast_log(LOG_DEBUG, "joined\n"); ++ ++ } else { ++ ++ /* Start a new monitor */ ++ if (ast_pthread_create(&monitor_thread, NULL, do_monitor, NULL) < 0) { ++ ast_mutex_unlock(&monitor_lock); ++ ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); ++ return -1; ++ } ++ ++ } ++ ++ ast_mutex_unlock(&monitor_lock); ++ return 0; ++} ++ ++static int ++blt_parse_config(void) ++{ ++ struct ast_config * cfg; ++ struct ast_variable * v; ++ char * cat; ++ ++ cfg = ast_load(BLT_CONFIG_FILE); ++ ++ if (!cfg) { ++ ast_log(LOG_NOTICE, "Unable to load Bluetooth config: %s. Bluetooth disabled\n", BLT_CONFIG_FILE); ++ return -1; ++ } ++ ++ v = ast_variable_browse(cfg, "general"); ++ ++ while (v) { ++ if (!strcasecmp(v->name, "rfchannel_ag")) { ++ rfcomm_channel_ag = atoi(v->value); ++ } else if (!strcasecmp(v->name, "rfchannel_hs")) { ++ rfcomm_channel_hs = atoi(v->value); ++ } else if (!strcasecmp(v->name, "interface")) { ++ hcidev_id = atoi(v->value); ++ } else { ++ ast_log(LOG_WARNING, "Unknown config key '%s' in section [general]\n", v->name); ++ } ++ v = v->next; ++ } ++ cat = ast_category_browse(cfg, NULL); ++ ++ while(cat) { ++ ++ char * str; ++ ++ if (strcasecmp(cat, "general")) { ++ blt_dev_t * device = malloc(sizeof(blt_dev_t)); ++ memset(device, 0, sizeof(blt_dev_t)); ++ device->sco_running = -1; ++ device->sco = -1; ++ device->rd = -1; ++ device->outgoing_id = -1; ++ device->status = BLT_STATUS_DOWN; ++ str2ba(cat, &(device->bdaddr)); ++ device->name = ast_variable_retrieve(cfg, cat, "name"); ++ ++ str = ast_variable_retrieve(cfg, cat, "type"); ++ ++ if (str == NULL) { ++ ast_log(LOG_ERROR, "Device [%s] has no role. Specify type=\n", cat); ++ return -1; ++ } else if (strcasecmp(str, "HS") == 0) ++ device->role = BLT_ROLE_HS; ++ else if (strcasecmp(str, "AG") == 0) { ++ device->role = BLT_ROLE_AG; ++ } else { ++ ast_log(LOG_ERROR, "Device [%s] has invalid role '%s'\n", cat, str); ++ return -1; ++ } ++ ++ /* XXX:T: Find channel to use using SDP. ++ * However, this needs to be non blocking, and I can't see ++ * anything in sdp_lib.h that will allow non blocking calls. ++ */ ++ ++ device->channel = 1; ++ ++ if ((str = ast_variable_retrieve(cfg, cat, "channel")) != NULL) ++ device->channel = atoi(str); ++ ++ if ((str = ast_variable_retrieve(cfg, cat, "autoconnect")) != NULL) ++ device->autoconnect = (strcasecmp(str, "yes") == 0 || strcmp(str, "1") == 0) ? 1 : 0; ++ ++ device->next = iface_head; ++ iface_head = device; ++ ifcount++; ++ } ++ ++ cat = ast_category_browse(cfg, cat); ++ } ++ return 0; ++} ++ ++ ++static int ++blt_show_peers(int fd, int argc, char *argv[]) ++{ ++ blt_dev_t * dev; ++ ++ if (ast_mutex_lock(&iface_lock)) { ++ ast_log(LOG_ERROR, "Failed to get Iface lock\n"); ++ ast_cli(fd, "Failed to get iface lock\n"); ++ return RESULT_FAILURE; ++ } ++ ++ dev = iface_head; ++ ++ ast_cli(fd, "BDAddr Name Role Status A/C SCOCon/Fd/Th Sig\n"); ++ ast_cli(fd, "----------------- ---------- ---- ----------- --- ------------ ---\n"); ++ ++ while (dev) { ++ char b1[18]; ++ ba2str(&(dev->bdaddr), b1); ++ ast_cli(fd, "%s %-10s %-4s %-11s %-3s %2d/%02d/%-6ld %s\n", ++ b1, dev->name, (dev->role == BLT_ROLE_HS) ? "HS" : "AG", status2str(dev->status), ++ (dev->autoconnect) ? "Yes" : "No", ++ dev->sco_running, ++ dev->sco, ++ dev->sco_thread, ++ (dev->role == BLT_ROLE_AG) ? (dev->service) ? "Yes" : "No" : "N/A" ++ ); ++ dev = dev->next; ++ } ++ ++ ast_mutex_unlock(&iface_lock); ++ return RESULT_SUCCESS; ++} ++ ++static int ++blt_show_information(int fd, int argc, char *argv[]) ++{ ++ char b1[18]; ++ ba2str(&local_bdaddr, b1); ++ ast_cli(fd, "-------------------------------------------\n"); ++ ast_cli(fd, " Version : %s\n", BLT_SVN_REVISION); ++ ast_cli(fd, " Monitor PID : %d\n", monitor_pid); ++ ast_cli(fd, " RFCOMM AG : Channel %d, FD %d\n", rfcomm_channel_ag, rfcomm_sock_ag); ++ ast_cli(fd, " RFCOMM HS : Channel %d, FD %d\n", rfcomm_channel_hs, rfcomm_sock_hs); ++ ast_cli(fd, " Device : hci%d, MAC Address %s\n", hcidev_id, b1); ++ ast_cli(fd, "-------------------------------------------\n"); ++ return RESULT_SUCCESS; ++} ++ ++static int ++blt_ag_sendcmd(int fd, int argc, char *argv[]) ++{ ++ blt_dev_t * dev; ++ ++ if (argc != 4) ++ return RESULT_SHOWUSAGE; ++ ++ ast_mutex_lock(&iface_lock); ++ dev = iface_head; ++ while (dev) { ++ if (!strcasecmp(argv[2], dev->name)) ++ break; ++ dev = dev->next; ++ } ++ ast_mutex_unlock(&iface_lock); ++ ++ if (!dev) { ++ ast_cli(fd, "Device '%s' does not exist\n", argv[2]); ++ return RESULT_FAILURE; ++ } ++ ++ if (dev->role != BLT_ROLE_AG) { ++ ast_cli(fd, "Device '%s' is not an AudioGateway\n", argv[2]); ++ return RESULT_FAILURE; ++ } ++ ++ if (dev->status == BLT_STATUS_DOWN || dev->status == BLT_STATUS_NEGOTIATING) { ++ ast_cli(fd, "Device '%s' is not connected\n", argv[2]); ++ return RESULT_FAILURE; ++ } ++ ++ if (*(argv[3] + strlen(argv[3]) - 1) == '.') ++ *(argv[3] + strlen(argv[3]) - 1) = '?'; ++ ++ ast_cli(fd, "Sending AT command to %s: %s\n", dev->name, argv[3]); ++ ++ ast_mutex_lock(&(dev->lock)); ++ send_atcmd(dev, argv[3]); ++ ast_mutex_unlock(&(dev->lock)); ++ ++ return RESULT_SUCCESS; ++} ++ ++static char * ++complete_device(char * line, char * word, int pos, int state, int rpos, blt_role_t role) ++{ ++ blt_dev_t * dev; ++ int which = 0; ++ char *ret; ++ ++ if (pos != rpos) ++ return NULL; ++ ++ ast_mutex_lock(&iface_lock); ++ ++ dev = iface_head; ++ ++ while (dev) { ++ ++ if ((dev->role == role) && (!strncasecmp(word, dev->name, strlen(word)))) { ++ if (++which > state) ++ break; ++ } ++ ++ dev = dev->next; ++ } ++ ++ if (dev) ++ ret = strdup(dev->name); ++ else ++ ret = NULL; ++ ++ ast_mutex_unlock(&iface_lock); ++ ++ return ret; ++} ++ ++static char * ++complete_device_2_ag(char * line, char * word, int pos, int state) ++{ ++ return complete_device(line, word, pos, state, 2, BLT_ROLE_AG); ++} ++ ++static char show_peers_usage[] = ++"Usage: bluetooth show peers\n" ++" List all bluetooth peers and their status\n"; ++ ++static struct ast_cli_entry ++cli_show_peers = ++ { { "bluetooth", "show", "peers", NULL }, blt_show_peers, "List Bluetooth Peers", show_peers_usage }; ++ ++ ++static char ag_sendcmd[] = ++"Usage: bluetooth ag sendcmd \n" ++" Sends a AT cmd over the RFCOMM link, and print result (AG only)\n"; ++ ++static struct ast_cli_entry ++cli_ag_sendcmd = ++ { { "bluetooth", "sendcmd", NULL }, blt_ag_sendcmd, "Send AG an AT command", ag_sendcmd, complete_device_2_ag }; ++ ++static char show_information[] = ++"Usage: bluetooth show information\n" ++" Lists information about the bluetooth subsystem\n"; ++ ++static struct ast_cli_entry ++cli_show_information = ++ { { "bluetooth", "show", "information", NULL }, blt_show_information, "List Bluetooth Info", show_information }; ++ ++void ++remove_sdp_records(void) ++{ ++ ++ sdp_session_t * sdp; ++ sdp_list_t * attr; ++ sdp_record_t * rec; ++ int res = -1; ++ uint32_t range = 0x0000ffff; ++ ++ if (sdp_record_ag == -1 || sdp_record_hs == -1) ++ return; ++ ++ ast_log(LOG_DEBUG, "Removing SDP records\n"); ++ ++ sdp = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); ++ ++ if (!sdp) ++ return; ++ ++ attr = sdp_list_append(0, &range); ++ rec = sdp_service_attr_req(sdp, sdp_record_ag, SDP_ATTR_REQ_RANGE, attr); ++ sdp_list_free(attr, 0); ++ ++ if (rec) ++ if (sdp_record_unregister(sdp, rec) == 0) ++ res = 0; ++ ++ attr = sdp_list_append(0, &range); ++ rec = sdp_service_attr_req(sdp, sdp_record_hs, SDP_ATTR_REQ_RANGE, attr); ++ sdp_list_free(attr, 0); ++ ++ if (rec) ++ if (sdp_record_unregister(sdp, rec) == 0) ++ res = 0; ++ ++ sdp_close(sdp); ++ ++ if (res == 0) ++ ast_log(LOG_NOTICE, "Removed SDP records\n"); ++ else ++ ast_log(LOG_ERROR, "Failed to remove SDP records\n"); ++ ++} ++ ++static int ++__unload_module(void) ++{ ++ ++ ast_channel_unregister(BLT_CHAN_NAME); ++ ++ if (monitor_thread != AST_PTHREADT_NULL) { ++ ++ if (ast_mutex_lock(&monitor_lock)) { ++ ++ if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { ++ pthread_cancel(monitor_thread); ++ pthread_kill(monitor_thread, SIGURG); ++ fprintf(stderr, "Waiting for monitor thread to join...\n"); ++ pthread_join(monitor_thread, NULL); ++ fprintf(stderr, "joined\n"); ++ } ++ monitor_thread = AST_PTHREADT_STOP; ++ ast_mutex_unlock(&monitor_lock); ++ ++ } else { ++ ++ ast_log(LOG_WARNING, "Unable to lock the monitor\n"); ++ return -1; ++ ++ } ++ ++ } ++ ++ ast_unregister_atexit(remove_sdp_records); ++ remove_sdp_records(); ++ return 0; ++} ++ ++int ++load_module() ++{ ++ sdp_session_t * sess; ++ int dd; ++ uint16_t vs; ++ ++ hcidev_id = BLT_DEFAULT_HCI_DEV; ++ ++ if (blt_parse_config() != 0) { ++ ast_log(LOG_ERROR, "Bluetooth configuration error. Bluetooth Disabled\n"); ++ return unload_module(); ++ } ++ ++ dd = hci_open_dev(hcidev_id); ++ if (dd == -1) { ++ ast_log(LOG_ERROR, "Unable to open interface hci%d: %s.\n", hcidev_id, strerror(errno)); ++ return -1; ++ } ++ ++ hci_read_voice_setting(dd, &vs, 1000); ++ vs = htobs(vs); ++ close(dd); ++ ++ if (vs != 0x0060) { ++ ast_log(LOG_ERROR, "Bluetooth voice setting must be 0x0060, not 0x%04x\n", vs); ++ unload_module(); ++ return 0; ++ } ++ ++ if ((sched = sched_context_create()) == NULL) { ++ ast_log(LOG_WARNING, "Unable to create schedule context\n"); ++ return -1; ++ } ++ ++ memset(&local_bdaddr, 0, sizeof(local_bdaddr)); ++ ++ hci_devba(hcidev_id, &local_bdaddr); ++ ++ /* --- Add SDP record --- */ ++ ++ sess = sdp_connect(&local_bdaddr, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); ++ ++ if ((rfcomm_sock_ag = rfcomm_listen(&local_bdaddr, rfcomm_channel_ag)) < 0) { ++ return -1; ++ } ++ ++ if ((rfcomm_sock_hs = rfcomm_listen(&local_bdaddr, rfcomm_channel_hs)) < 0) ++ return -1; ++ ++ if ((sco_socket = sco_listen(&local_bdaddr)) < 0) ++ return -1; ++ ++ if (!sess) { ++ ast_log(LOG_ERROR, "Failed to connect to SDP server: %s\n", strerror(errno)); ++ return -1; ++ } ++ ++ if (sdp_register(sess) != 0) { ++ ast_log(LOG_ERROR, "Failed to register HeadsetAudioGateway in SDP\n"); ++ return -1; ++ } ++ ++ sdp_close(sess); ++ ++ if (restart_monitor() != 0) ++ return -1; ++ ++ if (ast_channel_register(BLT_CHAN_NAME, "Bluetooth Driver", BLUETOOTH_FORMAT, blt_request)) { ++ ast_log(LOG_ERROR, "Unable to register channel class BTL\n"); ++ __unload_module(); ++ return -1; ++ } ++ ++ ast_cli_register(&cli_show_information); ++ ast_cli_register(&cli_show_peers); ++ ast_cli_register(&cli_ag_sendcmd); ++ ++ ast_register_atexit(remove_sdp_records); ++ ++ ast_log(LOG_NOTICE, "Loaded Bluetooth support, %s\n", BLT_SVN_REVISION + 1); ++ ++ return 0; ++} ++ ++int ++unload_module(void) ++{ ++ ast_cli_unregister(&cli_ag_sendcmd); ++ ast_cli_unregister(&cli_show_peers); ++ ast_cli_unregister(&cli_show_information); ++ return __unload_module(); ++} ++ ++int ++usecount() ++{ ++ int res; ++ ast_mutex_lock(&usecnt_lock); ++ res = usecnt; ++ ast_mutex_unlock(&usecnt_lock); ++ return res; ++} ++ ++char *description() ++{ ++ return "Bluetooth Channel Driver"; ++} ++ ++char * ++key() ++{ ++ return ASTERISK_GPL_KEY; ++} ++ ++ +diff -ruN asterisk-1.0.9-old/configs/bluetooth.conf asterisk-1.0.9-new/configs/bluetooth.conf +--- asterisk-1.0.9-old/configs/bluetooth.conf 1970-01-01 01:00:00.000000000 +0100 ++++ asterisk-1.0.9-new/configs/bluetooth.conf 2004-10-22 11:10:48.000000000 +0200 +@@ -0,0 +1,33 @@ ++[general] ++; Channel we listen on as a HS (Headset) ++rfchannel_hs = 2 ++; Channel we listen on as an AG (AudioGateway) ++rfchannel_ag = 3 ++; hci interface to use (number - e.g '0') ++interface = 0 ++ ++;; A HBH-500 Handsfree Kit ++[00:0A:D9:A1:AA:D2] ++; Any name to use, this is what we use to send calls to (BLT/). ++name = HBH-500 ++; IS this a HS or AG? ++type = HS ++; ++; ++; RFCOMM channel to connect to. For a HandsSet: ++; sdptool search --bdaddr xx:xx:xx:xx:xx:xx 0x111E ++; or,for an AudioGateway (Phone): ++; sdptool search --bdaddr xx:xx:xx:xx:xx:xx 0x111F ++; ++; Find the 'channel' value under RFCOMM. ++; ++channel = 2 ++; Automatically conenct? ++autoconnect = yes ++ ++;; A Nokia 6310i ++[00:60:57:1C:00:99] ++name = Neil ++type = AG ++channel = 13 ++autoconnect = yes -- 2.11.0