mirror of https://github.com/asterisk/asterisk
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							4841 lines
						
					
					
						
							126 KiB
						
					
					
				
			
		
		
	
	
							4841 lines
						
					
					
						
							126 KiB
						
					
					
				| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 1999 - 2006, Digium, Inc.
 | |
|  *
 | |
|  * Mark Spencer <markster@digium.com>
 | |
|  *
 | |
|  * See http://www.asterisk.org for more information about
 | |
|  * the Asterisk project. Please do not directly contact
 | |
|  * any of the maintainers of this project for assistance;
 | |
|  * the project provides a web site, mailing lists and IRC
 | |
|  * channels for your use.
 | |
|  *
 | |
|  * This program is free software, distributed under the terms of
 | |
|  * the GNU General Public License Version 2. See the LICENSE file
 | |
|  * at the top of the source tree.
 | |
|  */
 | |
| 
 | |
| /*!
 | |
|  * \file
 | |
|  * \brief Bluetooth Mobile Device channel driver
 | |
|  *
 | |
|  * \author Dave Bowerman <david.bowerman@gmail.com>
 | |
|  *
 | |
|  * \ingroup channel_drivers
 | |
|  */
 | |
| 
 | |
| /*! \li \ref chan_mobile.c uses the configuration file \ref chan_mobile.conf
 | |
|  * \addtogroup configuration_file Configuration Files
 | |
|  */
 | |
| 
 | |
| /*!
 | |
|  * \page chan_mobile.conf chan_mobile.conf
 | |
|  * \verbinclude chan_mobile.conf.sample
 | |
|  */
 | |
| 
 | |
| /*** MODULEINFO
 | |
| 	<depend>bluetooth</depend>
 | |
| 	<defaultenabled>no</defaultenabled>
 | |
| 	<support_level>extended</support_level>
 | |
|  ***/
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include <pthread.h>
 | |
| #include <signal.h>
 | |
| 
 | |
| #include <bluetooth/bluetooth.h>
 | |
| #include <bluetooth/hci.h>
 | |
| #include <bluetooth/hci_lib.h>
 | |
| #include <bluetooth/sdp.h>
 | |
| #include <bluetooth/sdp_lib.h>
 | |
| #include <bluetooth/rfcomm.h>
 | |
| #include <bluetooth/sco.h>
 | |
| #include <bluetooth/l2cap.h>
 | |
| 
 | |
| #include "asterisk/compat.h"
 | |
| #include "asterisk/lock.h"
 | |
| #include "asterisk/callerid.h"
 | |
| #include "asterisk/channel.h"
 | |
| #include "asterisk/config.h"
 | |
| #include "asterisk/logger.h"
 | |
| #include "asterisk/module.h"
 | |
| #include "asterisk/pbx.h"
 | |
| #include "asterisk/options.h"
 | |
| #include "asterisk/utils.h"
 | |
| #include "asterisk/linkedlists.h"
 | |
| #include "asterisk/cli.h"
 | |
| #include "asterisk/devicestate.h"
 | |
| #include "asterisk/causes.h"
 | |
| #include "asterisk/dsp.h"
 | |
| #include "asterisk/app.h"
 | |
| #include "asterisk/manager.h"
 | |
| #include "asterisk/io.h"
 | |
| #include "asterisk/smoother.h"
 | |
| #include "asterisk/format_cache.h"
 | |
| 
 | |
| #define MBL_CONFIG "chan_mobile.conf"
 | |
| #define MBL_CONFIG_OLD "mobile.conf"
 | |
| 
 | |
| #define DEVICE_FRAME_SIZE 48
 | |
| #define DEVICE_FRAME_FORMAT ast_format_slin
 | |
| #define CHANNEL_FRAME_SIZE 80
 | |
| 
 | |
| static int discovery_interval = 60;			/* The device discovery interval, default 60 seconds. */
 | |
| static pthread_t discovery_thread = AST_PTHREADT_NULL;	/* The discovery thread */
 | |
| static sdp_session_t *sdp_session;
 | |
| 
 | |
| AST_MUTEX_DEFINE_STATIC(unload_mutex);
 | |
| static int unloading_flag = 0;
 | |
| static inline int check_unloading(void);
 | |
| static inline void set_unloading(void);
 | |
| 
 | |
| enum mbl_type {
 | |
| 	MBL_TYPE_PHONE,
 | |
| 	MBL_TYPE_HEADSET
 | |
| };
 | |
| 
 | |
| struct adapter_pvt {
 | |
| 	int dev_id;					/* device id */
 | |
| 	int hci_socket;					/* device descriptor */
 | |
| 	char id[31];					/* the 'name' from mobile.conf */
 | |
| 	bdaddr_t addr;					/* adddress of adapter */
 | |
| 	unsigned int inuse:1;				/* are we in use ? */
 | |
| 	unsigned int alignment_detection:1;		/* do alignment detection on this adapter? */
 | |
| 	struct io_context *io;				/*!< io context for audio connections */
 | |
| 	struct io_context *accept_io;			/*!< io context for sco listener */
 | |
| 	int *sco_id;					/*!< the io context id of the sco listener socket */
 | |
| 	int sco_socket;					/*!< sco listener socket */
 | |
| 	pthread_t sco_listener_thread;			/*!< sco listener thread */
 | |
| 	AST_LIST_ENTRY(adapter_pvt) entry;
 | |
| };
 | |
| 
 | |
| static AST_RWLIST_HEAD_STATIC(adapters, adapter_pvt);
 | |
| 
 | |
| struct msg_queue_entry;
 | |
| struct hfp_pvt;
 | |
| struct mbl_pvt {
 | |
| 	struct ast_channel *owner;			/* Channel we belong to, possibly NULL */
 | |
| 	struct ast_frame fr;				/* "null" frame */
 | |
| 	ast_mutex_t lock;				/*!< pvt lock */
 | |
| 	/*! queue for messages we are expecting */
 | |
| 	AST_LIST_HEAD_NOLOCK(msg_queue, msg_queue_entry) msg_queue;
 | |
| 	enum mbl_type type;				/* Phone or Headset */
 | |
| 	char id[31];					/* The id from mobile.conf */
 | |
| 	int group;					/* group number for group dialling */
 | |
| 	bdaddr_t addr;					/* address of device */
 | |
| 	struct adapter_pvt *adapter;			/* the adapter we use */
 | |
| 	char context[AST_MAX_CONTEXT];			/* the context for incoming calls */
 | |
| 	struct hfp_pvt *hfp;				/*!< hfp pvt */
 | |
| 	int rfcomm_port;				/* rfcomm port number */
 | |
| 	int rfcomm_socket;				/* rfcomm socket descriptor */
 | |
| 	char rfcomm_buf[256];
 | |
| 	char io_buf[CHANNEL_FRAME_SIZE + AST_FRIENDLY_OFFSET];
 | |
| 	struct ast_smoother *bt_out_smoother;			/* our bt_out_smoother, for making 48 byte frames */
 | |
| 	struct ast_smoother *bt_in_smoother;			/* our smoother, for making "normal" CHANNEL_FRAME_SIZEed byte frames */
 | |
| 	int sco_socket;					/* sco socket descriptor */
 | |
| 	pthread_t monitor_thread;			/* monitor thread handle */
 | |
| 	int timeout;					/*!< used to set the timeout for rfcomm data (may be used in the future) */
 | |
| 	unsigned int no_callsetup:1;
 | |
| 	unsigned int has_sms:1;
 | |
| 	unsigned int do_alignment_detection:1;
 | |
| 	unsigned int alignment_detection_triggered:1;
 | |
| 	unsigned int blackberry:1;
 | |
| 	short alignment_samples[4];
 | |
| 	int alignment_count;
 | |
| 	int ring_sched_id;
 | |
| 	struct ast_dsp *dsp;
 | |
| 	struct ast_sched_context *sched;
 | |
| 	int hangupcause;
 | |
| 
 | |
| 	/* flags */
 | |
| 	unsigned int outgoing:1;	/*!< outgoing call */
 | |
| 	unsigned int incoming:1;	/*!< incoming call */
 | |
| 	unsigned int outgoing_sms:1;	/*!< outgoing sms */
 | |
| 	unsigned int incoming_sms:1;	/*!< outgoing sms */
 | |
| 	unsigned int needcallerid:1;	/*!< we need callerid */
 | |
| 	unsigned int needchup:1;	/*!< we need to send a chup */
 | |
| 	unsigned int needring:1;	/*!< we need to send a RING */
 | |
| 	unsigned int answered:1;	/*!< we sent/received an answer */
 | |
| 	unsigned int connected:1;	/*!< do we have an rfcomm connection to a device */
 | |
| 
 | |
| 	AST_LIST_ENTRY(mbl_pvt) entry;
 | |
| };
 | |
| 
 | |
| /*! Structure used by hfp_parse_clip to return two items */
 | |
| struct cidinfo {
 | |
| 	char *cnum;
 | |
| 	char *cnam;
 | |
| };
 | |
| 
 | |
| static AST_RWLIST_HEAD_STATIC(devices, mbl_pvt);
 | |
| 
 | |
| static int handle_response_ok(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_error(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_ciev(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_clip(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_ring(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_cmti(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_cmgr(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_cusd(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_busy(struct mbl_pvt *pvt);
 | |
| static int handle_response_no_dialtone(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_response_no_carrier(struct mbl_pvt *pvt, char *buf);
 | |
| static int handle_sms_prompt(struct mbl_pvt *pvt, char *buf);
 | |
| 
 | |
| /* CLI stuff */
 | |
| static char *handle_cli_mobile_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
 | |
| static char *handle_cli_mobile_search(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
 | |
| static char *handle_cli_mobile_rfcomm(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
 | |
| static char *handle_cli_mobile_cusd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
 | |
| 
 | |
| static struct ast_cli_entry mbl_cli[] = {
 | |
| 	AST_CLI_DEFINE(handle_cli_mobile_show_devices, "Show Bluetooth Cell / Mobile devices"),
 | |
| 	AST_CLI_DEFINE(handle_cli_mobile_search,       "Search for Bluetooth Cell / Mobile devices"),
 | |
| 	AST_CLI_DEFINE(handle_cli_mobile_rfcomm,       "Send commands to the rfcomm port for debugging"),
 | |
| 	AST_CLI_DEFINE(handle_cli_mobile_cusd,         "Send CUSD commands to the mobile"),
 | |
| };
 | |
| 
 | |
| /* App stuff */
 | |
| static char *app_mblstatus = "MobileStatus";
 | |
| static char *mblstatus_synopsis = "MobileStatus(Device,Variable)";
 | |
| static char *mblstatus_desc =
 | |
| "MobileStatus(Device,Variable)\n"
 | |
| "  Device - Id of mobile device from mobile.conf\n"
 | |
| "  Variable - Variable to store status in will be 1-3.\n"
 | |
| "             In order, Disconnected, Connected & Free, Connected & Busy.\n";
 | |
| 
 | |
| static char *app_mblsendsms = "MobileSendSMS";
 | |
| static char *mblsendsms_synopsis = "MobileSendSMS(Device,Dest,Message)";
 | |
| static char *mblsendsms_desc =
 | |
| "MobileSendSms(Device,Dest,Message)\n"
 | |
| "  Device - Id of device from mobile.conf\n"
 | |
| "  Dest - destination\n"
 | |
| "  Message - text of the message\n";
 | |
| 
 | |
| static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, struct cidinfo *cidinfo,
 | |
| 		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor);
 | |
| static struct ast_channel *mbl_request(const char *type, struct ast_format_cap *cap,
 | |
| 		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
 | |
| static int mbl_call(struct ast_channel *ast, const char *dest, int timeout);
 | |
| static int mbl_hangup(struct ast_channel *ast);
 | |
| static int mbl_answer(struct ast_channel *ast);
 | |
| static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
 | |
| static struct ast_frame *mbl_read(struct ast_channel *ast);
 | |
| static int mbl_write(struct ast_channel *ast, struct ast_frame *frame);
 | |
| static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
 | |
| static int mbl_devicestate(const char *data);
 | |
| 
 | |
| static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen);
 | |
| 
 | |
| static int mbl_queue_control(struct mbl_pvt *pvt, enum ast_control_frame_type control);
 | |
| static int mbl_queue_hangup(struct mbl_pvt *pvt);
 | |
| static int mbl_ast_hangup(struct mbl_pvt *pvt);
 | |
| static int mbl_has_service(struct mbl_pvt *pvt);
 | |
| 
 | |
| static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel);
 | |
| static int rfcomm_write(int rsock, char *buf);
 | |
| static int rfcomm_write_full(int rsock, char *buf, size_t count);
 | |
| static int rfcomm_wait(int rsock, int *ms);
 | |
| static ssize_t rfcomm_read(int rsock, char *buf, size_t count);
 | |
| 
 | |
| static int sco_connect(bdaddr_t src, bdaddr_t dst);
 | |
| static int sco_write(int s, char *buf, int len);
 | |
| static int sco_accept(int *id, int fd, short events, void *data);
 | |
| static int sco_bind(struct adapter_pvt *adapter);
 | |
| 
 | |
| static void *do_sco_listen(void *data);
 | |
| static int sdp_search(char *addr, int profile);
 | |
| 
 | |
| static int headset_send_ring(const void *data);
 | |
| 
 | |
| /*
 | |
|  * bluetooth handsfree profile helpers
 | |
|  */
 | |
| 
 | |
| #define HFP_HF_ECNR	(1 << 0)
 | |
| #define HFP_HF_CW	(1 << 1)
 | |
| #define HFP_HF_CID	(1 << 2)
 | |
| #define HFP_HF_VOICE	(1 << 3)
 | |
| #define HFP_HF_VOLUME	(1 << 4)
 | |
| #define HFP_HF_STATUS	(1 << 5)
 | |
| #define HFP_HF_CONTROL	(1 << 6)
 | |
| 
 | |
| #define HFP_AG_CW	(1 << 0)
 | |
| #define HFP_AG_ECNR	(1 << 1)
 | |
| #define HFP_AG_VOICE	(1 << 2)
 | |
| #define HFP_AG_RING	(1 << 3)
 | |
| #define HFP_AG_TAG	(1 << 4)
 | |
| #define HFP_AG_REJECT	(1 << 5)
 | |
| #define HFP_AG_STATUS	(1 << 6)
 | |
| #define HFP_AG_CONTROL	(1 << 7)
 | |
| #define HFP_AG_ERRORS	(1 << 8)
 | |
| 
 | |
| #define HFP_CIND_UNKNOWN	-1
 | |
| #define HFP_CIND_NONE		0
 | |
| #define HFP_CIND_SERVICE	1
 | |
| #define HFP_CIND_CALL		2
 | |
| #define HFP_CIND_CALLSETUP	3
 | |
| #define HFP_CIND_CALLHELD	4
 | |
| #define HFP_CIND_SIGNAL		5
 | |
| #define HFP_CIND_ROAM		6
 | |
| #define HFP_CIND_BATTCHG	7
 | |
| 
 | |
| /* call indicator values */
 | |
| #define HFP_CIND_CALL_NONE	0
 | |
| #define HFP_CIND_CALL_ACTIVE	1
 | |
| 
 | |
| /* callsetup indicator values */
 | |
| #define HFP_CIND_CALLSETUP_NONE		0
 | |
| #define HFP_CIND_CALLSETUP_INCOMING	1
 | |
| #define HFP_CIND_CALLSETUP_OUTGOING	2
 | |
| #define HFP_CIND_CALLSETUP_ALERTING	3
 | |
| 
 | |
| /* service indicator values */
 | |
| #define HFP_CIND_SERVICE_NONE		0
 | |
| #define HFP_CIND_SERVICE_AVAILABLE	1
 | |
| 
 | |
| /*!
 | |
|  * \brief This struct holds HFP features that we support.
 | |
|  */
 | |
| struct hfp_hf {
 | |
| 	int ecnr:1;	/*!< echo-cancel/noise reduction */
 | |
| 	int cw:1;	/*!< call waiting and three way calling */
 | |
| 	int cid:1;	/*!< cli presentation (callier id) */
 | |
| 	int voice:1;	/*!< voice recognition activation */
 | |
| 	int volume:1;	/*!< remote volume control */
 | |
| 	int status:1;	/*!< enhanced call status */
 | |
| 	int control:1;	/*!< enhanced call control*/
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \brief This struct holds HFP features the AG supports.
 | |
|  */
 | |
| struct hfp_ag {
 | |
| 	int cw:1;	/*!< three way calling */
 | |
| 	int ecnr:1;	/*!< echo-cancel/noise reduction */
 | |
| 	int voice:1;	/*!< voice recognition */
 | |
| 	int ring:1;	/*!< in band ring tone capability */
 | |
| 	int tag:1;	/*!< attach a number to a voice tag */
 | |
| 	int reject:1;	/*!< ability to reject a call */
 | |
| 	int status:1;	/*!< enhanced call status */
 | |
| 	int control:1;	/*!< enhanced call control*/
 | |
| 	int errors:1;	/*!< extended error result codes*/
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \brief This struct holds mappings for indications.
 | |
|  */
 | |
| struct hfp_cind {
 | |
| 	int service;	/*!< whether we have service or not */
 | |
| 	int call;	/*!< call state */
 | |
| 	int callsetup;	/*!< bluetooth call setup indications */
 | |
| 	int callheld;	/*!< bluetooth call hold indications */
 | |
| 	int signal;	/*!< signal strength */
 | |
| 	int roam;	/*!< roaming indicator */
 | |
| 	int battchg;	/*!< battery charge indicator */
 | |
| };
 | |
| 
 | |
| 
 | |
| /*!
 | |
|  * \brief This struct holds state information about the current hfp connection.
 | |
|  */
 | |
| struct hfp_pvt {
 | |
| 	struct mbl_pvt *owner;		/*!< the mbl_pvt struct that owns this struct */
 | |
| 	int initialized:1;		/*!< whether a service level connection exists or not */
 | |
| 	int nocallsetup:1;		/*!< whether we detected a callsetup indicator */
 | |
| 	struct hfp_ag brsf;		/*!< the supported feature set of the AG */
 | |
| 	int cind_index[16];		/*!< the cind/ciev index to name mapping for this AG */
 | |
| 	int cind_state[16];		/*!< the cind/ciev state for this AG */
 | |
| 	struct hfp_cind cind_map;	/*!< the cind name to index mapping for this AG */
 | |
| 	int rsock;			/*!< our rfcomm socket */
 | |
| 	int rport;			/*!< our rfcomm port */
 | |
| 	int sent_alerting;		/*!< have we sent alerting? */
 | |
| };
 | |
| 
 | |
| 
 | |
| /* Our supported features.
 | |
|  * we only support caller id
 | |
|  */
 | |
| static struct hfp_hf hfp_our_brsf = {
 | |
| 	.ecnr = 0,
 | |
| 	.cw = 0,
 | |
| 	.cid = 1,
 | |
| 	.voice = 0,
 | |
| 	.volume = 0,
 | |
| 	.status = 0,
 | |
| 	.control = 0,
 | |
| };
 | |
| 
 | |
| 
 | |
| static int hfp_parse_ciev(struct hfp_pvt *hfp, char *buf, int *value);
 | |
| static struct cidinfo hfp_parse_clip(struct hfp_pvt *hfp, char *buf);
 | |
| static int parse_next_token(char string[], const int start, const char delim);
 | |
| static int hfp_parse_cmti(struct hfp_pvt *hfp, char *buf);
 | |
| static int hfp_parse_cmgr(struct hfp_pvt *hfp, char *buf, char **from_number, char **text);
 | |
| static int hfp_parse_brsf(struct hfp_pvt *hfp, const char *buf);
 | |
| static int hfp_parse_cind(struct hfp_pvt *hfp, char *buf);
 | |
| static int hfp_parse_cind_test(struct hfp_pvt *hfp, char *buf);
 | |
| static char *hfp_parse_cusd(struct hfp_pvt *hfp, char *buf);
 | |
| 
 | |
| static int hfp_brsf2int(struct hfp_hf *hf);
 | |
| static struct hfp_ag *hfp_int2brsf(int brsf, struct hfp_ag *ag);
 | |
| 
 | |
| static int hfp_send_brsf(struct hfp_pvt *hfp, struct hfp_hf *brsf);
 | |
| static int hfp_send_cind(struct hfp_pvt *hfp);
 | |
| static int hfp_send_cind_test(struct hfp_pvt *hfp);
 | |
| static int hfp_send_cmer(struct hfp_pvt *hfp, int status);
 | |
| static int hfp_send_clip(struct hfp_pvt *hfp, int status);
 | |
| static int hfp_send_vgs(struct hfp_pvt *hfp, int value);
 | |
| 
 | |
| #if 0
 | |
| static int hfp_send_vgm(struct hfp_pvt *hfp, int value);
 | |
| #endif
 | |
| static int hfp_send_dtmf(struct hfp_pvt *hfp, char digit);
 | |
| static int hfp_send_cmgf(struct hfp_pvt *hfp, int mode);
 | |
| static int hfp_send_cnmi(struct hfp_pvt *hfp);
 | |
| static int hfp_send_cmgr(struct hfp_pvt *hfp, int index);
 | |
| static int hfp_send_cmgs(struct hfp_pvt *hfp, const char *number);
 | |
| static int hfp_send_sms_text(struct hfp_pvt *hfp, const char *message);
 | |
| static int hfp_send_chup(struct hfp_pvt *hfp);
 | |
| static int hfp_send_atd(struct hfp_pvt *hfp, const char *number);
 | |
| static int hfp_send_ata(struct hfp_pvt *hfp);
 | |
| static int hfp_send_cusd(struct hfp_pvt *hfp, const char *code);
 | |
| 
 | |
| /*
 | |
|  * bluetooth headset profile helpers
 | |
|  */
 | |
| static int hsp_send_ok(int rsock);
 | |
| static int hsp_send_error(int rsock);
 | |
| static int hsp_send_vgs(int rsock, int gain);
 | |
| static int hsp_send_vgm(int rsock, int gain);
 | |
| static int hsp_send_ring(int rsock);
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Hayes AT command helpers
 | |
|  */
 | |
| typedef enum {
 | |
| 	/* errors */
 | |
| 	AT_PARSE_ERROR = -2,
 | |
| 	AT_READ_ERROR = -1,
 | |
| 	AT_UNKNOWN = 0,
 | |
| 	/* at responses */
 | |
| 	AT_OK,
 | |
| 	AT_ERROR,
 | |
| 	AT_RING,
 | |
| 	AT_BRSF,
 | |
| 	AT_CIND,
 | |
| 	AT_CIEV,
 | |
| 	AT_CLIP,
 | |
| 	AT_CMTI,
 | |
| 	AT_CMGR,
 | |
| 	AT_SMS_PROMPT,
 | |
| 	AT_CMS_ERROR,
 | |
| 	/* at commands */
 | |
| 	AT_A,
 | |
| 	AT_D,
 | |
| 	AT_CHUP,
 | |
| 	AT_CKPD,
 | |
| 	AT_CMGS,
 | |
| 	AT_VGM,
 | |
| 	AT_VGS,
 | |
| 	AT_VTS,
 | |
| 	AT_CMGF,
 | |
| 	AT_CNMI,
 | |
| 	AT_CMER,
 | |
| 	AT_CIND_TEST,
 | |
| 	AT_CUSD,
 | |
| 	AT_BUSY,
 | |
| 	AT_NO_DIALTONE,
 | |
| 	AT_NO_CARRIER,
 | |
| 	AT_ECAM,
 | |
| } at_message_t;
 | |
| 
 | |
| static int at_match_prefix(char *buf, char *prefix);
 | |
| static at_message_t at_read_full(int rsock, char *buf, size_t count);
 | |
| static inline const char *at_msg2str(at_message_t msg);
 | |
| 
 | |
| struct msg_queue_entry {
 | |
| 	at_message_t expected;
 | |
| 	at_message_t response_to;
 | |
| 	void *data;
 | |
| 
 | |
| 	AST_LIST_ENTRY(msg_queue_entry) entry;
 | |
| };
 | |
| 
 | |
| static int msg_queue_push(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to);
 | |
| static int msg_queue_push_data(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to, void *data);
 | |
| static struct msg_queue_entry *msg_queue_pop(struct mbl_pvt *pvt);
 | |
| static void msg_queue_free_and_pop(struct mbl_pvt *pvt);
 | |
| static void msg_queue_flush(struct mbl_pvt *pvt);
 | |
| static struct msg_queue_entry *msg_queue_head(struct mbl_pvt *pvt);
 | |
| 
 | |
| /*
 | |
|  * channel stuff
 | |
|  */
 | |
| 
 | |
| static struct ast_channel_tech mbl_tech = {
 | |
| 	.type = "Mobile",
 | |
| 	.description = "Bluetooth Mobile Device Channel Driver",
 | |
| 	.requester = mbl_request,
 | |
| 	.call = mbl_call,
 | |
| 	.hangup = mbl_hangup,
 | |
| 	.answer = mbl_answer,
 | |
| 	.send_digit_end = mbl_digit_end,
 | |
| 	.read = mbl_read,
 | |
| 	.write = mbl_write,
 | |
| 	.fixup = mbl_fixup,
 | |
| 	.devicestate = mbl_devicestate
 | |
| };
 | |
| 
 | |
| /* CLI Commands implementation */
 | |
| 
 | |
| static char *handle_cli_mobile_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	char bdaddr[18];
 | |
| 	char group[6];
 | |
| 
 | |
| #define FORMAT1 "%-15.15s %-17.17s %-5.5s %-15.15s %-9.9s %-10.10s %-3.3s\n"
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "mobile show devices";
 | |
| 		e->usage =
 | |
| 			"Usage: mobile show devices\n"
 | |
| 			"       Shows the state of Bluetooth Cell / Mobile devices.\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc != 3)
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 
 | |
| 	ast_cli(a->fd, FORMAT1, "ID", "Address", "Group", "Adapter", "Connected", "State", "SMS");
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		ast_mutex_lock(&pvt->lock);
 | |
| 		ba2str(&pvt->addr, bdaddr);
 | |
| 		snprintf(group, sizeof(group), "%d", pvt->group);
 | |
| 		ast_cli(a->fd, FORMAT1,
 | |
| 				pvt->id,
 | |
| 				bdaddr,
 | |
| 				group,
 | |
| 				pvt->adapter->id,
 | |
| 				pvt->connected ? "Yes" : "No",
 | |
| 				(!pvt->connected) ? "None" : (pvt->owner) ? "Busy" : (pvt->outgoing_sms || pvt->incoming_sms) ? "SMS" : (mbl_has_service(pvt)) ? "Free" : "No Service",
 | |
| 				(pvt->has_sms) ? "Yes" : "No"
 | |
| 		       );
 | |
| 		ast_mutex_unlock(&pvt->lock);
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 
 | |
| #undef FORMAT1
 | |
| 
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| static char *handle_cli_mobile_search(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	struct adapter_pvt *adapter;
 | |
| 	inquiry_info *ii = NULL;
 | |
| 	int max_rsp, num_rsp;
 | |
| 	int len, flags;
 | |
| 	int i, phport, hsport;
 | |
| 	char addr[19] = {0};
 | |
| 	char name[31] = {0};
 | |
| 
 | |
| #define FORMAT1 "%-17.17s %-30.30s %-6.6s %-7.7s %-4.4s\n"
 | |
| #define FORMAT2 "%-17.17s %-30.30s %-6.6s %-7.7s %d\n"
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "mobile search";
 | |
| 		e->usage =
 | |
| 			"Usage: mobile search\n"
 | |
| 			"       Searches for Bluetooth Cell / Mobile devices in range.\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc != 2)
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 
 | |
| 	/* find a free adapter */
 | |
| 	AST_RWLIST_RDLOCK(&adapters);
 | |
| 	AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
 | |
| 		if (!adapter->inuse)
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&adapters);
 | |
| 
 | |
| 	if (!adapter) {
 | |
| 		ast_cli(a->fd, "All Bluetooth adapters are in use at this time.\n");
 | |
| 		return CLI_SUCCESS;
 | |
| 	}
 | |
| 
 | |
| 	len  = 8;
 | |
| 	max_rsp = 255;
 | |
| 	flags = IREQ_CACHE_FLUSH;
 | |
| 
 | |
| 	ii = ast_alloca(max_rsp * sizeof(inquiry_info));
 | |
| 	num_rsp = hci_inquiry(adapter->dev_id, len, max_rsp, NULL, &ii, flags);
 | |
| 	if (num_rsp > 0) {
 | |
| 		ast_cli(a->fd, FORMAT1, "Address", "Name", "Usable", "Type", "Port");
 | |
| 		for (i = 0; i < num_rsp; i++) {
 | |
| 			ba2str(&(ii + i)->bdaddr, addr);
 | |
| 			name[0] = 0x00;
 | |
| 			if (hci_read_remote_name(adapter->hci_socket, &(ii + i)->bdaddr, sizeof(name) - 1, name, 0) < 0)
 | |
| 				strcpy(name, "[unknown]");
 | |
| 			phport = sdp_search(addr, HANDSFREE_AGW_PROFILE_ID);
 | |
| 			if (!phport)
 | |
| 				hsport = sdp_search(addr, HEADSET_PROFILE_ID);
 | |
| 			else
 | |
| 				hsport = 0;
 | |
| 			ast_cli(a->fd, FORMAT2, addr, name, (phport > 0 || hsport > 0) ? "Yes" : "No",
 | |
| 				(phport > 0) ? "Phone" : "Headset", (phport > 0) ? phport : hsport);
 | |
| 		}
 | |
| 	} else
 | |
| 		ast_cli(a->fd, "No Bluetooth Cell / Mobile devices found.\n");
 | |
| 
 | |
| #undef FORMAT1
 | |
| #undef FORMAT2
 | |
| 
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| static char *handle_cli_mobile_rfcomm(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	char buf[128];
 | |
| 	struct mbl_pvt *pvt = NULL;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "mobile rfcomm";
 | |
| 		e->usage =
 | |
| 			"Usage: mobile rfcomm <device ID> <command>\n"
 | |
| 			"       Send <command> to the rfcomm port on the device\n"
 | |
| 			"       with the specified <device ID>.\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc != 4)
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		if (!strcmp(pvt->id, a->argv[2]))
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 
 | |
| 	if (!pvt) {
 | |
| 		ast_cli(a->fd, "Device %s not found.\n", a->argv[2]);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (!pvt->connected) {
 | |
| 		ast_cli(a->fd, "Device %s not connected.\n", a->argv[2]);
 | |
| 		goto e_unlock_pvt;
 | |
| 	}
 | |
| 
 | |
| 	snprintf(buf, sizeof(buf), "%s\r", a->argv[3]);
 | |
| 	rfcomm_write(pvt->rfcomm_socket, buf);
 | |
| 	msg_queue_push(pvt, AT_OK, AT_UNKNOWN);
 | |
| 
 | |
| e_unlock_pvt:
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| e_return:
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| static char *handle_cli_mobile_cusd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | |
| {
 | |
| 	char buf[128];
 | |
| 	struct mbl_pvt *pvt = NULL;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case CLI_INIT:
 | |
| 		e->command = "mobile cusd";
 | |
| 		e->usage =
 | |
| 			"Usage: mobile cusd <device ID> <command>\n"
 | |
| 			"       Send cusd <command> to the rfcomm port on the device\n"
 | |
| 			"       with the specified <device ID>.\n";
 | |
| 		return NULL;
 | |
| 	case CLI_GENERATE:
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (a->argc != 4)
 | |
| 		return CLI_SHOWUSAGE;
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		if (!strcmp(pvt->id, a->argv[2]))
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 
 | |
| 	if (!pvt) {
 | |
| 		ast_cli(a->fd, "Device %s not found.\n", a->argv[2]);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (!pvt->connected) {
 | |
| 		ast_cli(a->fd, "Device %s not connected.\n", a->argv[2]);
 | |
| 		goto e_unlock_pvt;
 | |
| 	}
 | |
| 
 | |
| 	snprintf(buf, sizeof(buf), "%s", a->argv[3]);
 | |
| 	if (hfp_send_cusd(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CUSD)) {
 | |
| 		ast_cli(a->fd, "[%s] error sending CUSD\n", pvt->id);
 | |
| 		goto e_unlock_pvt;
 | |
| 	}
 | |
| 
 | |
| e_unlock_pvt:
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| e_return:
 | |
| 	return CLI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	Dialplan applications implementation
 | |
| 
 | |
| */
 | |
| 
 | |
| static int mbl_status_exec(struct ast_channel *ast, const char *data)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	char *parse;
 | |
| 	int stat;
 | |
| 	char status[2];
 | |
| 
 | |
| 	AST_DECLARE_APP_ARGS(args,
 | |
| 		AST_APP_ARG(device);
 | |
| 		AST_APP_ARG(variable);
 | |
| 	);
 | |
| 
 | |
| 	if (ast_strlen_zero(data))
 | |
| 		return -1;
 | |
| 
 | |
| 	parse = ast_strdupa(data);
 | |
| 
 | |
| 	AST_STANDARD_APP_ARGS(args, parse);
 | |
| 
 | |
| 	if (ast_strlen_zero(args.device) || ast_strlen_zero(args.variable))
 | |
| 		return -1;
 | |
| 
 | |
| 	stat = 1;
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		if (!strcmp(pvt->id, args.device))
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 
 | |
| 	if (pvt) {
 | |
| 		ast_mutex_lock(&pvt->lock);
 | |
| 		if (pvt->connected)
 | |
| 			stat = 2;
 | |
| 		if (pvt->owner)
 | |
| 			stat = 3;
 | |
| 		ast_mutex_unlock(&pvt->lock);
 | |
| 	}
 | |
| 
 | |
| 	snprintf(status, sizeof(status), "%d", stat);
 | |
| 	pbx_builtin_setvar_helper(ast, args.variable, status);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_sendsms_exec(struct ast_channel *ast, const char *data)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	char *parse, *message;
 | |
| 
 | |
| 	AST_DECLARE_APP_ARGS(args,
 | |
| 		AST_APP_ARG(device);
 | |
| 		AST_APP_ARG(dest);
 | |
| 		AST_APP_ARG(message);
 | |
| 	);
 | |
| 
 | |
| 	if (ast_strlen_zero(data))
 | |
| 		return -1;
 | |
| 
 | |
| 	parse = ast_strdupa(data);
 | |
| 
 | |
| 	AST_STANDARD_APP_ARGS(args, parse);
 | |
| 
 | |
| 	if (ast_strlen_zero(args.device)) {
 | |
| 		ast_log(LOG_ERROR,"NULL device for message -- SMS will not be sent.\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_strlen_zero(args.dest)) {
 | |
| 		ast_log(LOG_ERROR,"NULL destination for message -- SMS will not be sent.\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_strlen_zero(args.message)) {
 | |
| 		ast_log(LOG_ERROR,"NULL Message to be sent -- SMS will not be sent.\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		if (!strcmp(pvt->id, args.device))
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 
 | |
| 	if (!pvt) {
 | |
| 		ast_log(LOG_ERROR,"Bluetooth device %s wasn't found in the list -- SMS will not be sent.\n", args.device);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (!pvt->connected) {
 | |
| 		ast_log(LOG_ERROR,"Bluetooth device %s wasn't connected -- SMS will not be sent.\n", args.device);
 | |
| 		goto e_unlock_pvt;
 | |
| 	}
 | |
| 
 | |
| 	if (!pvt->has_sms) {
 | |
| 		ast_log(LOG_ERROR,"Bluetooth device %s doesn't handle SMS -- SMS will not be sent.\n", args.device);
 | |
| 		goto e_unlock_pvt;
 | |
| 	}
 | |
| 
 | |
| 	message = ast_strdup(args.message);
 | |
| 
 | |
| 	if (hfp_send_cmgs(pvt->hfp, args.dest)
 | |
| 		|| msg_queue_push_data(pvt, AT_SMS_PROMPT, AT_CMGS, message)) {
 | |
| 
 | |
| 		ast_log(LOG_ERROR, "[%s] problem sending SMS message\n", pvt->id);
 | |
| 		goto e_free_message;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| e_free_message:
 | |
| 	ast_free(message);
 | |
| e_unlock_pvt:
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| e_return:
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	Channel Driver callbacks
 | |
| 
 | |
| */
 | |
| 
 | |
| static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, struct cidinfo *cidinfo,
 | |
| 		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor)
 | |
| {
 | |
| 	struct ast_channel *chn;
 | |
| 
 | |
| 	pvt->answered = 0;
 | |
| 	pvt->alignment_count = 0;
 | |
| 	pvt->alignment_detection_triggered = 0;
 | |
| 	if (pvt->adapter->alignment_detection)
 | |
| 		pvt->do_alignment_detection = 1;
 | |
| 	else
 | |
| 		pvt->do_alignment_detection = 0;
 | |
| 
 | |
| 	ast_smoother_reset(pvt->bt_out_smoother, DEVICE_FRAME_SIZE);
 | |
| 	ast_smoother_reset(pvt->bt_in_smoother, CHANNEL_FRAME_SIZE);
 | |
| 	ast_dsp_digitreset(pvt->dsp);
 | |
| 
 | |
| 	chn = ast_channel_alloc(1, state,
 | |
| 		cidinfo ? cidinfo->cnum : NULL,
 | |
| 		cidinfo ? cidinfo->cnam : NULL,
 | |
| 		0, 0, pvt->context, assignedids, requestor, 0,
 | |
| 		"Mobile/%s-%04lx", pvt->id, ast_random() & 0xffff);
 | |
| 	if (!chn) {
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	ast_channel_tech_set(chn, &mbl_tech);
 | |
| 	ast_channel_nativeformats_set(chn, mbl_tech.capabilities);
 | |
| 	ast_channel_set_rawreadformat(chn, DEVICE_FRAME_FORMAT);
 | |
| 	ast_channel_set_rawwriteformat(chn, DEVICE_FRAME_FORMAT);
 | |
| 	ast_channel_set_writeformat(chn, DEVICE_FRAME_FORMAT);
 | |
| 	ast_channel_set_readformat(chn, DEVICE_FRAME_FORMAT);
 | |
| 	ast_channel_tech_pvt_set(chn, pvt);
 | |
| 
 | |
| 	if (state == AST_STATE_RING)
 | |
| 		ast_channel_rings_set(chn, 1);
 | |
| 
 | |
| 	ast_channel_language_set(chn, "en");
 | |
| 	pvt->owner = chn;
 | |
| 
 | |
| 	if (pvt->sco_socket != -1) {
 | |
| 		ast_channel_set_fd(chn, 0, pvt->sco_socket);
 | |
| 	}
 | |
| 	ast_channel_unlock(chn);
 | |
| 
 | |
| 	return chn;
 | |
| 
 | |
| e_return:
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static struct ast_channel *mbl_request(const char *type, struct ast_format_cap *cap,
 | |
| 		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
 | |
| {
 | |
| 
 | |
| 	struct ast_channel *chn = NULL;
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	char *dest_dev = NULL;
 | |
| 	char *dest_num = NULL;
 | |
| 	int group = -1;
 | |
| 
 | |
| 	if (!data) {
 | |
| 		ast_log(LOG_WARNING, "Channel requested with no data\n");
 | |
| 		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_format_cap_iscompatible_format(cap, DEVICE_FRAME_FORMAT) == AST_FORMAT_CMP_NOT_EQUAL) {
 | |
| 		struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
 | |
| 		ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%s'\n", ast_format_cap_get_names(cap, &codec_buf));
 | |
| 		*cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	dest_dev = ast_strdupa(data);
 | |
| 
 | |
| 	dest_num = strchr(dest_dev, '/');
 | |
| 	if (dest_num)
 | |
| 		*dest_num++ = 0x00;
 | |
| 
 | |
| 	if (((dest_dev[0] == 'g') || (dest_dev[0] == 'G')) && ((dest_dev[1] >= '0') && (dest_dev[1] <= '9'))) {
 | |
| 		group = atoi(&dest_dev[1]);
 | |
| 	}
 | |
| 
 | |
| 	/* Find requested device and make sure it's connected. */
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		if (group > -1 && pvt->group == group && pvt->connected && !pvt->owner) {
 | |
| 			if (!mbl_has_service(pvt)) {
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			break;
 | |
| 		} else if (!strcmp(pvt->id, dest_dev)) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 	if (!pvt || !pvt->connected || pvt->owner) {
 | |
| 		ast_log(LOG_WARNING, "Request to call on device %s which is not connected / already in use.\n", dest_dev);
 | |
| 		*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if ((pvt->type == MBL_TYPE_PHONE) && !dest_num) {
 | |
| 		ast_log(LOG_WARNING, "Can't determine destination number.\n");
 | |
| 		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	chn = mbl_new(AST_STATE_DOWN, pvt, NULL, assignedids, requestor);
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 	if (!chn) {
 | |
| 		ast_log(LOG_WARNING, "Unable to allocate channel structure.\n");
 | |
| 		*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	return chn;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_call(struct ast_channel *ast, const char *dest, int timeout)
 | |
| {
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	char *dest_dev;
 | |
| 	char *dest_num = NULL;
 | |
| 
 | |
| 	dest_dev = ast_strdupa(dest);
 | |
| 
 | |
| 	pvt = ast_channel_tech_pvt(ast);
 | |
| 
 | |
| 	if (pvt->type == MBL_TYPE_PHONE) {
 | |
| 		dest_num = strchr(dest_dev, '/');
 | |
| 		if (!dest_num) {
 | |
| 			ast_log(LOG_WARNING, "Cant determine destination number.\n");
 | |
| 			return -1;
 | |
| 		}
 | |
| 		*dest_num++ = 0x00;
 | |
| 	}
 | |
| 
 | |
| 	if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) {
 | |
| 		ast_log(LOG_WARNING, "mbl_call called on %s, neither down nor reserved\n", ast_channel_name(ast));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	ast_debug(1, "Calling %s on %s\n", dest, ast_channel_name(ast));
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (pvt->type == MBL_TYPE_PHONE) {
 | |
| 		if (hfp_send_atd(pvt->hfp, dest_num)) {
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			ast_log(LOG_ERROR, "error sending ATD command on %s\n", pvt->id);
 | |
| 			return -1;
 | |
| 		}
 | |
| 		pvt->hangupcause = 0;
 | |
| 		pvt->needchup = 1;
 | |
| 		msg_queue_push(pvt, AT_OK, AT_D);
 | |
| 	} else {
 | |
| 		if (hsp_send_ring(pvt->rfcomm_socket)) {
 | |
| 			ast_log(LOG_ERROR, "[%s] error ringing device\n", pvt->id);
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		if ((pvt->ring_sched_id = ast_sched_add(pvt->sched, 6000, headset_send_ring, pvt)) == -1) {
 | |
| 			ast_log(LOG_ERROR, "[%s] error ringing device\n", pvt->id);
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		pvt->outgoing = 1;
 | |
| 		pvt->needring = 1;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_hangup(struct ast_channel *ast)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt;
 | |
| 
 | |
| 	if (!ast_channel_tech_pvt(ast)) {
 | |
| 		ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
 | |
| 		return 0;
 | |
| 	}
 | |
| 	pvt = ast_channel_tech_pvt(ast);
 | |
| 
 | |
| 	ast_debug(1, "[%s] hanging up device\n", pvt->id);
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	ast_channel_set_fd(ast, 0, -1);
 | |
| 	close(pvt->sco_socket);
 | |
| 	pvt->sco_socket = -1;
 | |
| 
 | |
| 	if (pvt->needchup) {
 | |
| 		hfp_send_chup(pvt->hfp);
 | |
| 		msg_queue_push(pvt, AT_OK, AT_CHUP);
 | |
| 		pvt->needchup = 0;
 | |
| 	}
 | |
| 
 | |
| 	pvt->outgoing = 0;
 | |
| 	pvt->incoming = 0;
 | |
| 	pvt->needring = 0;
 | |
| 	pvt->owner = NULL;
 | |
| 	ast_channel_tech_pvt_set(ast, NULL);
 | |
| 
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	ast_setstate(ast, AST_STATE_DOWN);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_answer(struct ast_channel *ast)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt;
 | |
| 
 | |
| 	pvt = ast_channel_tech_pvt(ast);
 | |
| 
 | |
| 	if (pvt->type == MBL_TYPE_HEADSET)
 | |
| 		return 0;
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (pvt->incoming) {
 | |
| 		hfp_send_ata(pvt->hfp);
 | |
| 		msg_queue_push(pvt, AT_OK, AT_A);
 | |
| 		pvt->answered = 1;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
 | |
| {
 | |
| 	struct mbl_pvt *pvt = ast_channel_tech_pvt(ast);
 | |
| 
 | |
| 	if (pvt->type == MBL_TYPE_HEADSET)
 | |
| 		return 0;
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (hfp_send_dtmf(pvt->hfp, digit)) {
 | |
| 		ast_mutex_unlock(&pvt->lock);
 | |
| 		ast_debug(1, "[%s] error sending digit %c\n", pvt->id, digit);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	msg_queue_push(pvt, AT_OK, AT_VTS);
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	ast_debug(1, "[%s] dialed %c\n", pvt->id, digit);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct ast_frame *mbl_read(struct ast_channel *ast)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt = ast_channel_tech_pvt(ast);
 | |
| 	struct ast_frame *fr = &ast_null_frame;
 | |
| 	int r;
 | |
| 
 | |
| 	ast_debug(3, "*** mbl_read()\n");
 | |
| 
 | |
| 	while (ast_mutex_trylock(&pvt->lock)) {
 | |
| 		CHANNEL_DEADLOCK_AVOIDANCE(ast);
 | |
| 	}
 | |
| 
 | |
| 	if (!pvt->owner || pvt->sco_socket == -1) {
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	memset(&pvt->fr, 0x00, sizeof(struct ast_frame));
 | |
| 	pvt->fr.frametype = AST_FRAME_VOICE;
 | |
| 	pvt->fr.subclass.format = DEVICE_FRAME_FORMAT;
 | |
| 	pvt->fr.src = "Mobile";
 | |
| 	pvt->fr.offset = AST_FRIENDLY_OFFSET;
 | |
| 	pvt->fr.mallocd = 0;
 | |
| 	pvt->fr.delivery.tv_sec = 0;
 | |
| 	pvt->fr.delivery.tv_usec = 0;
 | |
| 	pvt->fr.data.ptr = pvt->io_buf + AST_FRIENDLY_OFFSET;
 | |
| 
 | |
| 	do {
 | |
| 		if ((r = read(pvt->sco_socket, pvt->fr.data.ptr, DEVICE_FRAME_SIZE)) == -1) {
 | |
| 			if (errno != EAGAIN && errno != EINTR) {
 | |
| 				ast_debug(1, "[%s] read error %d, going to wait for new connection\n", pvt->id, errno);
 | |
| 				close(pvt->sco_socket);
 | |
| 				pvt->sco_socket = -1;
 | |
| 				ast_channel_set_fd(ast, 0, -1);
 | |
| 			}
 | |
| 			goto e_return;
 | |
| 		}
 | |
| 
 | |
| 		pvt->fr.datalen = r;
 | |
| 		pvt->fr.samples = r / 2;
 | |
| 
 | |
| 		if (pvt->do_alignment_detection)
 | |
| 			do_alignment_detection(pvt, pvt->fr.data.ptr, r);
 | |
| 
 | |
| 		ast_smoother_feed(pvt->bt_in_smoother, &pvt->fr);
 | |
| 		fr = ast_smoother_read(pvt->bt_in_smoother);
 | |
| 	} while (fr == NULL);
 | |
| 	fr = ast_dsp_process(ast, pvt->dsp, fr);
 | |
| 
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return fr;
 | |
| 
 | |
| e_return:
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 	return fr;
 | |
| }
 | |
| 
 | |
| static int mbl_write(struct ast_channel *ast, struct ast_frame *frame)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt = ast_channel_tech_pvt(ast);
 | |
| 	struct ast_frame *f;
 | |
| 
 | |
| 	ast_debug(3, "*** mbl_write\n");
 | |
| 
 | |
| 	if (frame->frametype != AST_FRAME_VOICE) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	while (ast_mutex_trylock(&pvt->lock)) {
 | |
| 		CHANNEL_DEADLOCK_AVOIDANCE(ast);
 | |
| 	}
 | |
| 
 | |
| 	ast_smoother_feed(pvt->bt_out_smoother, frame);
 | |
| 
 | |
| 	while ((f = ast_smoother_read(pvt->bt_out_smoother))) {
 | |
| 		sco_write(pvt->sco_socket, f->data.ptr, f->datalen);
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt = ast_channel_tech_pvt(newchan);
 | |
| 
 | |
| 	if (!pvt) {
 | |
| 		ast_debug(1, "fixup failed, no pvt on newchan\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (pvt->owner == oldchan)
 | |
| 		pvt->owner = newchan;
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_devicestate(const char *data)
 | |
| {
 | |
| 
 | |
| 	char *device;
 | |
| 	int res = AST_DEVICE_INVALID;
 | |
| 	struct mbl_pvt *pvt;
 | |
| 
 | |
| 	device = ast_strdupa(S_OR(data, ""));
 | |
| 
 | |
| 	ast_debug(1, "Checking device state for device %s\n", device);
 | |
| 
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		if (!strcmp(pvt->id, device))
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 
 | |
| 	if (!pvt)
 | |
| 		return res;
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (pvt->connected) {
 | |
| 		if (pvt->owner)
 | |
| 			res = AST_DEVICE_INUSE;
 | |
| 		else
 | |
| 			res = AST_DEVICE_NOT_INUSE;
 | |
| 
 | |
| 		if (!mbl_has_service(pvt))
 | |
| 			res = AST_DEVICE_UNAVAILABLE;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return res;
 | |
| 
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	Callback helpers
 | |
| 
 | |
| */
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	do_alignment_detection()
 | |
| 
 | |
| 	This routine attempts to detect where we get misaligned sco audio data from the bluetooth adaptor.
 | |
| 
 | |
| 	Its enabled by alignmentdetect=yes under the adapter entry in mobile.conf
 | |
| 
 | |
| 	Some adapters suffer a problem where occasionally they will byte shift the audio stream one byte to the right.
 | |
| 	The result is static or white noise on the inbound (from the adapter) leg of the call.
 | |
| 	This is characterised by a sudden jump in magnitude of the value of the 16 bit samples.
 | |
| 
 | |
| 	Here we look at the first 4 48 byte frames. We average the absolute values of each sample in the frame,
 | |
| 	then average the sum of the averages of frames 1, 2, and 3.
 | |
| 	Frame zero is usually zero.
 | |
| 	If the end result > 100, and it usually is if we have the problem, set a flag and compensate by shifting the bytes
 | |
| 	for each subsequent frame during the call.
 | |
| 
 | |
| 	If the result is <= 100 then clear the flag so we don't come back in here...
 | |
| 
 | |
| 	This seems to work OK....
 | |
| 
 | |
| */
 | |
| 
 | |
| static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen)
 | |
| {
 | |
| 
 | |
| 	int i;
 | |
| 	short a, *s;
 | |
| 	char *p;
 | |
| 
 | |
| 	if (pvt->alignment_detection_triggered) {
 | |
| 		for (i=buflen, p=buf+buflen-1; i>0; i--, p--)
 | |
| 			*p = *(p-1);
 | |
| 		*(p+1) = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (pvt->alignment_count < 4) {
 | |
| 		s = (short *)buf;
 | |
| 		for (i=0, a=0; i<buflen/2; i++) {
 | |
| 			a += *s++;
 | |
| 			a /= i+1;
 | |
| 		}
 | |
| 		pvt->alignment_samples[pvt->alignment_count++] = a;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	ast_debug(1, "Alignment Detection result is [%-d %-d %-d %-d]\n", pvt->alignment_samples[0], pvt->alignment_samples[1], pvt->alignment_samples[2], pvt->alignment_samples[3]);
 | |
| 
 | |
| 	a = abs(pvt->alignment_samples[1]) + abs(pvt->alignment_samples[2]) + abs(pvt->alignment_samples[3]);
 | |
| 	a /= 3;
 | |
| 	if (a > 100) {
 | |
| 		pvt->alignment_detection_triggered = 1;
 | |
| 		ast_debug(1, "Alignment Detection Triggered.\n");
 | |
| 	} else
 | |
| 		pvt->do_alignment_detection = 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int mbl_queue_control(struct mbl_pvt *pvt, enum ast_control_frame_type control)
 | |
| {
 | |
| 	for (;;) {
 | |
| 		if (pvt->owner) {
 | |
| 			if (ast_channel_trylock(pvt->owner)) {
 | |
| 				DEADLOCK_AVOIDANCE(&pvt->lock);
 | |
| 			} else {
 | |
| 				ast_queue_control(pvt->owner, control);
 | |
| 				ast_channel_unlock(pvt->owner);
 | |
| 				break;
 | |
| 			}
 | |
| 		} else
 | |
| 			break;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mbl_queue_hangup(struct mbl_pvt *pvt)
 | |
| {
 | |
| 	for (;;) {
 | |
| 		if (pvt->owner) {
 | |
| 			if (ast_channel_trylock(pvt->owner)) {
 | |
| 				DEADLOCK_AVOIDANCE(&pvt->lock);
 | |
| 			} else {
 | |
| 				if (pvt->hangupcause != 0) {
 | |
| 					ast_channel_hangupcause_set(pvt->owner, pvt->hangupcause);
 | |
| 				}
 | |
| 				ast_queue_hangup(pvt->owner);
 | |
| 				ast_channel_unlock(pvt->owner);
 | |
| 				break;
 | |
| 			}
 | |
| 		} else
 | |
| 			break;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mbl_ast_hangup(struct mbl_pvt *pvt)
 | |
| {
 | |
| 	ast_hangup(pvt->owner);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Check if a mobile device has service.
 | |
|  * \param pvt a mbl_pvt struct
 | |
|  * \retval 1 this device has service
 | |
|  * \retval 0 no service
 | |
|  *
 | |
|  * \note This function will always indicate that service is available if the
 | |
|  * given device does not support service indication.
 | |
|  */
 | |
| static int mbl_has_service(struct mbl_pvt *pvt)
 | |
| {
 | |
| 
 | |
| 	if (pvt->type != MBL_TYPE_PHONE)
 | |
| 		return 1;
 | |
| 
 | |
| 	if (!pvt->hfp->cind_map.service)
 | |
| 		return 1;
 | |
| 
 | |
| 	if (pvt->hfp->cind_state[pvt->hfp->cind_map.service] == HFP_CIND_SERVICE_AVAILABLE)
 | |
| 		return 1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	rfcomm helpers
 | |
| 
 | |
| */
 | |
| 
 | |
| static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel)
 | |
| {
 | |
| 
 | |
| 	struct sockaddr_rc addr;
 | |
| 	int s;
 | |
| 
 | |
| 	if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
 | |
| 		ast_debug(1, "socket() failed (%d).\n", errno);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	memset(&addr, 0, sizeof(addr));
 | |
| 	addr.rc_family = AF_BLUETOOTH;
 | |
| 	bacpy(&addr.rc_bdaddr, &src);
 | |
| 	addr.rc_channel = (uint8_t) 0;
 | |
| 	if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
 | |
| 		ast_debug(1, "bind() failed (%d).\n", errno);
 | |
| 		close(s);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	memset(&addr, 0, sizeof(addr));
 | |
| 	addr.rc_family = AF_BLUETOOTH;
 | |
| 	bacpy(&addr.rc_bdaddr, &dst);
 | |
| 	addr.rc_channel = remote_channel;
 | |
| 	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
 | |
| 		ast_debug(1, "connect() failed (%d).\n", errno);
 | |
| 		close(s);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return s;
 | |
| 
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Write to an rfcomm socket.
 | |
|  * \param rsock the socket to write to
 | |
|  * \param buf the null terminated buffer to write
 | |
|  *
 | |
|  * This function will write characters from buf.  The buffer must be null
 | |
|  * terminated.
 | |
|  *
 | |
|  * \retval -1 error
 | |
|  * \retval 0 success
 | |
|  */
 | |
| static int rfcomm_write(int rsock, char *buf)
 | |
| {
 | |
| 	return rfcomm_write_full(rsock, buf, strlen(buf));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*!
 | |
|  * \brief Write to an rfcomm socket.
 | |
|  * \param rsock the socket to write to
 | |
|  * \param buf the buffer to write
 | |
|  * \param count the number of characters from the buffer to write
 | |
|  *
 | |
|  * This function will write count characters from buf.  It will always write
 | |
|  * count chars unless it encounters an error.
 | |
|  *
 | |
|  * \retval -1 error
 | |
|  * \retval 0 success
 | |
|  */
 | |
| static int rfcomm_write_full(int rsock, char *buf, size_t count)
 | |
| {
 | |
| 	char *p = buf;
 | |
| 	ssize_t out_count;
 | |
| 
 | |
| 	ast_debug(1, "rfcomm_write() (%d) [%.*s]\n", rsock, (int) count, buf);
 | |
| 	while (count > 0) {
 | |
| 		if ((out_count = write(rsock, p, count)) == -1) {
 | |
| 			ast_debug(1, "rfcomm_write() error [%d]\n", errno);
 | |
| 			return -1;
 | |
| 		}
 | |
| 		count -= out_count;
 | |
| 		p += out_count;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Wait for activity on an rfcomm socket.
 | |
|  * \param rsock the socket to watch
 | |
|  * \param ms a pointer to an int containing a timeout in ms
 | |
|  * \return zero on timeout and the socket fd (non-zero) otherwise
 | |
|  * \retval 0 timeout
 | |
|  */
 | |
| static int rfcomm_wait(int rsock, int *ms)
 | |
| {
 | |
| 	int exception, outfd;
 | |
| 	outfd = ast_waitfor_n_fd(&rsock, 1, ms, &exception);
 | |
| 	if (outfd < 0)
 | |
| 		outfd = 0;
 | |
| 
 | |
| 	return outfd;
 | |
| }
 | |
| 
 | |
| #ifdef RFCOMM_READ_DEBUG
 | |
| #define rfcomm_read_debug(c) __rfcomm_read_debug(c)
 | |
| static void __rfcomm_read_debug(char c)
 | |
| {
 | |
| 	if (c == '\r')
 | |
| 		ast_debug(2, "rfcomm_read: \\r\n");
 | |
| 	else if (c == '\n')
 | |
| 		ast_debug(2, "rfcomm_read: \\n\n");
 | |
| 	else
 | |
| 		ast_debug(2, "rfcomm_read: %c\n", c);
 | |
| }
 | |
| #else
 | |
| #define rfcomm_read_debug(c)
 | |
| #endif
 | |
| 
 | |
| /*!
 | |
|  * \brief Append the given character to the given buffer and increase the
 | |
|  * in_count.
 | |
|  */
 | |
| static void inline rfcomm_append_buf(char **buf, size_t count, size_t *in_count, char c)
 | |
| {
 | |
| 	if (*in_count < count) {
 | |
| 		(*in_count)++;
 | |
| 		*(*buf)++ = c;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read a character from the given stream and check if it matches what
 | |
|  * we expected.
 | |
|  */
 | |
| static int rfcomm_read_and_expect_char(int rsock, char *result, char expected)
 | |
| {
 | |
| 	int res;
 | |
| 	char c;
 | |
| 
 | |
| 	if (!result)
 | |
| 		result = &c;
 | |
| 
 | |
| 	if ((res = read(rsock, result, 1)) < 1) {
 | |
| 		return res;
 | |
| 	}
 | |
| 	rfcomm_read_debug(*result);
 | |
| 
 | |
| 	if (*result != expected) {
 | |
| 		return -2;
 | |
| 	}
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read a character from the given stream and append it to the given
 | |
|  * buffer if it matches the expected character.
 | |
|  */
 | |
| static int rfcomm_read_and_append_char(int rsock, char **buf, size_t count, size_t *in_count, char *result, char expected)
 | |
| {
 | |
| 	int res;
 | |
| 	char c;
 | |
| 
 | |
| 	if (!result)
 | |
| 		result = &c;
 | |
| 
 | |
| 	if ((res = rfcomm_read_and_expect_char(rsock, result, expected)) < 1) {
 | |
| 		return res;
 | |
| 	}
 | |
| 
 | |
| 	rfcomm_append_buf(buf, count, in_count, *result);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read until \verbatim '\r\n'. \endverbatim
 | |
|  * This function consumes the \verbatim'\r\n'\endverbatim but does not add it to buf.
 | |
|  */
 | |
| static int rfcomm_read_until_crlf(int rsock, char **buf, size_t count, size_t *in_count)
 | |
| {
 | |
| 	int res;
 | |
| 	char c;
 | |
| 
 | |
| 	while ((res = read(rsock, &c, 1)) == 1) {
 | |
| 		rfcomm_read_debug(c);
 | |
| 		if (c == '\r') {
 | |
| 			if ((res = rfcomm_read_and_expect_char(rsock, &c, '\n')) == 1) {
 | |
| 				break;
 | |
| 			} else if (res == -2) {
 | |
| 				rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 			} else {
 | |
| 				rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		rfcomm_append_buf(buf, count, in_count, c);
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read the remainder of an AT SMS prompt.
 | |
|  * \note the entire parsed string is \verbatim '\r\n> ' \endverbatim
 | |
|  *
 | |
|  * By the time this function is executed, only a ' ' is left to read.
 | |
|  */
 | |
| static int rfcomm_read_sms_prompt(int rsock, char **buf, size_t count, size_t *in_count)
 | |
| {
 | |
| 	int res;
 | |
| 	if ((res = rfcomm_read_and_append_char(rsock, buf, count, in_count, NULL, ' ')) < 1)
 | |
| 	       goto e_return;
 | |
| 
 | |
| 	return 1;
 | |
| 
 | |
| e_return:
 | |
| 	ast_log(LOG_ERROR, "error parsing SMS prompt on rfcomm socket\n");
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read until a \verbatim \r\nOK\r\n \endverbatim message.
 | |
|  */
 | |
| static int rfcomm_read_until_ok(int rsock, char **buf, size_t count, size_t *in_count)
 | |
| {
 | |
| 	int res;
 | |
| 	char c;
 | |
| 
 | |
| 	/* here, we read until finding a \r\n, then we read one character at a
 | |
| 	 * time looking for the string '\r\nOK\r\n'.  If we only find a partial
 | |
| 	 * match, we place that in the buffer and try again. */
 | |
| 
 | |
| 	for (;;) {
 | |
| 		if ((res = rfcomm_read_until_crlf(rsock, buf, count, in_count)) != 1) {
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 		rfcomm_append_buf(buf, count, in_count, '\n');
 | |
| 
 | |
| 		if ((res = rfcomm_read_and_expect_char(rsock, &c, '\r')) != 1) {
 | |
| 			if (res != -2) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			rfcomm_append_buf(buf, count, in_count, c);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if ((res = rfcomm_read_and_expect_char(rsock, &c, '\n')) != 1) {
 | |
| 			if (res != -2) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 			rfcomm_append_buf(buf, count, in_count, c);
 | |
| 			continue;
 | |
| 		}
 | |
| 		if ((res = rfcomm_read_and_expect_char(rsock, &c, 'O')) != 1) {
 | |
| 			if (res != -2) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\n');
 | |
| 			rfcomm_append_buf(buf, count, in_count, c);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if ((res = rfcomm_read_and_expect_char(rsock, &c, 'K')) != 1) {
 | |
| 			if (res != -2) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\n');
 | |
| 			rfcomm_append_buf(buf, count, in_count, 'O');
 | |
| 			rfcomm_append_buf(buf, count, in_count, c);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if ((res = rfcomm_read_and_expect_char(rsock, &c, '\r')) != 1) {
 | |
| 			if (res != -2) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\n');
 | |
| 			rfcomm_append_buf(buf, count, in_count, 'O');
 | |
| 			rfcomm_append_buf(buf, count, in_count, 'K');
 | |
| 			rfcomm_append_buf(buf, count, in_count, c);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if ((res = rfcomm_read_and_expect_char(rsock, &c, '\n')) != 1) {
 | |
| 			if (res != -2) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\n');
 | |
| 			rfcomm_append_buf(buf, count, in_count, 'O');
 | |
| 			rfcomm_append_buf(buf, count, in_count, 'K');
 | |
| 			rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 			rfcomm_append_buf(buf, count, in_count, c);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		/* we have successfully parsed a '\r\nOK\r\n' string */
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*!
 | |
|  * \brief Read the remainder of a +CMGR message.
 | |
|  * \note the entire parsed string is \verbatim '+CMGR: ...\r\n...\r\n...\r\n...\r\nOK\r\n' \endverbatim
 | |
|  */
 | |
| static int rfcomm_read_cmgr(int rsock, char **buf, size_t count, size_t *in_count)
 | |
| {
 | |
| 	int res;
 | |
| 
 | |
| 	/* append the \r\n that was stripped by the calling function */
 | |
| 	rfcomm_append_buf(buf, count, in_count, '\r');
 | |
| 	rfcomm_append_buf(buf, count, in_count, '\n');
 | |
| 
 | |
| 	if ((res = rfcomm_read_until_ok(rsock, buf, count, in_count)) != 1) {
 | |
| 		ast_log(LOG_ERROR, "error reading +CMGR message on rfcomm socket\n");
 | |
| 	}
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read and AT result code.
 | |
|  * \note the entire parsed string is \verbatim '\r\n<result code>\r\n' \endverbatim
 | |
|  */
 | |
| static int rfcomm_read_result(int rsock, char **buf, size_t count, size_t *in_count)
 | |
| {
 | |
| 	int res;
 | |
| 	char c;
 | |
| 
 | |
| 	if ((res = rfcomm_read_and_expect_char(rsock, &c, '\n')) < 1) {
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	if ((res = rfcomm_read_and_append_char(rsock, buf, count, in_count, &c, '>')) == 1) {
 | |
| 		return rfcomm_read_sms_prompt(rsock, buf, count, in_count);
 | |
| 	} else if (res != -2) {
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	rfcomm_append_buf(buf, count, in_count, c);
 | |
| 	res = rfcomm_read_until_crlf(rsock, buf, count, in_count);
 | |
| 
 | |
| 	if (res != 1)
 | |
| 		return res;
 | |
| 
 | |
| 	/* check for CMGR, which contains an embedded \r\n pairs terminated by
 | |
| 	 * an \r\nOK\r\n message */
 | |
| 	if (*in_count >= 5 && !strncmp(*buf - *in_count, "+CMGR", 5)) {
 | |
| 		return rfcomm_read_cmgr(rsock, buf, count, in_count);
 | |
| 	}
 | |
| 
 | |
| 	return 1;
 | |
| 
 | |
| e_return:
 | |
| 	ast_log(LOG_ERROR, "error parsing AT result on rfcomm socket\n");
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read the remainder of an AT command.
 | |
|  * \note the entire parsed string is \verbatim '<at command>\r' \endverbatim
 | |
|  */
 | |
| static int rfcomm_read_command(int rsock, char **buf, size_t count, size_t *in_count)
 | |
| {
 | |
| 	int res;
 | |
| 	char c;
 | |
| 
 | |
| 	while ((res = read(rsock, &c, 1)) == 1) {
 | |
| 		rfcomm_read_debug(c);
 | |
| 		/* stop when we get to '\r' */
 | |
| 		if (c == '\r')
 | |
| 			break;
 | |
| 
 | |
| 		rfcomm_append_buf(buf, count, in_count, c);
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read one Hayes AT message from an rfcomm socket.
 | |
|  * \param rsock the rfcomm socket to read from
 | |
|  * \param buf the buffer to store the result in
 | |
|  * \param count the size of the buffer or the maximum number of characters to read
 | |
|  *
 | |
|  * Here we need to read complete Hayes AT messages.  The AT message formats we
 | |
|  * support are listed below.
 | |
|  *
 | |
|  * \verbatim
 | |
|  * \r\n<result code>\r\n
 | |
|  * <at command>\r
 | |
|  * \r\n>
 | |
|  * \endverbatim
 | |
|  *
 | |
|  * These formats correspond to AT result codes, AT commands, and the AT SMS
 | |
|  * prompt respectively.  When messages are read the leading and trailing \verbatim '\r' \endverbatim
 | |
|  * and \verbatim '\n' \endverbatim characters are discarded.  If the given buffer is not large enough
 | |
|  * to hold the response, what does not fit in the buffer will be dropped.
 | |
|  *
 | |
|  * \note The rfcomm connection to the device is asynchronous, so there is no
 | |
|  * guarantee that responses will be returned in a single read() call. We handle
 | |
|  * this by blocking until we can read an entire response.
 | |
|  *
 | |
|  * \retval 0 end of file
 | |
|  * \retval -1 read error
 | |
|  * \retval -2 parse error
 | |
|  * \retval other the number of characters added to buf
 | |
|  */
 | |
| static ssize_t rfcomm_read(int rsock, char *buf, size_t count)
 | |
| {
 | |
| 	ssize_t res;
 | |
| 	size_t in_count = 0;
 | |
| 	char c;
 | |
| 
 | |
| 	if ((res = rfcomm_read_and_expect_char(rsock, &c, '\r')) == 1) {
 | |
| 		res = rfcomm_read_result(rsock, &buf, count, &in_count);
 | |
| 	} else if (res == -2) {
 | |
| 		rfcomm_append_buf(&buf, count, &in_count, c);
 | |
| 		res = rfcomm_read_command(rsock, &buf, count, &in_count);
 | |
| 	}
 | |
| 
 | |
| 	if (res < 1)
 | |
| 		return res;
 | |
| 	else
 | |
| 		return in_count;
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	sco helpers and callbacks
 | |
| 
 | |
| */
 | |
| 
 | |
| static int sco_connect(bdaddr_t src, bdaddr_t dst)
 | |
| {
 | |
| 
 | |
| 	struct sockaddr_sco addr;
 | |
| 	int s;
 | |
| 
 | |
| 	if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
 | |
| 		ast_debug(1, "socket() failed (%d).\n", errno);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| /* XXX this does not work with the do_sco_listen() thread (which also bind()s
 | |
|  * to this address).  Also I am not sure if it is necessary. */
 | |
| #if 0
 | |
| 	memset(&addr, 0, sizeof(addr));
 | |
| 	addr.sco_family = AF_BLUETOOTH;
 | |
| 	bacpy(&addr.sco_bdaddr, &src);
 | |
| 	if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
 | |
| 		ast_debug(1, "bind() failed (%d).\n", errno);
 | |
| 		close(s);
 | |
| 		return -1;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	memset(&addr, 0, sizeof(addr));
 | |
| 	addr.sco_family = AF_BLUETOOTH;
 | |
| 	bacpy(&addr.sco_bdaddr, &dst);
 | |
| 
 | |
| 	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
 | |
| 		ast_debug(1, "sco connect() failed (%d).\n", errno);
 | |
| 		close(s);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return s;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int sco_write(int s, char *buf, int len)
 | |
| {
 | |
| 
 | |
| 	int r;
 | |
| 
 | |
| 	if (s == -1) {
 | |
| 		ast_debug(3, "sco_write() not ready\n");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	ast_debug(3, "sco_write()\n");
 | |
| 
 | |
| 	r = write(s, buf, len);
 | |
| 	if (r == -1) {
 | |
| 		ast_debug(3, "sco write error %d\n", errno);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	return 1;
 | |
| 
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Accept SCO connections.
 | |
|  * This function is an ast_io callback function used to accept incoming sco
 | |
|  * audio connections.
 | |
|  */
 | |
| static int sco_accept(int *id, int fd, short events, void *data)
 | |
| {
 | |
| 	struct adapter_pvt *adapter = (struct adapter_pvt *) data;
 | |
| 	struct sockaddr_sco addr;
 | |
| 	socklen_t addrlen;
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	socklen_t len;
 | |
| 	char saddr[18];
 | |
| 	struct sco_options so;
 | |
| 	int sock;
 | |
| 
 | |
| 	addrlen = sizeof(struct sockaddr_sco);
 | |
| 	if ((sock = accept(fd, (struct sockaddr *)&addr, &addrlen)) == -1) {
 | |
| 		ast_log(LOG_ERROR, "error accepting audio connection on adapter %s\n", adapter->id);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	len = sizeof(so);
 | |
| 	getsockopt(sock, SOL_SCO, SCO_OPTIONS, &so, &len);
 | |
| 
 | |
| 	ba2str(&addr.sco_bdaddr, saddr);
 | |
| 	ast_debug(1, "Incoming Audio Connection from device %s MTU is %d\n", saddr, so.mtu);
 | |
| 
 | |
| 	/* figure out which device this sco connection belongs to */
 | |
| 	pvt = NULL;
 | |
| 	AST_RWLIST_RDLOCK(&devices);
 | |
| 	AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 		if (!bacmp(&pvt->addr, &addr.sco_bdaddr))
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 	if (!pvt) {
 | |
| 		ast_log(LOG_WARNING, "could not find device for incoming audio connection\n");
 | |
| 		close(sock);
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (pvt->sco_socket != -1) {
 | |
| 		close(pvt->sco_socket);
 | |
| 		pvt->sco_socket = -1;
 | |
| 	}
 | |
| 
 | |
| 	pvt->sco_socket = sock;
 | |
| 	if (pvt->owner) {
 | |
| 		ast_channel_set_fd(pvt->owner, 0, sock);
 | |
| 	} else {
 | |
| 		ast_debug(1, "incoming audio connection for pvt without owner\n");
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Bind an SCO listener socket for the given adapter.
 | |
|  * \param adapter an adapter_pvt
 | |
|  * \return -1 on error, non zero on success
 | |
|  */
 | |
| static int sco_bind(struct adapter_pvt *adapter)
 | |
| {
 | |
| 	struct sockaddr_sco addr;
 | |
| 	int opt = 1;
 | |
| 
 | |
| 	if ((adapter->sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
 | |
| 		ast_log(LOG_ERROR, "Unable to create sco listener socket for adapter %s.\n", adapter->id);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	memset(&addr, 0, sizeof(addr));
 | |
| 	addr.sco_family = AF_BLUETOOTH;
 | |
| 	bacpy(&addr.sco_bdaddr, &adapter->addr);
 | |
| 	if (bind(adapter->sco_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
 | |
| 		ast_log(LOG_ERROR, "Unable to bind sco listener socket. (%d)\n", errno);
 | |
| 		goto e_close_socket;
 | |
| 	}
 | |
| 	if (setsockopt(adapter->sco_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
 | |
| 		ast_log(LOG_ERROR, "Unable to setsockopt sco listener socket.\n");
 | |
| 		goto e_close_socket;
 | |
| 	}
 | |
| 	if (listen(adapter->sco_socket, 5) < 0) {
 | |
| 		ast_log(LOG_ERROR, "Unable to listen sco listener socket.\n");
 | |
| 		goto e_close_socket;
 | |
| 	}
 | |
| 
 | |
| 	return adapter->sco_socket;
 | |
| 
 | |
| e_close_socket:
 | |
| 	close(adapter->sco_socket);
 | |
| 	adapter->sco_socket = -1;
 | |
| e_return:
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Hayes AT command helpers.
 | |
|  */
 | |
| 
 | |
| /*!
 | |
|  * \brief Match the given buffer with the given prefix.
 | |
|  * \param buf the buffer to match
 | |
|  * \param prefix the prefix to match
 | |
|  */
 | |
| static int at_match_prefix(char *buf, char *prefix)
 | |
| {
 | |
| 	return !strncmp(buf, prefix, strlen(prefix));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read an AT message and classify it.
 | |
|  * \param rsock an rfcomm socket
 | |
|  * \param buf the buffer to store the result in
 | |
|  * \param count the size of the buffer or the maximum number of characters to read
 | |
|  * \return the type of message received, in addition buf will contain the
 | |
|  * message received and will be null terminated
 | |
|  * \see at_read()
 | |
|  */
 | |
| static at_message_t at_read_full(int rsock, char *buf, size_t count)
 | |
| {
 | |
| 	ssize_t s;
 | |
| 	if ((s = rfcomm_read(rsock, buf, count - 1)) < 1)
 | |
| 		return s;
 | |
| 	buf[s] = '\0';
 | |
| 
 | |
| 	if (!strcmp("OK", buf)) {
 | |
| 		return AT_OK;
 | |
| 	} else if (!strcmp("ERROR", buf)) {
 | |
| 		return AT_ERROR;
 | |
| 	} else if (!strcmp("RING", buf)) {
 | |
| 		return AT_RING;
 | |
| 	} else if (!strcmp("AT+CKPD=200", buf)) {
 | |
| 		return AT_CKPD;
 | |
| 	} else if (!strcmp("> ", buf)) {
 | |
| 		return AT_SMS_PROMPT;
 | |
| 	} else if (at_match_prefix(buf, "+CMTI:")) {
 | |
| 		return AT_CMTI;
 | |
| 	} else if (at_match_prefix(buf, "+CIEV:")) {
 | |
| 		return AT_CIEV;
 | |
| 	} else if (at_match_prefix(buf, "+BRSF:")) {
 | |
| 		return AT_BRSF;
 | |
| 	} else if (at_match_prefix(buf, "+CIND:")) {
 | |
| 		return AT_CIND;
 | |
| 	} else if (at_match_prefix(buf, "+CLIP:")) {
 | |
| 		return AT_CLIP;
 | |
| 	} else if (at_match_prefix(buf, "+CMGR:")) {
 | |
| 		return AT_CMGR;
 | |
| 	} else if (at_match_prefix(buf, "+VGM:")) {
 | |
| 		return AT_VGM;
 | |
| 	} else if (at_match_prefix(buf, "+VGS:")) {
 | |
| 		return AT_VGS;
 | |
| 	} else if (at_match_prefix(buf, "+CMS ERROR:")) {
 | |
| 		return AT_CMS_ERROR;
 | |
| 	} else if (at_match_prefix(buf, "AT+VGM=")) {
 | |
| 		return AT_VGM;
 | |
| 	} else if (at_match_prefix(buf, "AT+VGS=")) {
 | |
| 		return AT_VGS;
 | |
| 	} else if (at_match_prefix(buf, "+CUSD:")) {
 | |
| 		return AT_CUSD;
 | |
| 	} else if (at_match_prefix(buf, "BUSY")) {
 | |
| 		return AT_BUSY;
 | |
| 	} else if (at_match_prefix(buf, "NO DIALTONE")) {
 | |
| 		return AT_NO_DIALTONE;
 | |
| 	} else if (at_match_prefix(buf, "NO CARRIER")) {
 | |
| 		return AT_NO_CARRIER;
 | |
| 	} else if (at_match_prefix(buf, "*ECAV:")) {
 | |
| 		return AT_ECAM;
 | |
| 	} else {
 | |
| 		return AT_UNKNOWN;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Get the string representation of the given AT message.
 | |
|  * \param msg the message to process
 | |
|  * \return a string describing the given message
 | |
|  */
 | |
| static inline const char *at_msg2str(at_message_t msg)
 | |
| {
 | |
| 	switch (msg) {
 | |
| 	/* errors */
 | |
| 	case AT_PARSE_ERROR:
 | |
| 		return "PARSE ERROR";
 | |
| 	case AT_READ_ERROR:
 | |
| 		return "READ ERROR";
 | |
| 	default:
 | |
| 	case AT_UNKNOWN:
 | |
| 		return "UNKNOWN";
 | |
| 	/* at responses */
 | |
| 	case AT_OK:
 | |
| 		return "OK";
 | |
| 	case AT_ERROR:
 | |
| 		return "ERROR";
 | |
| 	case AT_RING:
 | |
| 		return "RING";
 | |
| 	case AT_BRSF:
 | |
| 		return "AT+BRSF";
 | |
| 	case AT_CIND:
 | |
| 		return "AT+CIND";
 | |
| 	case AT_CIEV:
 | |
| 		return "AT+CIEV";
 | |
| 	case AT_CLIP:
 | |
| 		return "AT+CLIP";
 | |
| 	case AT_CMTI:
 | |
| 		return "AT+CMTI";
 | |
| 	case AT_CMGR:
 | |
| 		return "AT+CMGR";
 | |
| 	case AT_SMS_PROMPT:
 | |
| 		return "SMS PROMPT";
 | |
| 	case AT_CMS_ERROR:
 | |
| 		return "+CMS ERROR";
 | |
| 	case AT_BUSY:
 | |
| 		return "BUSY";
 | |
| 	case AT_NO_DIALTONE:
 | |
| 		return "NO DIALTONE";
 | |
| 	case AT_NO_CARRIER:
 | |
| 		return "NO CARRIER";
 | |
| 	/* at commands */
 | |
| 	case AT_A:
 | |
| 		return "ATA";
 | |
| 	case AT_D:
 | |
| 		return "ATD";
 | |
| 	case AT_CHUP:
 | |
| 		return "AT+CHUP";
 | |
| 	case AT_CKPD:
 | |
| 		return "AT+CKPD";
 | |
| 	case AT_CMGS:
 | |
| 		return "AT+CMGS";
 | |
| 	case AT_VGM:
 | |
| 		return "AT+VGM";
 | |
| 	case AT_VGS:
 | |
| 		return "AT+VGS";
 | |
| 	case AT_VTS:
 | |
| 		return "AT+VTS";
 | |
| 	case AT_CMGF:
 | |
| 		return "AT+CMGF";
 | |
| 	case AT_CNMI:
 | |
| 		return "AT+CNMI";
 | |
| 	case AT_CMER:
 | |
| 		return "AT+CMER";
 | |
| 	case AT_CIND_TEST:
 | |
| 		return "AT+CIND=?";
 | |
| 	case AT_CUSD:
 | |
| 		return "AT+CUSD";
 | |
| 	case AT_ECAM:
 | |
| 		return "AT*ECAM";
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * bluetooth handsfree profile helpers
 | |
|  */
 | |
| 
 | |
|  /*!
 | |
|  * \brief Parse a ECAV event.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  * \return -1 on error (parse error) or a ECAM value on success
 | |
|  *
 | |
|  * Example:
 | |
|  * \verbatim *ECAV: <ccid>,<ccstatus>,<calltype>[,<processid>]
 | |
|                     [,exitcause][,<number>,<type>] \endverbatim
 | |
|  *
 | |
|  * Example indicating busy:
 | |
|  * \verbatim *ECAV: 1,7,1 \endverbatim
 | |
|  */
 | |
| static int hfp_parse_ecav(struct hfp_pvt *hfp, char *buf)
 | |
| {
 | |
| 	int ccid = 0;
 | |
| 	int ccstatus = 0;
 | |
| 	int calltype = 0;
 | |
| 
 | |
| 	if (!sscanf(buf, "*ECAV: %2d,%2d,%2d", &ccid, &ccstatus, &calltype)) {
 | |
| 		ast_debug(1, "[%s] error parsing ECAV event '%s'\n", hfp->owner->id, buf);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return ccstatus;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Enable Sony Ericsson extensions / indications.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  */
 | |
| static int hfp_send_ecam(struct hfp_pvt *hfp)
 | |
| {
 | |
| 	return rfcomm_write(hfp->rsock, "AT*ECAM=1\r");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse a CIEV event.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  * \param value a pointer to an int to store the event value in (can be NULL)
 | |
|  * \return 0 on error (parse error, or unknown event) or a HFP_CIND_* value on
 | |
|  * success
 | |
|  */
 | |
| static int hfp_parse_ciev(struct hfp_pvt *hfp, char *buf, int *value)
 | |
| {
 | |
| 	int i, v;
 | |
| 	if (!value)
 | |
| 		value = &v;
 | |
| 
 | |
| 	if (!sscanf(buf, "+CIEV: %d,%d", &i, value)) {
 | |
| 		ast_debug(2, "[%s] error parsing CIEV event '%s'\n", hfp->owner->id, buf);
 | |
| 		return HFP_CIND_NONE;
 | |
| 	}
 | |
| 
 | |
| 	if (i >= ARRAY_LEN(hfp->cind_state)) {
 | |
| 		ast_debug(2, "[%s] CIEV event index too high (%s)\n", hfp->owner->id, buf);
 | |
| 		return HFP_CIND_NONE;
 | |
| 	}
 | |
| 
 | |
| 	hfp->cind_state[i] = *value;
 | |
| 	return hfp->cind_index[i];
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse a CLIP event.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  * \note buf will be modified when the CID string is parsed
 | |
|  * \return a cidinfo structure pointing to the cnam and cnum
 | |
|  * data in buf.  On parse errors, either or both pointers
 | |
|  * will point to null strings
 | |
|  */
 | |
| static struct cidinfo hfp_parse_clip(struct hfp_pvt *hfp, char *buf)
 | |
| {
 | |
| 	int i;
 | |
| 	int tokens[6];
 | |
| 	char *cnamtmp;
 | |
| 	char delim = ' ';	/* First token terminates with space */
 | |
| 	int invalid = 0;	/* Number of invalid chars in cnam */
 | |
| 	struct cidinfo cidinfo = { NULL, NULL };
 | |
| 
 | |
| 	/* parse clip info in the following format:
 | |
| 	 * +CLIP: "123456789",128,...
 | |
| 	 */
 | |
| 	ast_debug(3, "[%s] hfp_parse_clip is processing \"%s\"\n", hfp->owner->id, buf);
 | |
| 	tokens[0] = 0;		/* First token starts in position 0 */
 | |
| 	for (i = 1; i < ARRAY_LEN(tokens); i++) {
 | |
| 		tokens[i] = parse_next_token(buf, tokens[i - 1], delim);
 | |
| 		delim = ',';	/* Subsequent tokens terminate with comma */
 | |
| 	}
 | |
| 	ast_debug(3, "[%s] hfp_parse_clip found tokens: 0=%s, 1=%s, 2=%s, 3=%s, 4=%s, 5=%s\n",
 | |
| 		hfp->owner->id, &buf[tokens[0]], &buf[tokens[1]], &buf[tokens[2]],
 | |
| 		&buf[tokens[3]], &buf[tokens[4]], &buf[tokens[5]]);
 | |
| 
 | |
| 	/* Clean up cnum, and make sure it is legitimate since it is untrusted. */
 | |
| 	cidinfo.cnum = ast_strip_quoted(&buf[tokens[1]], "\"", "\"");
 | |
| 	if (!ast_isphonenumber(cidinfo.cnum)) {
 | |
| 		ast_debug(1, "[%s] hfp_parse_clip invalid cidinfo.cnum data \"%s\" - deleting\n",
 | |
| 			hfp->owner->id, cidinfo.cnum);
 | |
| 		cidinfo.cnum = "";
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Some docs say tokens 2 and 3 including the commas are optional.
 | |
| 	 * If absent, that would move CNAM back to token 3.
 | |
| 	 */
 | |
| 	cidinfo.cnam = &buf[tokens[5]];	/* Assume it's in token 5 */
 | |
| 	if (buf[tokens[5]] == '\0' && buf[tokens[4]] == '\0') {
 | |
| 		/* Tokens 4 and 5 are empty.  See if token 3 looks like CNAM (starts with ") */
 | |
| 		i = tokens[3];
 | |
| 		while (buf[i] == ' ') {		/* Find the first non-blank */
 | |
| 			i++;
 | |
| 		}
 | |
| 		if (buf[i] == '"') {
 | |
| 			/* Starts with quote.  Use this for CNAM. */
 | |
| 			cidinfo.cnam = &buf[i];
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Clean up CNAM. */
 | |
| 	cidinfo.cnam = ast_strip_quoted(cidinfo.cnam, "\"", "\"");
 | |
| 	for (cnamtmp = cidinfo.cnam; *cnamtmp != '\0'; cnamtmp++) {
 | |
| 		if (!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789-,abcdefghijklmnopqrstuvwxyz_", *cnamtmp)) {
 | |
| 			*cnamtmp = '_';	/* Invalid.  Replace with underscore. */
 | |
| 			invalid++;
 | |
| 		}
 | |
| 	}
 | |
| 	if (invalid) {
 | |
| 		ast_debug(2, "[%s] hfp_parse_clip replaced %d invalid byte(s) in cnam data\n",
 | |
| 			hfp->owner->id, invalid);
 | |
| 	}
 | |
| 	ast_debug(2, "[%s] hfp_parse_clip returns cnum=%s and cnam=%s\n",
 | |
| 		hfp->owner->id, cidinfo.cnum, cidinfo.cnam);
 | |
| 
 | |
| 	return cidinfo;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Terminate current token and return an index to start of the next token.
 | |
|  * \param string the null-terminated string being parsed (will be altered!)
 | |
|  * \param start where the current token starts
 | |
|  * \param delim the token termination delimiter.  \0 is also considered a terminator.
 | |
|  * \return index of the next token.  May be the same as this token if the string is
 | |
|  * exhausted.
 | |
|  */
 | |
| static int parse_next_token(char string[], const int start, const char delim)
 | |
| {
 | |
| 	int index;
 | |
| 	int quoting = 0;
 | |
| 
 | |
| 	for (index = start; string[index] != 0; index++) {
 | |
| 		if ((string[index] == delim) && !quoting ) {
 | |
| 			/* Found the delimiter, outside of quotes.  This is the end of the token. */
 | |
| 			string[index] = '\0';	/* Terminate this token. */
 | |
| 			index++;		/* Point the index to the start of the next token. */
 | |
| 			break;			/* We're done. */
 | |
| 		} else if (string[index] == '"' && !quoting) {
 | |
| 			/* Found a beginning quote mark.  Remember it. */
 | |
| 			quoting = 1;
 | |
| 		} else if (string[index] == '"' ) {
 | |
| 			/* Found the end quote mark. */
 | |
| 			quoting = 0;
 | |
| 		}
 | |
| 	}
 | |
| 	return index;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse a CMTI notification.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  * \note buf will be modified when the CMTI message is parsed
 | |
|  * \return -1 on error (parse error) or the index of the new sms message
 | |
|  */
 | |
| static int hfp_parse_cmti(struct hfp_pvt *hfp, char *buf)
 | |
| {
 | |
| 	int index = -1;
 | |
| 
 | |
| 	/* parse cmti info in the following format:
 | |
| 	 * +CMTI: <mem>,<index>
 | |
| 	 */
 | |
| 	if (!sscanf(buf, "+CMTI: %*[^,],%d", &index)) {
 | |
| 		ast_debug(2, "[%s] error parsing CMTI event '%s'\n", hfp->owner->id, buf);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return index;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse a CMGR message.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  * \param from_number a pointer to a char pointer which will store the from
 | |
|  * number
 | |
|  * \param text a pointer to a char pointer which will store the message text
 | |
|  * \note buf will be modified when the CMGR message is parsed
 | |
|  * \retval -1 parse error
 | |
|  * \retval 0 success
 | |
|  */
 | |
| static int hfp_parse_cmgr(struct hfp_pvt *hfp, char *buf, char **from_number, char **text)
 | |
| {
 | |
| 	int i, state;
 | |
| 	size_t s;
 | |
| 
 | |
| 	/* parse cmgr info in the following format:
 | |
| 	 * +CMGR: <msg status>,"+123456789",...\r\n
 | |
| 	 * <message text>
 | |
| 	 */
 | |
| 	state = 0;
 | |
| 	s = strlen(buf);
 | |
| 	for (i = 0; i < s && state != 6; i++) {
 | |
| 		switch (state) {
 | |
| 		case 0: /* search for start of the number section (,) */
 | |
| 			if (buf[i] == ',') {
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 1: /* find the opening quote (") */
 | |
| 			if (buf[i] == '"') {
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 2: /* mark the start of the number */
 | |
| 			if (from_number) {
 | |
| 				*from_number = &buf[i];
 | |
| 				state++;
 | |
| 			}
 | |
| 			/* fall through */
 | |
| 		case 3: /* search for the end of the number (") */
 | |
| 			if (buf[i] == '"') {
 | |
| 				buf[i] = '\0';
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 4: /* search for the start of the message text (\n) */
 | |
| 			if (buf[i] == '\n') {
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 5: /* mark the start of the message text */
 | |
| 			if (text) {
 | |
| 				*text = &buf[i];
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (state != 6) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse a CUSD answer.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  * \note buf will be modified when the CUSD string is parsed
 | |
|  * \return NULL on error (parse error) or a pointer to the cusd message
 | |
|  * information in buf
 | |
|  */
 | |
| static char *hfp_parse_cusd(struct hfp_pvt *hfp, char *buf)
 | |
| {
 | |
| 	int i, message_start, message_end;
 | |
| 	char *cusd;
 | |
| 	size_t s;
 | |
| 
 | |
| 	/* parse cusd message in the following format:
 | |
| 	 * +CUSD: 0,"100,00 EURO, valid till 01.01.2010, you are using tariff "Mega Tariff". More informations *111#."
 | |
| 	 */
 | |
| 	message_start = 0;
 | |
| 	message_end = 0;
 | |
| 	s = strlen(buf);
 | |
| 
 | |
| 	/* Find the start of the message (") */
 | |
| 	for (i = 0; i < s; i++) {
 | |
| 		if (buf[i] == '"') {
 | |
| 			message_start = i + 1;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (message_start == 0 || message_start >= s) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Find the end of the message (") */
 | |
| 	for (i = s; i > 0; i--) {
 | |
| 		if (buf[i] == '"') {
 | |
| 			message_end = i;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (message_end == 0) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (message_start >= message_end) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	cusd = &buf[message_start];
 | |
| 	buf[message_end] = '\0';
 | |
| 
 | |
| 	return cusd;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Convert a hfp_hf struct to a BRSF int.
 | |
|  * \param hf an hfp_hf brsf object
 | |
|  * \return an integer representing the given brsf struct
 | |
|  */
 | |
| static int hfp_brsf2int(struct hfp_hf *hf)
 | |
| {
 | |
| 	int brsf = 0;
 | |
| 
 | |
| 	brsf |= hf->ecnr ? HFP_HF_ECNR : 0;
 | |
| 	brsf |= hf->cw ? HFP_HF_CW : 0;
 | |
| 	brsf |= hf->cid ? HFP_HF_CID : 0;
 | |
| 	brsf |= hf->voice ? HFP_HF_VOICE : 0;
 | |
| 	brsf |= hf->volume ? HFP_HF_VOLUME : 0;
 | |
| 	brsf |= hf->status ? HFP_HF_STATUS : 0;
 | |
| 	brsf |= hf->control ? HFP_HF_CONTROL : 0;
 | |
| 
 | |
| 	return brsf;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Convert a BRSF int to an hfp_ag struct.
 | |
|  * \param brsf a brsf integer
 | |
|  * \param ag a AG (hfp_ag) brsf object
 | |
|  * \return a pointer to the given hfp_ag object populated with the values from
 | |
|  * the given brsf integer
 | |
|  */
 | |
| static struct hfp_ag *hfp_int2brsf(int brsf, struct hfp_ag *ag)
 | |
| {
 | |
| 	ag->cw = brsf & HFP_AG_CW ? 1 : 0;
 | |
| 	ag->ecnr = brsf & HFP_AG_ECNR ? 1 : 0;
 | |
| 	ag->voice = brsf & HFP_AG_VOICE ? 1 : 0;
 | |
| 	ag->ring = brsf & HFP_AG_RING ? 1 : 0;
 | |
| 	ag->tag = brsf & HFP_AG_TAG ? 1 : 0;
 | |
| 	ag->reject = brsf & HFP_AG_REJECT ? 1 : 0;
 | |
| 	ag->status = brsf & HFP_AG_STATUS ? 1 : 0;
 | |
| 	ag->control = brsf & HFP_AG_CONTROL ? 1 : 0;
 | |
| 	ag->errors = brsf & HFP_AG_ERRORS ? 1 : 0;
 | |
| 
 | |
| 	return ag;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*!
 | |
|  * \brief Send a BRSF request.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param brsf an hfp_hf brsf struct
 | |
|  *
 | |
|  * \retval 0 on success
 | |
|  * \retval -1 on error
 | |
|  */
 | |
| static int hfp_send_brsf(struct hfp_pvt *hfp, struct hfp_hf *brsf)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+BRSF=%d\r", hfp_brsf2int(brsf));
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send the CIND read command.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  */
 | |
| static int hfp_send_cind(struct hfp_pvt *hfp)
 | |
| {
 | |
| 	return rfcomm_write(hfp->rsock, "AT+CIND?\r");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send the CIND test command.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  */
 | |
| static int hfp_send_cind_test(struct hfp_pvt *hfp)
 | |
| {
 | |
| 	return rfcomm_write(hfp->rsock, "AT+CIND=?\r");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Enable or disable indicator events reporting.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param status enable or disable events reporting (should be 1 or 0)
 | |
|  */
 | |
| static int hfp_send_cmer(struct hfp_pvt *hfp, int status)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+CMER=3,0,0,%d\r", status ? 1 : 0);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send the current speaker gain level.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param value the value to send (must be between 0 and 15)
 | |
|  */
 | |
| static int hfp_send_vgs(struct hfp_pvt *hfp, int value)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+VGS=%d\r", value);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| #if 0
 | |
| /*!
 | |
|  * \brief Send the current microphone gain level.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param value the value to send (must be between 0 and 15)
 | |
|  */
 | |
| static int hfp_send_vgm(struct hfp_pvt *hfp, int value)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+VGM=%d\r", value);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*!
 | |
|  * \brief Enable or disable calling line identification.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param status enable or disable calling line identification (should be 1 or
 | |
|  * 0)
 | |
|  */
 | |
| static int hfp_send_clip(struct hfp_pvt *hfp, int status)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+CLIP=%d\r", status ? 1 : 0);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send a DTMF command.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param digit the dtmf digit to send
 | |
|  * \return the result of rfcomm_write() or -1 on an invalid digit being sent
 | |
|  */
 | |
| static int hfp_send_dtmf(struct hfp_pvt *hfp, char digit)
 | |
| {
 | |
| 	char cmd[10];
 | |
| 
 | |
| 	switch(digit) {
 | |
| 	case '0':
 | |
| 	case '1':
 | |
| 	case '2':
 | |
| 	case '3':
 | |
| 	case '4':
 | |
| 	case '5':
 | |
| 	case '6':
 | |
| 	case '7':
 | |
| 	case '8':
 | |
| 	case '9':
 | |
| 	case '*':
 | |
| 	case '#':
 | |
| 		snprintf(cmd, sizeof(cmd), "AT+VTS=%c\r", digit);
 | |
| 		return rfcomm_write(hfp->rsock, cmd);
 | |
| 	default:
 | |
| 		return -1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Set the SMS mode.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param mode the sms mode (0 = PDU, 1 = Text)
 | |
|  */
 | |
| static int hfp_send_cmgf(struct hfp_pvt *hfp, int mode)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+CMGF=%d\r", mode);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Setup SMS new message indication.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  */
 | |
| static int hfp_send_cnmi(struct hfp_pvt *hfp)
 | |
| {
 | |
| 	return rfcomm_write(hfp->rsock, "AT+CNMI=2,1,0,0,0\r");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read an SMS message.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param index the location of the requested message
 | |
|  */
 | |
| static int hfp_send_cmgr(struct hfp_pvt *hfp, int index)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+CMGR=%d\r", index);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Start sending an SMS message.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param number the destination of the message
 | |
|  */
 | |
| static int hfp_send_cmgs(struct hfp_pvt *hfp, const char *number)
 | |
| {
 | |
| 	char cmd[64];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+CMGS=\"%s\"\r", number);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send the text of an SMS message.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param message the text of the message
 | |
|  */
 | |
| static int hfp_send_sms_text(struct hfp_pvt *hfp, const char *message)
 | |
| {
 | |
| 	char cmd[162];
 | |
| 	snprintf(cmd, sizeof(cmd), "%.160s\x1a", message);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send AT+CHUP.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  */
 | |
| static int hfp_send_chup(struct hfp_pvt *hfp)
 | |
| {
 | |
| 	return rfcomm_write(hfp->rsock, "AT+CHUP\r");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send ATD.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param number the number to send
 | |
|  */
 | |
| static int hfp_send_atd(struct hfp_pvt *hfp, const char *number)
 | |
| {
 | |
| 	char cmd[64];
 | |
| 	snprintf(cmd, sizeof(cmd), "ATD%s;\r", number);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send ATA.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  */
 | |
| static int hfp_send_ata(struct hfp_pvt *hfp)
 | |
| {
 | |
| 	return rfcomm_write(hfp->rsock, "ATA\r");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send CUSD.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param code the CUSD code to send
 | |
|  */
 | |
| static int hfp_send_cusd(struct hfp_pvt *hfp, const char *code)
 | |
| {
 | |
| 	char cmd[128];
 | |
| 	snprintf(cmd, sizeof(cmd), "AT+CUSD=1,\"%s\",15\r", code);
 | |
| 	return rfcomm_write(hfp->rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse BRSF data.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  */
 | |
| static int hfp_parse_brsf(struct hfp_pvt *hfp, const char *buf)
 | |
| {
 | |
| 	int brsf;
 | |
| 
 | |
| 	if (!sscanf(buf, "+BRSF:%d", &brsf))
 | |
| 		return -1;
 | |
| 
 | |
| 	hfp_int2brsf(brsf, &hfp->brsf);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse and store the given indicator.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param group the indicator group
 | |
|  * \param indicator the indicator to parse
 | |
|  */
 | |
| static int hfp_parse_cind_indicator(struct hfp_pvt *hfp, int group, char *indicator)
 | |
| {
 | |
| 	int value;
 | |
| 
 | |
| 	/* store the current indicator */
 | |
| 	if (group >= ARRAY_LEN(hfp->cind_state)) {
 | |
| 		ast_debug(1, "ignoring CIND state '%s' for group %d, we only support up to %d indicators\n", indicator, group, (int) sizeof(hfp->cind_state));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (!sscanf(indicator, "%d", &value)) {
 | |
| 		ast_debug(1, "error parsing CIND state '%s' for group %d\n", indicator, group);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	hfp->cind_state[group] = value;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Read the result of the AT+CIND? command.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  * \note hfp_send_cind_test() and hfp_parse_cind_test() should be called at
 | |
|  * least once before this function is called.
 | |
|  */
 | |
| static int hfp_parse_cind(struct hfp_pvt *hfp, char *buf)
 | |
| {
 | |
| 	int i, state, group;
 | |
| 	size_t s;
 | |
| 	char *indicator = NULL;
 | |
| 
 | |
| 	/* parse current state of all of our indicators.  The list is in the
 | |
| 	 * following format:
 | |
| 	 * +CIND: 1,0,2,0,0,0,0
 | |
| 	 */
 | |
| 	group = 0;
 | |
| 	state = 0;
 | |
| 	s = strlen(buf);
 | |
| 	for (i = 0; i < s; i++) {
 | |
| 		switch (state) {
 | |
| 		case 0: /* search for start of the status indicators (a space) */
 | |
| 			if (buf[i] == ' ') {
 | |
| 				group++;
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 1: /* mark this indicator */
 | |
| 			indicator = &buf[i];
 | |
| 			state++;
 | |
| 			break;
 | |
| 		case 2: /* search for the start of the next indicator (a comma) */
 | |
| 			if (buf[i] == ',') {
 | |
| 				buf[i] = '\0';
 | |
| 
 | |
| 				hfp_parse_cind_indicator(hfp, group, indicator);
 | |
| 
 | |
| 				group++;
 | |
| 				state = 1;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* store the last indicator */
 | |
| 	if (state == 2)
 | |
| 		hfp_parse_cind_indicator(hfp, group, indicator);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Parse the result of the AT+CIND=? command.
 | |
|  * \param hfp an hfp_pvt struct
 | |
|  * \param buf the buffer to parse (null terminated)
 | |
|  */
 | |
| static int hfp_parse_cind_test(struct hfp_pvt *hfp, char *buf)
 | |
| {
 | |
| 	int i, state, group;
 | |
| 	size_t s;
 | |
| 	char *indicator = NULL;
 | |
| 
 | |
| 	hfp->nocallsetup = 1;
 | |
| 
 | |
| 	/* parse the indications list.  It is in the follwing format:
 | |
| 	 * +CIND: ("ind1",(0-1)),("ind2",(0-5))
 | |
| 	 */
 | |
| 	group = 0;
 | |
| 	state = 0;
 | |
| 	s = strlen(buf);
 | |
| 	for (i = 0; i < s; i++) {
 | |
| 		switch (state) {
 | |
| 		case 0: /* search for start of indicator block */
 | |
| 			if (buf[i] == '(') {
 | |
| 				group++;
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 1: /* search for '"' in indicator block */
 | |
| 			if (buf[i] == '"') {
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 2: /* mark the start of the indicator name */
 | |
| 			indicator = &buf[i];
 | |
| 			state++;
 | |
| 			break;
 | |
| 		case 3: /* look for the end of the indicator name */
 | |
| 			if (buf[i] == '"') {
 | |
| 				buf[i] = '\0';
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 4: /* find the start of the value range */
 | |
| 			if (buf[i] == '(') {
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 5: /* mark the start of the value range */
 | |
| 			state++;
 | |
| 			break;
 | |
| 		case 6: /* find the end of the value range */
 | |
| 			if (buf[i] == ')') {
 | |
| 				buf[i] = '\0';
 | |
| 				state++;
 | |
| 			}
 | |
| 			break;
 | |
| 		case 7: /* process the values we found */
 | |
| 			if (group < sizeof(hfp->cind_index)) {
 | |
| 				if (!strcmp(indicator, "service")) {
 | |
| 					hfp->cind_map.service = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_SERVICE;
 | |
| 				} else if (!strcmp(indicator, "call")) {
 | |
| 					hfp->cind_map.call = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_CALL;
 | |
| 				} else if (!strcmp(indicator, "callsetup")) {
 | |
| 					hfp->nocallsetup = 0;
 | |
| 					hfp->cind_map.callsetup = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_CALLSETUP;
 | |
| 				} else if (!strcmp(indicator, "call_setup")) { /* non standard call setup identifier */
 | |
| 					hfp->nocallsetup = 0;
 | |
| 					hfp->cind_map.callsetup = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_CALLSETUP;
 | |
| 				} else if (!strcmp(indicator, "callheld")) {
 | |
| 					hfp->cind_map.callheld = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_CALLHELD;
 | |
| 				} else if (!strcmp(indicator, "signal")) {
 | |
| 					hfp->cind_map.signal = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_SIGNAL;
 | |
| 				} else if (!strcmp(indicator, "roam")) {
 | |
| 					hfp->cind_map.roam = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_ROAM;
 | |
| 				} else if (!strcmp(indicator, "battchg")) {
 | |
| 					hfp->cind_map.battchg = group;
 | |
| 					hfp->cind_index[group] = HFP_CIND_BATTCHG;
 | |
| 				} else {
 | |
| 					hfp->cind_index[group] = HFP_CIND_UNKNOWN;
 | |
| 					ast_debug(2, "ignoring unknown CIND indicator '%s'\n", indicator);
 | |
| 				}
 | |
| 			} else {
 | |
| 					ast_debug(1, "can't store indicator %d (%s), we only support up to %d indicators", group, indicator, (int) sizeof(hfp->cind_index));
 | |
| 			}
 | |
| 
 | |
| 			state = 0;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	hfp->owner->no_callsetup = hfp->nocallsetup;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Bluetooth Headset Profile helpers
 | |
|  */
 | |
| 
 | |
| /*!
 | |
|  * \brief Send an OK AT response.
 | |
|  * \param rsock the rfcomm socket to use
 | |
|  */
 | |
| static int hsp_send_ok(int rsock)
 | |
| {
 | |
| 	return rfcomm_write(rsock, "\r\nOK\r\n");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send an ERROR AT response.
 | |
|  * \param rsock the rfcomm socket to use
 | |
|  */
 | |
| static int hsp_send_error(int rsock)
 | |
| {
 | |
| 	return rfcomm_write(rsock, "\r\nERROR\r\n");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send a speaker gain unsolicited AT response
 | |
|  * \param rsock the rfcomm socket to use
 | |
|  * \param gain the speaker gain value
 | |
|  */
 | |
| static int hsp_send_vgs(int rsock, int gain)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "\r\n+VGS=%d\r\n", gain);
 | |
| 	return rfcomm_write(rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send a microphone gain unsolicited AT response
 | |
|  * \param rsock the rfcomm socket to use
 | |
|  * \param gain the microphone gain value
 | |
|  */
 | |
| static int hsp_send_vgm(int rsock, int gain)
 | |
| {
 | |
| 	char cmd[32];
 | |
| 	snprintf(cmd, sizeof(cmd), "\r\n+VGM=%d\r\n", gain);
 | |
| 	return rfcomm_write(rsock, cmd);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send a RING unsolicited AT response.
 | |
|  * \param rsock the rfcomm socket to use
 | |
|  */
 | |
| static int hsp_send_ring(int rsock)
 | |
| {
 | |
| 	return rfcomm_write(rsock, "\r\nRING\r\n");
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * message queue functions
 | |
|  */
 | |
| 
 | |
| /*!
 | |
|  * \brief Add an item to the back of the queue.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param expect the msg we expect to receive
 | |
|  * \param response_to the message that was sent to generate the expected
 | |
|  * response
 | |
|  */
 | |
| static int msg_queue_push(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to)
 | |
| {
 | |
| 	struct msg_queue_entry *msg;
 | |
| 	if (!(msg = ast_calloc(1, sizeof(*msg)))) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 	msg->expected = expect;
 | |
| 	msg->response_to = response_to;
 | |
| 
 | |
| 	AST_LIST_INSERT_TAIL(&pvt->msg_queue, msg, entry);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Add an item to the back of the queue with data.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param expect the msg we expect to receive
 | |
|  * \param response_to the message that was sent to generate the expected
 | |
|  * response
 | |
|  * \param data data associated with this message, it will be freed when the
 | |
|  * message is freed
 | |
|  */
 | |
| static int msg_queue_push_data(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to, void *data)
 | |
| {
 | |
| 	struct msg_queue_entry *msg;
 | |
| 	if (!(msg = ast_calloc(1, sizeof(*msg)))) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 	msg->expected = expect;
 | |
| 	msg->response_to = response_to;
 | |
| 	msg->data = data;
 | |
| 
 | |
| 	AST_LIST_INSERT_TAIL(&pvt->msg_queue, msg, entry);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Remove an item from the front of the queue.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \return a pointer to the removed item
 | |
|  */
 | |
| static struct msg_queue_entry *msg_queue_pop(struct mbl_pvt *pvt)
 | |
| {
 | |
| 	return AST_LIST_REMOVE_HEAD(&pvt->msg_queue, entry);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Remove an item from the front of the queue, and free it.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  */
 | |
| static void msg_queue_free_and_pop(struct mbl_pvt *pvt)
 | |
| {
 | |
| 	struct msg_queue_entry *msg;
 | |
| 	if ((msg = msg_queue_pop(pvt))) {
 | |
| 		if (msg->data)
 | |
| 			ast_free(msg->data);
 | |
| 		ast_free(msg);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Remove all items from the queue and free them.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  */
 | |
| static void msg_queue_flush(struct mbl_pvt *pvt)
 | |
| {
 | |
| 	struct msg_queue_entry *msg;
 | |
| 	while ((msg = msg_queue_head(pvt)))
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Get the head of a queue.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \return a pointer to the head of the given msg queue
 | |
|  */
 | |
| static struct msg_queue_entry *msg_queue_head(struct mbl_pvt *pvt)
 | |
| {
 | |
| 	return AST_LIST_FIRST(&pvt->msg_queue);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	sdp helpers
 | |
| 
 | |
| */
 | |
| 
 | |
| static int sdp_search(char *addr, int profile)
 | |
| {
 | |
| 
 | |
| 	sdp_session_t *session = 0;
 | |
| 	bdaddr_t bdaddr;
 | |
| 	uuid_t svc_uuid;
 | |
| 	uint32_t range = 0x0000ffff;
 | |
| 	sdp_list_t *response_list, *search_list, *attrid_list;
 | |
| 	int status, port;
 | |
| 	sdp_list_t *proto_list;
 | |
| 	sdp_record_t *sdprec;
 | |
| 
 | |
| 	str2ba(addr, &bdaddr);
 | |
| 	port = 0;
 | |
| 	session = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);
 | |
| 	if (!session) {
 | |
| 		ast_debug(1, "sdp_connect() failed on device %s.\n", addr);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	sdp_uuid32_create(&svc_uuid, profile);
 | |
| 	search_list = sdp_list_append(0, &svc_uuid);
 | |
| 	attrid_list = sdp_list_append(0, &range);
 | |
| 	response_list = 0x00;
 | |
| 	status = sdp_service_search_attr_req(session, search_list, SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
 | |
| 	if (status == 0) {
 | |
| 		if (response_list) {
 | |
| 			sdprec = (sdp_record_t *) response_list->data;
 | |
| 			proto_list = 0x00;
 | |
| 			if (sdp_get_access_protos(sdprec, &proto_list) == 0) {
 | |
| 				port = sdp_get_proto_port(proto_list, RFCOMM_UUID);
 | |
| 				sdp_list_free(proto_list, 0);
 | |
| 			}
 | |
| 			sdp_record_free(sdprec);
 | |
| 			sdp_list_free(response_list, 0);
 | |
| 		} else
 | |
| 			ast_debug(1, "No responses returned for device %s.\n", addr);
 | |
| 	} else
 | |
| 		ast_debug(1, "sdp_service_search_attr_req() failed on device %s.\n", addr);
 | |
| 
 | |
| 	sdp_list_free(search_list, 0);
 | |
| 	sdp_list_free(attrid_list, 0);
 | |
| 	sdp_close(session);
 | |
| 
 | |
| 	return port;
 | |
| 
 | |
| }
 | |
| 
 | |
| static sdp_session_t *sdp_register(void)
 | |
| {
 | |
| 	uint32_t service_uuid_int[] = {0, 0, 0, GENERIC_AUDIO_SVCLASS_ID};
 | |
| 	uint8_t rfcomm_channel = 1;
 | |
| 	const char *service_name = "Asterisk PABX";
 | |
| 	const char *service_dsc = "Asterisk PABX";
 | |
| 	const char *service_prov = "Asterisk";
 | |
| 
 | |
| 	uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid, svc_class1_uuid, svc_class2_uuid;
 | |
| 	sdp_list_t  *l2cap_list = 0, *rfcomm_list = 0, *root_list = 0, *proto_list = 0, *access_proto_list = 0, *svc_uuid_list = 0;
 | |
| 	sdp_data_t *channel = 0;
 | |
| 
 | |
| 	sdp_session_t *session = 0;
 | |
| 
 | |
| 	sdp_record_t *record = sdp_record_alloc();
 | |
| 
 | |
| 	sdp_uuid128_create(&svc_uuid, &service_uuid_int);
 | |
| 	sdp_set_service_id(record, svc_uuid);
 | |
| 
 | |
| 	sdp_uuid32_create(&svc_class1_uuid, GENERIC_AUDIO_SVCLASS_ID);
 | |
| 	sdp_uuid32_create(&svc_class2_uuid, HEADSET_PROFILE_ID);
 | |
| 
 | |
| 	svc_uuid_list = sdp_list_append(0, &svc_class1_uuid);
 | |
| 	svc_uuid_list = sdp_list_append(svc_uuid_list, &svc_class2_uuid);
 | |
| 	sdp_set_service_classes(record, svc_uuid_list);
 | |
| 
 | |
| 	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
 | |
| 	root_list = sdp_list_append(0, &root_uuid);
 | |
| 	sdp_set_browse_groups( record, root_list );
 | |
| 
 | |
| 	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
 | |
| 	l2cap_list = sdp_list_append(0, &l2cap_uuid);
 | |
| 	proto_list = sdp_list_append(0, l2cap_list);
 | |
| 
 | |
| 	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
 | |
| 	channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
 | |
| 	rfcomm_list = sdp_list_append(0, &rfcomm_uuid);
 | |
| 	sdp_list_append(rfcomm_list, channel);
 | |
| 	sdp_list_append(proto_list, rfcomm_list);
 | |
| 
 | |
| 	access_proto_list = sdp_list_append(0, proto_list);
 | |
| 	sdp_set_access_protos(record, access_proto_list);
 | |
| 
 | |
| 	sdp_set_info_attr(record, service_name, service_prov, service_dsc);
 | |
| 
 | |
| 	if (!(session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY)))
 | |
| 		ast_log(LOG_WARNING, "Failed to connect sdp and create session.\n");
 | |
| 	else {
 | |
| 		if (sdp_record_register(session, record, 0) < 0) {
 | |
| 			ast_log(LOG_WARNING, "Failed to sdp_record_register error: %d\n", errno);
 | |
| 			return NULL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sdp_data_free(channel);
 | |
| 	sdp_list_free(rfcomm_list, 0);
 | |
| 	sdp_list_free(root_list, 0);
 | |
| 	sdp_list_free(access_proto_list, 0);
 | |
| 	sdp_list_free(svc_uuid_list, 0);
 | |
| 
 | |
| 	return session;
 | |
| 
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	Thread routines
 | |
| 
 | |
| */
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle the BRSF response.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_brsf(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	struct msg_queue_entry *entry;
 | |
| 	if ((entry = msg_queue_head(pvt)) && entry->expected == AT_BRSF) {
 | |
| 		if (hfp_parse_brsf(pvt->hfp, buf)) {
 | |
| 			ast_debug(1, "[%s] error parsing BRSF\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		}
 | |
| 
 | |
| 		if (msg_queue_push(pvt, AT_OK, AT_BRSF)) {
 | |
| 			ast_debug(1, "[%s] error handling BRSF\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		}
 | |
| 
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| 	} else if (entry) {
 | |
| 		ast_debug(1, "[%s] received unexpected AT message 'BRSF' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
 | |
| 	} else {
 | |
| 		ast_debug(1, "[%s] received unexpected AT message 'BRSF'\n", pvt->id);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| e_return:
 | |
| 	msg_queue_free_and_pop(pvt);
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle the CIND response.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_cind(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	struct msg_queue_entry *entry;
 | |
| 	if ((entry = msg_queue_head(pvt)) && entry->expected == AT_CIND) {
 | |
| 		switch (entry->response_to) {
 | |
| 		case AT_CIND_TEST:
 | |
| 			if (hfp_parse_cind_test(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CIND_TEST)) {
 | |
| 				ast_debug(1, "[%s] error performing CIND test\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CIND:
 | |
| 			if (hfp_parse_cind(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CIND)) {
 | |
| 				ast_debug(1, "[%s] error getting CIND state\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 			break;
 | |
| 		default:
 | |
| 			ast_debug(1, "[%s] error getting CIND state\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		}
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| 	} else if (entry) {
 | |
| 		ast_debug(1, "[%s] received unexpected AT message 'CIND' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
 | |
| 	} else {
 | |
| 		ast_debug(1, "[%s] received unexpected AT message 'CIND'\n", pvt->id);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| e_return:
 | |
| 	msg_queue_free_and_pop(pvt);
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle OK AT messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_ok(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	struct msg_queue_entry *entry;
 | |
| 	if ((entry = msg_queue_head(pvt)) && entry->expected == AT_OK) {
 | |
| 		switch (entry->response_to) {
 | |
| 
 | |
| 		/* initialization stuff */
 | |
| 		case AT_BRSF:
 | |
| 			ast_debug(1, "[%s] BSRF sent successfully\n", pvt->id);
 | |
| 
 | |
| 			/* If this is a blackberry do CMER now, otherwise
 | |
| 			 * continue with CIND as normal. */
 | |
| 			if (pvt->blackberry) {
 | |
| 				if (hfp_send_cmer(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMER)) {
 | |
| 					ast_debug(1, "[%s] error sending CMER\n", pvt->id);
 | |
| 					goto e_return;
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (hfp_send_cind_test(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND_TEST)) {
 | |
| 					ast_debug(1, "[%s] error sending CIND test\n", pvt->id);
 | |
| 					goto e_return;
 | |
| 				}
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CIND_TEST:
 | |
| 			ast_debug(1, "[%s] CIND test sent successfully\n", pvt->id);
 | |
| 
 | |
| 			ast_debug(2, "[%s] call: %d\n", pvt->id, pvt->hfp->cind_map.call);
 | |
| 			ast_debug(2, "[%s] callsetup: %d\n", pvt->id, pvt->hfp->cind_map.callsetup);
 | |
| 			ast_debug(2, "[%s] service: %d\n", pvt->id, pvt->hfp->cind_map.service);
 | |
| 
 | |
| 			if (hfp_send_cind(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND)) {
 | |
| 				ast_debug(1, "[%s] error requesting CIND state\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CIND:
 | |
| 			ast_debug(1, "[%s] CIND sent successfully\n", pvt->id);
 | |
| 
 | |
| 			/* check if a call is active */
 | |
| 			if (pvt->hfp->cind_state[pvt->hfp->cind_map.call]) {
 | |
| 				ast_verb(3, "Bluetooth Device %s has a call in progress - delaying connection.\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 
 | |
| 			/* If this is NOT a blackberry proceed with CMER,
 | |
| 			 * otherwise send CLIP. */
 | |
| 			if (!pvt->blackberry) {
 | |
| 				if (hfp_send_cmer(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMER)) {
 | |
| 					ast_debug(1, "[%s] error sending CMER\n", pvt->id);
 | |
| 					goto e_return;
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (hfp_send_clip(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CLIP)) {
 | |
| 					ast_debug(1, "[%s] error enabling calling line notification\n", pvt->id);
 | |
| 					goto e_return;
 | |
| 				}
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CMER:
 | |
| 			ast_debug(1, "[%s] CMER sent successfully\n", pvt->id);
 | |
| 
 | |
| 			/* If this is a blackberry proceed with the CIND test,
 | |
| 			 * otherwise send CLIP. */
 | |
| 			if (pvt->blackberry) {
 | |
| 				if (hfp_send_cind_test(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND_TEST)) {
 | |
| 					ast_debug(1, "[%s] error sending CIND test\n", pvt->id);
 | |
| 					goto e_return;
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (hfp_send_clip(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CLIP)) {
 | |
| 					ast_debug(1, "[%s] error enabling calling line notification\n", pvt->id);
 | |
| 					goto e_return;
 | |
| 				}
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CLIP:
 | |
| 			ast_debug(1, "[%s] calling line indication enabled\n", pvt->id);
 | |
| 			if (hfp_send_ecam(pvt->hfp) || msg_queue_push(pvt, AT_OK, AT_ECAM)) {
 | |
| 				ast_debug(1, "[%s] error enabling Sony Ericsson call monitoring extensions\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 
 | |
| 			break;
 | |
| 		case AT_ECAM:
 | |
| 			ast_debug(1, "[%s] Sony Ericsson call monitoring is active on device\n", pvt->id);
 | |
| 			if (hfp_send_vgs(pvt->hfp, 15) || msg_queue_push(pvt, AT_OK, AT_VGS)) {
 | |
| 				ast_debug(1, "[%s] error synchronizing gain settings\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 
 | |
| 			pvt->timeout = -1;
 | |
| 			pvt->hfp->initialized = 1;
 | |
| 			ast_verb(3, "Bluetooth Device %s initialized and ready.\n", pvt->id);
 | |
| 
 | |
| 			break;
 | |
| 		case AT_VGS:
 | |
| 			ast_debug(1, "[%s] volume level synchronization successful\n", pvt->id);
 | |
| 
 | |
| 			/* set the SMS operating mode to text mode */
 | |
| 			if (pvt->has_sms) {
 | |
| 				if (hfp_send_cmgf(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMGF)) {
 | |
| 					ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
 | |
| 					goto e_return;
 | |
| 				}
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CMGF:
 | |
| 			ast_debug(1, "[%s] sms text mode enabled\n", pvt->id);
 | |
| 			/* turn on SMS new message indication */
 | |
| 			if (hfp_send_cnmi(pvt->hfp) || msg_queue_push(pvt, AT_OK, AT_CNMI)) {
 | |
| 				ast_debug(1, "[%s] error setting CNMI\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CNMI:
 | |
| 			ast_debug(1, "[%s] sms new message indication enabled\n", pvt->id);
 | |
| 			pvt->has_sms = 1;
 | |
| 			break;
 | |
| 		/* end initialization stuff */
 | |
| 
 | |
| 		case AT_A:
 | |
| 			ast_debug(1, "[%s] answer sent successfully\n", pvt->id);
 | |
| 			pvt->needchup = 1;
 | |
| 			break;
 | |
| 		case AT_D:
 | |
| 			ast_debug(1, "[%s] dial sent successfully\n", pvt->id);
 | |
| 			pvt->needchup = 1;
 | |
| 			pvt->outgoing = 1;
 | |
| 			mbl_queue_control(pvt, AST_CONTROL_PROGRESS);
 | |
| 			break;
 | |
| 		case AT_CHUP:
 | |
| 			ast_debug(1, "[%s] successful hangup\n", pvt->id);
 | |
| 			break;
 | |
| 		case AT_CMGS:
 | |
| 			ast_debug(1, "[%s] successfully sent sms message\n", pvt->id);
 | |
| 			pvt->outgoing_sms = 0;
 | |
| 			break;
 | |
| 		case AT_VTS:
 | |
| 			ast_debug(1, "[%s] digit sent successfully\n", pvt->id);
 | |
| 			break;
 | |
| 		case AT_CUSD:
 | |
| 			ast_debug(1, "[%s] CUSD code sent successfully\n", pvt->id);
 | |
| 			break;
 | |
| 		case AT_UNKNOWN:
 | |
| 		default:
 | |
| 			ast_debug(1, "[%s] received OK for unhandled request: %s\n", pvt->id, at_msg2str(entry->response_to));
 | |
| 			break;
 | |
| 		}
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| 	} else if (entry) {
 | |
| 		ast_debug(1, "[%s] received AT message 'OK' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
 | |
| 	} else {
 | |
| 		ast_debug(1, "[%s] received unexpected AT message 'OK'\n", pvt->id);
 | |
| 	}
 | |
| 	return 0;
 | |
| 
 | |
| e_return:
 | |
| 	msg_queue_free_and_pop(pvt);
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle ERROR AT messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_error(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	struct msg_queue_entry *entry;
 | |
| 	if ((entry = msg_queue_head(pvt))
 | |
| 			&& (entry->expected == AT_OK
 | |
| 			|| entry->expected == AT_ERROR
 | |
| 			|| entry->expected == AT_CMS_ERROR
 | |
| 			|| entry->expected == AT_CMGR
 | |
| 			|| entry->expected == AT_SMS_PROMPT)) {
 | |
| 		switch (entry->response_to) {
 | |
| 
 | |
| 		/* initialization stuff */
 | |
| 		case AT_BRSF:
 | |
| 			ast_debug(1, "[%s] error reading BSRF\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		case AT_CIND_TEST:
 | |
| 			ast_debug(1, "[%s] error during CIND test\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		case AT_CIND:
 | |
| 			ast_debug(1, "[%s] error requesting CIND state\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		case AT_CMER:
 | |
| 			ast_debug(1, "[%s] error during CMER request\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		case AT_CLIP:
 | |
| 			ast_debug(1, "[%s] error enabling calling line indication\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		case AT_VGS:
 | |
| 			ast_debug(1, "[%s] volume level synchronization failed\n", pvt->id);
 | |
| 
 | |
| 			/* this is not a fatal error, let's continue with initialization */
 | |
| 
 | |
| 			/* set the SMS operating mode to text mode */
 | |
| 			if (hfp_send_cmgf(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMGF)) {
 | |
| 				ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CMGF:
 | |
| 			pvt->has_sms = 0;
 | |
| 			ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
 | |
| 			ast_debug(1, "[%s] no SMS support\n", pvt->id);
 | |
| 			break;
 | |
| 		case AT_CNMI:
 | |
| 			pvt->has_sms = 0;
 | |
| 			ast_debug(1, "[%s] error setting CNMI\n", pvt->id);
 | |
| 			ast_debug(1, "[%s] no SMS support\n", pvt->id);
 | |
| 			break;
 | |
| 		case AT_ECAM:
 | |
| 			ast_debug(1, "[%s] Mobile does not support Sony Ericsson extensions\n", pvt->id);
 | |
| 
 | |
| 			/* this is not a fatal error, let's continue with the initialization */
 | |
| 
 | |
| 			if (hfp_send_vgs(pvt->hfp, 15) || msg_queue_push(pvt, AT_OK, AT_VGS)) {
 | |
| 				ast_debug(1, "[%s] error synchronizing gain settings\n", pvt->id);
 | |
| 				goto e_return;
 | |
| 			}
 | |
| 
 | |
| 			pvt->timeout = -1;
 | |
| 			pvt->hfp->initialized = 1;
 | |
| 			ast_verb(3, "Bluetooth Device %s initialized and ready.\n", pvt->id);
 | |
| 
 | |
| 			break;
 | |
| 		/* end initialization stuff */
 | |
| 
 | |
| 		case AT_A:
 | |
| 			ast_debug(1, "[%s] answer failed\n", pvt->id);
 | |
| 			mbl_queue_hangup(pvt);
 | |
| 			break;
 | |
| 		case AT_D:
 | |
| 			ast_debug(1, "[%s] dial failed\n", pvt->id);
 | |
| 			pvt->needchup = 0;
 | |
| 			mbl_queue_control(pvt, AST_CONTROL_CONGESTION);
 | |
| 			break;
 | |
| 		case AT_CHUP:
 | |
| 			ast_debug(1, "[%s] error sending hangup, disconnecting\n", pvt->id);
 | |
| 			goto e_return;
 | |
| 		case AT_CMGR:
 | |
| 			ast_debug(1, "[%s] error reading sms message\n", pvt->id);
 | |
| 			pvt->incoming_sms = 0;
 | |
| 			break;
 | |
| 		case AT_CMGS:
 | |
| 			ast_debug(1, "[%s] error sending sms message\n", pvt->id);
 | |
| 			pvt->outgoing_sms = 0;
 | |
| 			break;
 | |
| 		case AT_VTS:
 | |
| 			ast_debug(1, "[%s] error sending digit\n", pvt->id);
 | |
| 			break;
 | |
| 		case AT_CUSD:
 | |
| 			ast_verb(0, "[%s] error sending CUSD command\n", pvt->id);
 | |
| 			break;
 | |
| 		case AT_UNKNOWN:
 | |
| 		default:
 | |
| 			ast_debug(1, "[%s] received ERROR for unhandled request: %s\n", pvt->id, at_msg2str(entry->response_to));
 | |
| 			break;
 | |
| 		}
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| 	} else if (entry) {
 | |
| 		ast_debug(1, "[%s] received AT message 'ERROR' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
 | |
| 	} else {
 | |
| 		ast_debug(1, "[%s] received unexpected AT message 'ERROR'\n", pvt->id);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| e_return:
 | |
| 	msg_queue_free_and_pop(pvt);
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle AT+CIEV messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_ciev(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	int i;
 | |
| 	switch (hfp_parse_ciev(pvt->hfp, buf, &i)) {
 | |
| 	case HFP_CIND_CALL:
 | |
| 		switch (i) {
 | |
| 		case HFP_CIND_CALL_NONE:
 | |
| 			ast_debug(1, "[%s] line disconnected\n", pvt->id);
 | |
| 			if (pvt->owner) {
 | |
| 				ast_debug(1, "[%s] hanging up owner\n", pvt->id);
 | |
| 				if (mbl_queue_hangup(pvt)) {
 | |
| 					ast_log(LOG_ERROR, "[%s] error queueing hangup, disconnecting...\n", pvt->id);
 | |
| 					return -1;
 | |
| 				}
 | |
| 			}
 | |
| 			pvt->needchup = 0;
 | |
| 			pvt->needcallerid = 0;
 | |
| 			pvt->incoming = 0;
 | |
| 			pvt->outgoing = 0;
 | |
| 			break;
 | |
| 		case HFP_CIND_CALL_ACTIVE:
 | |
| 			if (pvt->outgoing) {
 | |
| 				ast_debug(1, "[%s] remote end answered\n", pvt->id);
 | |
| 				mbl_queue_control(pvt, AST_CONTROL_ANSWER);
 | |
| 			} else if (pvt->incoming && pvt->answered) {
 | |
| 				ast_setstate(pvt->owner, AST_STATE_UP);
 | |
| 			} else if (pvt->incoming) {
 | |
| 				/* user answered from handset, disconnecting */
 | |
| 				ast_verb(3, "[%s] user answered bluetooth device from handset, disconnecting\n", pvt->id);
 | |
| 				mbl_queue_hangup(pvt);
 | |
| 				return -1;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		break;
 | |
| 
 | |
| 	case HFP_CIND_CALLSETUP:
 | |
| 		switch (i) {
 | |
| 		case HFP_CIND_CALLSETUP_NONE:
 | |
| 			if (pvt->hfp->cind_state[pvt->hfp->cind_map.call] != HFP_CIND_CALL_ACTIVE) {
 | |
| 				if (pvt->owner) {
 | |
| 					if (pvt->hfp->sent_alerting == 1) {
 | |
| 						handle_response_busy(pvt);
 | |
| 					}
 | |
| 					if (mbl_queue_hangup(pvt)) {
 | |
| 						ast_log(LOG_ERROR, "[%s] error queueing hangup, disconnecting...\n", pvt->id);
 | |
| 						return -1;
 | |
| 					}
 | |
| 				}
 | |
| 				pvt->needchup = 0;
 | |
| 				pvt->needcallerid = 0;
 | |
| 				pvt->incoming = 0;
 | |
| 				pvt->outgoing = 0;
 | |
| 			}
 | |
| 			break;
 | |
| 		case HFP_CIND_CALLSETUP_INCOMING:
 | |
| 			ast_debug(1, "[%s] incoming call, waiting for caller id\n", pvt->id);
 | |
| 			pvt->needcallerid = 1;
 | |
| 			pvt->incoming = 1;
 | |
| 			break;
 | |
| 		case HFP_CIND_CALLSETUP_OUTGOING:
 | |
| 			if (pvt->outgoing) {
 | |
| 				pvt->hfp->sent_alerting = 0;
 | |
| 				ast_debug(1, "[%s] outgoing call\n", pvt->id);
 | |
| 			} else {
 | |
| 				ast_verb(3, "[%s] user dialed from handset, disconnecting\n", pvt->id);
 | |
| 				return -1;
 | |
| 			}
 | |
| 			break;
 | |
| 		case HFP_CIND_CALLSETUP_ALERTING:
 | |
| 			if (pvt->outgoing) {
 | |
| 				ast_debug(1, "[%s] remote alerting\n", pvt->id);
 | |
| 				mbl_queue_control(pvt, AST_CONTROL_RINGING);
 | |
| 				pvt->hfp->sent_alerting = 1;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		break;
 | |
| 	case HFP_CIND_NONE:
 | |
| 		ast_debug(1, "[%s] error parsing CIND: %s\n", pvt->id, buf);
 | |
| 		break;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle AT+CLIP messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_clip(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	struct msg_queue_entry *msg;
 | |
| 	struct ast_channel *chan;
 | |
| 	struct cidinfo cidinfo;
 | |
| 
 | |
| 	if ((msg = msg_queue_head(pvt)) && msg->expected == AT_CLIP) {
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| 
 | |
| 		pvt->needcallerid = 0;
 | |
| 		cidinfo = hfp_parse_clip(pvt->hfp, buf);
 | |
| 
 | |
| 		if (!(chan = mbl_new(AST_STATE_RING, pvt, &cidinfo, NULL, NULL))) {
 | |
| 			ast_log(LOG_ERROR, "[%s] unable to allocate channel for incoming call\n", pvt->id);
 | |
| 			hfp_send_chup(pvt->hfp);
 | |
| 			msg_queue_push(pvt, AT_OK, AT_CHUP);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		/* from this point on, we need to send a chup in the event of a
 | |
| 		 * hangup */
 | |
| 		pvt->needchup = 1;
 | |
| 
 | |
| 		if (ast_pbx_start(chan)) {
 | |
| 			ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming call\n", pvt->id);
 | |
| 			mbl_ast_hangup(pvt);
 | |
| 			return -1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle RING messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_ring(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	if (pvt->needcallerid) {
 | |
| 		ast_debug(1, "[%s] got ring while waiting for caller id\n", pvt->id);
 | |
| 		return msg_queue_push(pvt, AT_CLIP, AT_UNKNOWN);
 | |
| 	} else {
 | |
| 		return 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle AT+CMTI messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_cmti(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	int index = hfp_parse_cmti(pvt->hfp, buf);
 | |
| 	if (index > 0) {
 | |
| 		ast_debug(1, "[%s] incoming sms message\n", pvt->id);
 | |
| 
 | |
| 		if (hfp_send_cmgr(pvt->hfp, index)
 | |
| 				|| msg_queue_push(pvt, AT_CMGR, AT_CMGR)) {
 | |
| 			ast_debug(1, "[%s] error sending CMGR to retrieve SMS message\n", pvt->id);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		pvt->incoming_sms = 1;
 | |
| 		return 0;
 | |
| 	} else {
 | |
| 		ast_debug(1, "[%s] error parsing incoming sms message alert, disconnecting\n", pvt->id);
 | |
| 		return -1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle AT+CMGR messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_cmgr(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	char *from_number = NULL, *text = NULL;
 | |
| 	struct ast_channel *chan;
 | |
| 	struct msg_queue_entry *msg;
 | |
| 
 | |
| 	if ((msg = msg_queue_head(pvt)) && msg->expected == AT_CMGR) {
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| 
 | |
| 		if (hfp_parse_cmgr(pvt->hfp, buf, &from_number, &text)) {
 | |
| 			ast_debug(1, "[%s] error parsing sms message, disconnecting\n", pvt->id);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		ast_debug(1, "[%s] successfully read sms message\n", pvt->id);
 | |
| 		pvt->incoming_sms = 0;
 | |
| 
 | |
| 		/* XXX this channel probably does not need to be associated with this pvt */
 | |
| 		if (!(chan = mbl_new(AST_STATE_DOWN, pvt, NULL, NULL, NULL))) {
 | |
| 			ast_debug(1, "[%s] error creating sms message channel, disconnecting\n", pvt->id);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		ast_channel_exten_set(chan, "sms");
 | |
| 		pbx_builtin_setvar_helper(chan, "SMSSRC", from_number);
 | |
| 		pbx_builtin_setvar_helper(chan, "SMSTXT", text);
 | |
| 
 | |
| 		if (ast_pbx_start(chan)) {
 | |
| 			ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming sms\n", pvt->id);
 | |
| 			mbl_ast_hangup(pvt);
 | |
| 		}
 | |
| 	} else {
 | |
| 		ast_debug(1, "[%s] got unexpected +CMGR message, ignoring\n", pvt->id);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Send an SMS message from the queue.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_sms_prompt(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	struct msg_queue_entry *msg;
 | |
| 	if (!(msg = msg_queue_head(pvt))) {
 | |
| 		ast_debug(1, "[%s] error, got sms prompt with no pending sms messages\n", pvt->id);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (msg->expected != AT_SMS_PROMPT) {
 | |
| 		ast_debug(1, "[%s] error, got sms prompt but no pending sms messages\n", pvt->id);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (hfp_send_sms_text(pvt->hfp, msg->data)
 | |
| 			|| msg_queue_push(pvt, AT_OK, AT_CMGS)) {
 | |
| 		msg_queue_free_and_pop(pvt);
 | |
| 		ast_debug(1, "[%s] error sending sms message\n", pvt->id);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	msg_queue_free_and_pop(pvt);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle CUSD messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_cusd(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	char *cusd;
 | |
| 
 | |
| 	if (!(cusd = hfp_parse_cusd(pvt->hfp, buf))) {
 | |
| 		ast_verb(0, "[%s] error parsing CUSD: %s\n", pvt->id, buf);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	ast_verb(0, "[%s] CUSD response: %s\n", pvt->id, cusd);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle BUSY messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_busy(struct mbl_pvt *pvt)
 | |
| {
 | |
| 	pvt->hangupcause = AST_CAUSE_USER_BUSY;
 | |
| 	pvt->needchup = 1;
 | |
| 	mbl_queue_control(pvt, AST_CONTROL_BUSY);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle NO DIALTONE messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_no_dialtone(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	ast_verb(1, "[%s] mobile reports NO DIALTONE\n", pvt->id);
 | |
| 	pvt->needchup = 1;
 | |
| 	mbl_queue_control(pvt, AST_CONTROL_CONGESTION);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Handle NO CARRIER messages.
 | |
|  * \param pvt a mbl_pvt structure
 | |
|  * \param buf a null terminated buffer containing an AT message
 | |
|  * \retval 0 success
 | |
|  * \retval -1 error
 | |
|  */
 | |
| static int handle_response_no_carrier(struct mbl_pvt *pvt, char *buf)
 | |
| {
 | |
| 	ast_verb(1, "[%s] mobile reports NO CARRIER\n", pvt->id);
 | |
| 	pvt->needchup = 1;
 | |
| 	mbl_queue_control(pvt, AST_CONTROL_CONGESTION);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void *do_monitor_phone(void *data)
 | |
| {
 | |
| 	struct mbl_pvt *pvt = (struct mbl_pvt *)data;
 | |
| 	struct hfp_pvt *hfp = pvt->hfp;
 | |
| 	char buf[350];
 | |
| 	int t;
 | |
| 	at_message_t at_msg;
 | |
| 	struct msg_queue_entry *entry;
 | |
| 
 | |
| 	/* Note: At one point the initialization procedure was neatly contained
 | |
| 	 * in the hfp_init() function, but that initialization method did not
 | |
| 	 * work with non standard devices.  As a result, the initialization
 | |
| 	 * procedure is not spread throughout the event handling loop.
 | |
| 	 */
 | |
| 
 | |
| 	/* start initialization with the BRSF request */
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	pvt->timeout = 10000;
 | |
| 	if (hfp_send_brsf(hfp, &hfp_our_brsf)  || msg_queue_push(pvt, AT_BRSF, AT_BRSF)) {
 | |
| 		ast_debug(1, "[%s] error sending BRSF\n", hfp->owner->id);
 | |
| 		goto e_cleanup;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	while (!check_unloading()) {
 | |
| 		ast_mutex_lock(&pvt->lock);
 | |
| 		t = pvt->timeout;
 | |
| 		ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 		if (!rfcomm_wait(pvt->rfcomm_socket, &t)) {
 | |
| 			ast_debug(1, "[%s] timeout waiting for rfcomm data, disconnecting\n", pvt->id);
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (!hfp->initialized) {
 | |
| 				if ((entry = msg_queue_head(pvt))) {
 | |
| 					switch (entry->response_to) {
 | |
| 					case AT_CIND_TEST:
 | |
| 						if (pvt->blackberry)
 | |
| 							ast_debug(1, "[%s] timeout during CIND test\n", hfp->owner->id);
 | |
| 						else
 | |
| 							ast_debug(1, "[%s] timeout during CIND test, try setting 'blackberry=yes'\n", hfp->owner->id);
 | |
| 						break;
 | |
| 					case AT_CMER:
 | |
| 						if (pvt->blackberry)
 | |
| 							ast_debug(1, "[%s] timeout after sending CMER, try setting 'blackberry=no'\n", hfp->owner->id);
 | |
| 						else
 | |
| 							ast_debug(1, "[%s] timeout after sending CMER\n", hfp->owner->id);
 | |
| 						break;
 | |
| 					default:
 | |
| 						ast_debug(1, "[%s] timeout while waiting for %s in response to %s\n", pvt->id, at_msg2str(entry->expected), at_msg2str(entry->response_to));
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			goto e_cleanup;
 | |
| 		}
 | |
| 
 | |
| 		if ((at_msg = at_read_full(hfp->rsock, buf, sizeof(buf))) < 0) {
 | |
| 			ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, strerror(errno), errno);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		ast_debug(1, "[%s] read %s\n", pvt->id, buf);
 | |
| 
 | |
| 		switch (at_msg) {
 | |
| 		case AT_BRSF:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_brsf(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_CIND:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_cind(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_OK:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_ok(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_CMS_ERROR:
 | |
| 		case AT_ERROR:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_error(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_RING:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_ring(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_CIEV:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_ciev(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_CLIP:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_clip(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_CMTI:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_cmti(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_CMGR:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_cmgr(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_SMS_PROMPT:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_sms_prompt(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_CUSD:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_cusd(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_BUSY:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_busy(pvt)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_NO_DIALTONE:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_no_dialtone(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_NO_CARRIER:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (handle_response_no_carrier(pvt, buf)) {
 | |
| 				ast_mutex_unlock(&pvt->lock);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_ECAM:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (hfp_parse_ecav(hfp, buf) == 7) {
 | |
| 				if (handle_response_busy(pvt)) {
 | |
| 					ast_mutex_unlock(&pvt->lock);
 | |
| 					goto e_cleanup;
 | |
| 				}
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		case AT_UNKNOWN:
 | |
| 			ast_debug(1, "[%s] ignoring unknown message: %s\n", pvt->id, buf);
 | |
| 			break;
 | |
| 		case AT_PARSE_ERROR:
 | |
| 			ast_debug(1, "[%s] error parsing message\n", pvt->id);
 | |
| 			goto e_cleanup;
 | |
| 		case AT_READ_ERROR:
 | |
| 			ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, strerror(errno), errno);
 | |
| 			goto e_cleanup;
 | |
| 		default:
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| e_cleanup:
 | |
| 
 | |
| 	if (!hfp->initialized)
 | |
| 		ast_verb(3, "Error initializing Bluetooth device %s.\n", pvt->id);
 | |
| 
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (pvt->owner) {
 | |
| 		ast_debug(1, "[%s] device disconnected, hanging up owner\n", pvt->id);
 | |
| 		pvt->needchup = 0;
 | |
| 		mbl_queue_hangup(pvt);
 | |
| 	}
 | |
| 
 | |
| 	close(pvt->rfcomm_socket);
 | |
| 	close(pvt->sco_socket);
 | |
| 	pvt->sco_socket = -1;
 | |
| 
 | |
| 	msg_queue_flush(pvt);
 | |
| 
 | |
| 	pvt->connected = 0;
 | |
| 	hfp->initialized = 0;
 | |
| 
 | |
| 	pvt->adapter->inuse = 0;
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	ast_verb(3, "Bluetooth Device %s has disconnected.\n", pvt->id);
 | |
| 	manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id);
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static int headset_send_ring(const void *data)
 | |
| {
 | |
| 	struct mbl_pvt *pvt = (struct mbl_pvt *) data;
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (!pvt->needring) {
 | |
| 		ast_mutex_unlock(&pvt->lock);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	if (hsp_send_ring(pvt->rfcomm_socket)) {
 | |
| 		ast_debug(1, "[%s] error sending RING\n", pvt->id);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static void *do_monitor_headset(void *data)
 | |
| {
 | |
| 
 | |
| 	struct mbl_pvt *pvt = (struct mbl_pvt *)data;
 | |
| 	char buf[256];
 | |
| 	int t;
 | |
| 	at_message_t at_msg;
 | |
| 	struct ast_channel *chan = NULL;
 | |
| 
 | |
| 	ast_verb(3, "Bluetooth Device %s initialised and ready.\n", pvt->id);
 | |
| 
 | |
| 	while (!check_unloading()) {
 | |
| 
 | |
| 		t = ast_sched_wait(pvt->sched);
 | |
| 		if (t == -1) {
 | |
| 			t = 6000;
 | |
| 		}
 | |
| 
 | |
| 		ast_sched_runq(pvt->sched);
 | |
| 
 | |
| 		if (rfcomm_wait(pvt->rfcomm_socket, &t) == 0)
 | |
| 			continue;
 | |
| 
 | |
| 		if ((at_msg = at_read_full(pvt->rfcomm_socket, buf, sizeof(buf))) < 0) {
 | |
| 			ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, strerror(errno), errno);
 | |
| 			goto e_cleanup;
 | |
| 		}
 | |
| 		ast_debug(1, "[%s] %s\n", pvt->id, buf);
 | |
| 
 | |
| 		switch (at_msg) {
 | |
| 		case AT_VGS:
 | |
| 		case AT_VGM:
 | |
| 			/* XXX volume change requested, we will just
 | |
| 			 * pretend to do something with it */
 | |
| 			if (hsp_send_ok(pvt->rfcomm_socket)) {
 | |
| 				ast_debug(1, "[%s] error sending AT message 'OK'\n", pvt->id);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			break;
 | |
| 		case AT_CKPD:
 | |
| 			ast_mutex_lock(&pvt->lock);
 | |
| 			if (pvt->outgoing) {
 | |
| 				pvt->needring = 0;
 | |
| 				hsp_send_ok(pvt->rfcomm_socket);
 | |
| 				if (pvt->answered) {
 | |
| 					/* we have an answered call up to the
 | |
| 					 * HS, he wants to hangup */
 | |
| 					mbl_queue_hangup(pvt);
 | |
| 				} else {
 | |
| 					/* we have an outgoing call to the HS,
 | |
| 					 * he wants to answer */
 | |
| 					if ((pvt->sco_socket = sco_connect(pvt->adapter->addr, pvt->addr)) == -1) {
 | |
| 						ast_log(LOG_ERROR, "[%s] unable to create audio connection\n", pvt->id);
 | |
| 						mbl_queue_hangup(pvt);
 | |
| 						ast_mutex_unlock(&pvt->lock);
 | |
| 						goto e_cleanup;
 | |
| 					}
 | |
| 
 | |
| 					ast_channel_set_fd(pvt->owner, 0, pvt->sco_socket);
 | |
| 
 | |
| 					mbl_queue_control(pvt, AST_CONTROL_ANSWER);
 | |
| 					pvt->answered = 1;
 | |
| 
 | |
| 					if (hsp_send_vgs(pvt->rfcomm_socket, 13) || hsp_send_vgm(pvt->rfcomm_socket, 13)) {
 | |
| 						ast_debug(1, "[%s] error sending VGS/VGM\n", pvt->id);
 | |
| 						mbl_queue_hangup(pvt);
 | |
| 						ast_mutex_unlock(&pvt->lock);
 | |
| 						goto e_cleanup;
 | |
| 					}
 | |
| 				}
 | |
| 			} else if (pvt->incoming) {
 | |
| 				/* we have an incoming call from the
 | |
| 				 * HS, he wants to hang up */
 | |
| 				mbl_queue_hangup(pvt);
 | |
| 			} else {
 | |
| 				/* no call is up, HS wants to dial */
 | |
| 				hsp_send_ok(pvt->rfcomm_socket);
 | |
| 
 | |
| 				if ((pvt->sco_socket = sco_connect(pvt->adapter->addr, pvt->addr)) == -1) {
 | |
| 					ast_log(LOG_ERROR, "[%s] unable to create audio connection\n", pvt->id);
 | |
| 					ast_mutex_unlock(&pvt->lock);
 | |
| 					goto e_cleanup;
 | |
| 				}
 | |
| 
 | |
| 				pvt->incoming = 1;
 | |
| 
 | |
| 				if (!(chan = mbl_new(AST_STATE_UP, pvt, NULL, NULL, NULL))) {
 | |
| 					ast_log(LOG_ERROR, "[%s] unable to allocate channel for incoming call\n", pvt->id);
 | |
| 					ast_mutex_unlock(&pvt->lock);
 | |
| 					goto e_cleanup;
 | |
| 				}
 | |
| 
 | |
| 				ast_channel_set_fd(chan, 0, pvt->sco_socket);
 | |
| 
 | |
| 				ast_channel_exten_set(chan, "s");
 | |
| 				if (ast_pbx_start(chan)) {
 | |
| 					ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming call\n", pvt->id);
 | |
| 					ast_hangup(chan);
 | |
| 					ast_mutex_unlock(&pvt->lock);
 | |
| 					goto e_cleanup;
 | |
| 				}
 | |
| 			}
 | |
| 			ast_mutex_unlock(&pvt->lock);
 | |
| 			break;
 | |
| 		default:
 | |
| 			ast_debug(1, "[%s] received unknown AT command: %s (%s)\n", pvt->id, buf, at_msg2str(at_msg));
 | |
| 			if (hsp_send_error(pvt->rfcomm_socket)) {
 | |
| 				ast_debug(1, "[%s] error sending AT message 'ERROR'\n", pvt->id);
 | |
| 				goto e_cleanup;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| e_cleanup:
 | |
| 	ast_mutex_lock(&pvt->lock);
 | |
| 	if (pvt->owner) {
 | |
| 		ast_debug(1, "[%s] device disconnected, hanging up owner\n", pvt->id);
 | |
| 		mbl_queue_hangup(pvt);
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	close(pvt->rfcomm_socket);
 | |
| 	close(pvt->sco_socket);
 | |
| 	pvt->sco_socket = -1;
 | |
| 
 | |
| 	pvt->connected = 0;
 | |
| 
 | |
| 	pvt->needring = 0;
 | |
| 	pvt->outgoing = 0;
 | |
| 	pvt->incoming = 0;
 | |
| 
 | |
| 	pvt->adapter->inuse = 0;
 | |
| 	ast_mutex_unlock(&pvt->lock);
 | |
| 
 | |
| 	manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id);
 | |
| 	ast_verb(3, "Bluetooth Device %s has disconnected\n", pvt->id);
 | |
| 
 | |
| 	return NULL;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int start_monitor(struct mbl_pvt *pvt)
 | |
| {
 | |
| 
 | |
| 	if (pvt->type == MBL_TYPE_PHONE) {
 | |
| 		pvt->hfp->rsock = pvt->rfcomm_socket;
 | |
| 
 | |
| 		if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_phone, pvt) < 0) {
 | |
| 			pvt->monitor_thread = AST_PTHREADT_NULL;
 | |
| 			return 0;
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_headset, pvt) < 0) {
 | |
| 			pvt->monitor_thread = AST_PTHREADT_NULL;
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 1;
 | |
| 
 | |
| }
 | |
| 
 | |
| static void *do_discovery(void *data)
 | |
| {
 | |
| 
 | |
| 	struct adapter_pvt *adapter;
 | |
| 	struct mbl_pvt *pvt;
 | |
| 
 | |
| 	while (!check_unloading()) {
 | |
| 		AST_RWLIST_RDLOCK(&adapters);
 | |
| 		AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
 | |
| 			if (!adapter->inuse) {
 | |
| 				AST_RWLIST_RDLOCK(&devices);
 | |
| 				AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
 | |
| 					ast_mutex_lock(&pvt->lock);
 | |
| 					if (!adapter->inuse && !pvt->connected && !strcmp(adapter->id, pvt->adapter->id)) {
 | |
| 						if ((pvt->rfcomm_socket = rfcomm_connect(adapter->addr, pvt->addr, pvt->rfcomm_port)) > -1) {
 | |
| 							if (start_monitor(pvt)) {
 | |
| 								pvt->connected = 1;
 | |
| 								adapter->inuse = 1;
 | |
| 								manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Connect\r\nDevice: %s\r\n", pvt->id);
 | |
| 								ast_verb(3, "Bluetooth Device %s has connected, initializing...\n", pvt->id);
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 					ast_mutex_unlock(&pvt->lock);
 | |
| 				}
 | |
| 				AST_RWLIST_UNLOCK(&devices);
 | |
| 			}
 | |
| 		}
 | |
| 		AST_RWLIST_UNLOCK(&adapters);
 | |
| 
 | |
| 
 | |
| 		/* Go to sleep (only if we are not unloading) */
 | |
| 		if (!check_unloading())
 | |
| 			sleep(discovery_interval);
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Service new and existing SCO connections.
 | |
|  * This thread accepts new sco connections and handles audio data.  There is
 | |
|  * one do_sco_listen thread for each adapter.
 | |
|  */
 | |
| static void *do_sco_listen(void *data)
 | |
| {
 | |
| 	struct adapter_pvt *adapter = (struct adapter_pvt *) data;
 | |
| 
 | |
| 	while (!check_unloading()) {
 | |
| 		/* check for new sco connections */
 | |
| 		if (ast_io_wait(adapter->accept_io, 0) == -1) {
 | |
| 			/* handle errors */
 | |
| 			ast_log(LOG_ERROR, "ast_io_wait() failed for adapter %s\n", adapter->id);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		/* handle audio data */
 | |
| 		if (ast_io_wait(adapter->io, 1) == -1) {
 | |
| 			ast_log(LOG_ERROR, "ast_io_wait() failed for audio on adapter %s\n", adapter->id);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /*
 | |
| 
 | |
| 	Module
 | |
| 
 | |
| */
 | |
| 
 | |
| /*!
 | |
|  * \brief Load an adapter from the configuration file.
 | |
|  * \param cfg the config to load the adapter from
 | |
|  * \param cat the adapter to load
 | |
|  *
 | |
|  * This function loads the given adapter and starts the sco listener thread for
 | |
|  * that adapter.
 | |
|  *
 | |
|  * \return NULL on error, a pointer to the adapter that was loaded on success
 | |
|  */
 | |
| static struct adapter_pvt *mbl_load_adapter(struct ast_config *cfg, const char *cat)
 | |
| {
 | |
| 	const char *id, *address;
 | |
| 	struct adapter_pvt *adapter;
 | |
| 	struct ast_variable *v;
 | |
| 	struct hci_dev_req dr;
 | |
| 	uint16_t vs;
 | |
| 
 | |
| 	id = ast_variable_retrieve(cfg, cat, "id");
 | |
| 	address = ast_variable_retrieve(cfg, cat, "address");
 | |
| 
 | |
| 	if (ast_strlen_zero(id) || ast_strlen_zero(address)) {
 | |
| 		ast_log(LOG_ERROR, "Skipping adapter. Missing id or address settings.\n");
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	ast_debug(1, "Reading configuration for adapter %s %s.\n", id, address);
 | |
| 
 | |
| 	if (!(adapter = ast_calloc(1, sizeof(*adapter)))) {
 | |
| 		ast_log(LOG_ERROR, "Skipping adapter %s. Error allocating memory.\n", id);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	ast_copy_string(adapter->id, id, sizeof(adapter->id));
 | |
| 	str2ba(address, &adapter->addr);
 | |
| 
 | |
| 	/* attempt to connect to the adapter */
 | |
| 	adapter->dev_id = hci_devid(address);
 | |
| 	adapter->hci_socket = hci_open_dev(adapter->dev_id);
 | |
| 	if (adapter->dev_id < 0 || adapter->hci_socket < 0) {
 | |
| 		ast_log(LOG_ERROR, "Skipping adapter %s. Unable to communicate with adapter.\n", adapter->id);
 | |
| 		goto e_free_adapter;
 | |
| 	}
 | |
| 
 | |
| 	/* check voice setting */
 | |
| 	hci_read_voice_setting(adapter->hci_socket, &vs, 1000);
 | |
| 	vs = htobs(vs);
 | |
| 	if (vs != 0x0060) {
 | |
| 		ast_log(LOG_ERROR, "Skipping adapter %s. Voice setting must be 0x0060 - see 'man hciconfig' for details.\n", adapter->id);
 | |
| 		goto e_hci_close_dev;
 | |
| 	}
 | |
| 
 | |
| 	for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
 | |
| 		if (!strcasecmp(v->name, "forcemaster")) {
 | |
| 			if (ast_true(v->value)) {
 | |
| 				dr.dev_id = adapter->dev_id;
 | |
| 				if (hci_strtolm("master", &dr.dev_opt)) {
 | |
| 					if (ioctl(adapter->hci_socket, HCISETLINKMODE, (unsigned long) &dr) < 0) {
 | |
| 						ast_log(LOG_WARNING, "Unable to set adapter %s link mode to MASTER. Ignoring 'forcemaster' option.\n", adapter->id);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (!strcasecmp(v->name, "alignmentdetection")) {
 | |
| 			adapter->alignment_detection = ast_true(v->value);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* create io contexts */
 | |
| 	if (!(adapter->accept_io = io_context_create())) {
 | |
| 		ast_log(LOG_ERROR, "Unable to create I/O context for audio connection listener\n");
 | |
| 		goto e_hci_close_dev;
 | |
| 	}
 | |
| 
 | |
| 	if (!(adapter->io = io_context_create())) {
 | |
| 		ast_log(LOG_ERROR, "Unable to create I/O context for audio connections\n");
 | |
| 		goto e_destroy_accept_io;
 | |
| 	}
 | |
| 
 | |
| 	/* bind the sco listener socket */
 | |
| 	if (sco_bind(adapter) < 0) {
 | |
| 		ast_log(LOG_ERROR, "Skipping adapter %s. Error binding audio connection listener socket.\n", adapter->id);
 | |
| 		goto e_destroy_io;
 | |
| 	}
 | |
| 
 | |
| 	/* add the socket to the io context */
 | |
| 	if (!(adapter->sco_id = ast_io_add(adapter->accept_io, adapter->sco_socket, sco_accept, AST_IO_IN, adapter))) {
 | |
| 		ast_log(LOG_ERROR, "Skipping adapter %s. Error adding listener socket to I/O context.\n", adapter->id);
 | |
| 		goto e_close_sco;
 | |
| 	}
 | |
| 
 | |
| 	/* start the sco listener for this adapter */
 | |
| 	if (ast_pthread_create_background(&adapter->sco_listener_thread, NULL, do_sco_listen, adapter)) {
 | |
| 		ast_log(LOG_ERROR, "Skipping adapter %s. Error creating audio connection listener thread.\n", adapter->id);
 | |
| 		goto e_remove_sco;
 | |
| 	}
 | |
| 
 | |
| 	/* add the adapter to our global list */
 | |
| 	AST_RWLIST_WRLOCK(&adapters);
 | |
| 	AST_RWLIST_INSERT_HEAD(&adapters, adapter, entry);
 | |
| 	AST_RWLIST_UNLOCK(&adapters);
 | |
| 	ast_debug(1, "Loaded adapter %s %s.\n", adapter->id, address);
 | |
| 
 | |
| 	return adapter;
 | |
| 
 | |
| e_remove_sco:
 | |
| 	ast_io_remove(adapter->accept_io, adapter->sco_id);
 | |
| e_close_sco:
 | |
| 	close(adapter->sco_socket);
 | |
| e_destroy_io:
 | |
| 	io_context_destroy(adapter->io);
 | |
| e_destroy_accept_io:
 | |
| 	io_context_destroy(adapter->accept_io);
 | |
| e_hci_close_dev:
 | |
| 	hci_close_dev(adapter->hci_socket);
 | |
| e_free_adapter:
 | |
| 	ast_free(adapter);
 | |
| e_return:
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Load a device from the configuration file.
 | |
|  * \param cfg the config to load the device from
 | |
|  * \param cat the device to load
 | |
|  * \return NULL on error, a pointer to the device that was loaded on success
 | |
|  */
 | |
| static struct mbl_pvt *mbl_load_device(struct ast_config *cfg, const char *cat)
 | |
| {
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	struct adapter_pvt *adapter;
 | |
| 	struct ast_variable *v;
 | |
| 	const char *address, *adapter_str, *port;
 | |
| 	ast_debug(1, "Reading configuration for device %s.\n", cat);
 | |
| 
 | |
| 	adapter_str = ast_variable_retrieve(cfg, cat, "adapter");
 | |
| 	if(ast_strlen_zero(adapter_str)) {
 | |
| 		ast_log(LOG_ERROR, "Skipping device %s. No adapter specified.\n", cat);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	/* find the adapter */
 | |
| 	AST_RWLIST_RDLOCK(&adapters);
 | |
| 	AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
 | |
| 		if (!strcmp(adapter->id, adapter_str))
 | |
| 			break;
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&adapters);
 | |
| 	if (!adapter) {
 | |
| 		ast_log(LOG_ERROR, "Skipping device %s. Unknown adapter '%s' specified.\n", cat, adapter_str);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	address = ast_variable_retrieve(cfg, cat, "address");
 | |
| 	port = ast_variable_retrieve(cfg, cat, "port");
 | |
| 	if (ast_strlen_zero(port) || ast_strlen_zero(address)) {
 | |
| 		ast_log(LOG_ERROR, "Skipping device %s. Missing required port or address setting.\n", cat);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	/* create and initialize our pvt structure */
 | |
| 	if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
 | |
| 		ast_log(LOG_ERROR, "Skipping device %s. Error allocating memory.\n", cat);
 | |
| 		goto e_return;
 | |
| 	}
 | |
| 
 | |
| 	ast_mutex_init(&pvt->lock);
 | |
| 	AST_LIST_HEAD_INIT_NOLOCK(&pvt->msg_queue);
 | |
| 
 | |
| 	/* set some defaults */
 | |
| 
 | |
| 	pvt->type = MBL_TYPE_PHONE;
 | |
| 	ast_copy_string(pvt->context, "default", sizeof(pvt->context));
 | |
| 
 | |
| 	/* populate the pvt structure */
 | |
| 	pvt->adapter = adapter;
 | |
| 	ast_copy_string(pvt->id, cat, sizeof(pvt->id));
 | |
| 	str2ba(address, &pvt->addr);
 | |
| 	pvt->timeout = -1;
 | |
| 	pvt->rfcomm_socket = -1;
 | |
| 	pvt->rfcomm_port = atoi(port);
 | |
| 	pvt->sco_socket = -1;
 | |
| 	pvt->monitor_thread = AST_PTHREADT_NULL;
 | |
| 	pvt->ring_sched_id = -1;
 | |
| 	pvt->has_sms = 1;
 | |
| 
 | |
| 	/* setup the bt_out_smoother */
 | |
| 	if (!(pvt->bt_out_smoother = ast_smoother_new(DEVICE_FRAME_SIZE))) {
 | |
| 		ast_log(LOG_ERROR, "Skipping device %s. Error setting up frame bt_out_smoother.\n", cat);
 | |
| 		goto e_free_pvt;
 | |
| 	}
 | |
| 
 | |
| 	/* setup the bt_in_smoother */
 | |
| 	if (!(pvt->bt_in_smoother = ast_smoother_new(CHANNEL_FRAME_SIZE))) {
 | |
| 		ast_log(LOG_ERROR, "Skipping device %s. Error setting up frame bt_in_smoother.\n", cat);
 | |
| 		goto e_free_bt_out_smoother;
 | |
| 	}
 | |
| 
 | |
| 	/* setup the dsp */
 | |
| 	if (!(pvt->dsp = ast_dsp_new())) {
 | |
| 		ast_log(LOG_ERROR, "Skipping device %s. Error setting up dsp for dtmf detection.\n", cat);
 | |
| 		goto e_free_bt_in_smoother;
 | |
| 	}
 | |
| 
 | |
| 	/* setup the scheduler */
 | |
| 	if (!(pvt->sched = ast_sched_context_create())) {
 | |
| 		ast_log(LOG_ERROR, "Unable to create scheduler context for headset device\n");
 | |
| 		goto e_free_dsp;
 | |
| 	}
 | |
| 
 | |
| 	ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DIGIT_DETECT);
 | |
| 	ast_dsp_set_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
 | |
| 
 | |
| 	for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
 | |
| 		if (!strcasecmp(v->name, "type")) {
 | |
| 			if (!strcasecmp(v->value, "headset"))
 | |
| 				pvt->type = MBL_TYPE_HEADSET;
 | |
| 			else
 | |
| 				pvt->type = MBL_TYPE_PHONE;
 | |
| 		} else if (!strcasecmp(v->name, "context")) {
 | |
| 			ast_copy_string(pvt->context, v->value, sizeof(pvt->context));
 | |
| 		} else if (!strcasecmp(v->name, "group")) {
 | |
| 			/* group is set to 0 if invalid */
 | |
| 			pvt->group = atoi(v->value);
 | |
| 		} else if (!strcasecmp(v->name, "sms")) {
 | |
| 			pvt->has_sms = ast_true(v->value);
 | |
| 		} else if (!strcasecmp(v->name, "nocallsetup")) {
 | |
| 			pvt->no_callsetup = ast_true(v->value);
 | |
| 
 | |
| 			if (pvt->no_callsetup)
 | |
| 				ast_debug(1, "Setting nocallsetup mode for device %s.\n", pvt->id);
 | |
| 		} else if (!strcasecmp(v->name, "blackberry")) {
 | |
| 			pvt->blackberry = ast_true(v->value);
 | |
| 			pvt->has_sms = 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (pvt->type == MBL_TYPE_PHONE) {
 | |
| 		if (!(pvt->hfp = ast_calloc(1, sizeof(*pvt->hfp)))) {
 | |
| 			ast_log(LOG_ERROR, "Skipping device %s. Error allocating memory.\n", pvt->id);
 | |
| 			goto e_free_sched;
 | |
| 		}
 | |
| 
 | |
| 		pvt->hfp->owner = pvt;
 | |
| 		pvt->hfp->rport = pvt->rfcomm_port;
 | |
| 		pvt->hfp->nocallsetup = pvt->no_callsetup;
 | |
| 	} else {
 | |
| 		pvt->has_sms = 0;
 | |
| 	}
 | |
| 
 | |
| 	AST_RWLIST_WRLOCK(&devices);
 | |
| 	AST_RWLIST_INSERT_HEAD(&devices, pvt, entry);
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 	ast_debug(1, "Loaded device %s.\n", pvt->id);
 | |
| 
 | |
| 	return pvt;
 | |
| 
 | |
| e_free_sched:
 | |
| 	ast_sched_context_destroy(pvt->sched);
 | |
| e_free_dsp:
 | |
| 	ast_dsp_free(pvt->dsp);
 | |
| e_free_bt_in_smoother:
 | |
| 	ast_smoother_free(pvt->bt_in_smoother);
 | |
| e_free_bt_out_smoother:
 | |
| 	ast_smoother_free(pvt->bt_out_smoother);
 | |
| e_free_pvt:
 | |
| 	ast_free(pvt);
 | |
| e_return:
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static int mbl_load_config(void)
 | |
| {
 | |
| 	struct ast_config *cfg;
 | |
| 	const char *cat;
 | |
| 	struct ast_variable *v;
 | |
| 	struct ast_flags config_flags = { 0 };
 | |
| 
 | |
| 	cfg = ast_config_load(MBL_CONFIG, config_flags);
 | |
| 	if (!cfg) {
 | |
| 		cfg = ast_config_load(MBL_CONFIG_OLD, config_flags);
 | |
| 	}
 | |
| 	if (!cfg)
 | |
| 		return -1;
 | |
| 
 | |
| 	/* parse [general] section */
 | |
| 	for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
 | |
| 		if (!strcasecmp(v->name, "interval")) {
 | |
| 			if (!sscanf(v->value, "%d", &discovery_interval)) {
 | |
| 				ast_log(LOG_NOTICE, "error parsing 'interval' in general section, using default value\n");
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* load adapters */
 | |
| 	for (cat = ast_category_browse(cfg, NULL); cat; cat = ast_category_browse(cfg, cat)) {
 | |
| 		if (!strcasecmp(cat, "adapter")) {
 | |
| 			mbl_load_adapter(cfg, cat);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (AST_RWLIST_EMPTY(&adapters)) {
 | |
| 		ast_log(LOG_ERROR,
 | |
| 			"***********************************************************************\n"
 | |
| 			"No adapters could be loaded from the configuration file.\n"
 | |
| 			"Please review mobile.conf. See sample for details.\n"
 | |
| 			"***********************************************************************\n"
 | |
| 		       );
 | |
| 		ast_config_destroy(cfg);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* now load devices */
 | |
| 	for (cat = ast_category_browse(cfg, NULL); cat; cat = ast_category_browse(cfg, cat)) {
 | |
| 		if (strcasecmp(cat, "general") && strcasecmp(cat, "adapter")) {
 | |
| 			mbl_load_device(cfg, cat);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ast_config_destroy(cfg);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Check if the module is unloading.
 | |
|  * \retval 0 not unloading
 | |
|  * \retval 1 unloading
 | |
|  */
 | |
| static inline int check_unloading()
 | |
| {
 | |
| 	int res;
 | |
| 	ast_mutex_lock(&unload_mutex);
 | |
| 	res = unloading_flag;
 | |
| 	ast_mutex_unlock(&unload_mutex);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Set the unloading flag.
 | |
|  */
 | |
| static inline void set_unloading()
 | |
| {
 | |
| 	ast_mutex_lock(&unload_mutex);
 | |
| 	unloading_flag = 1;
 | |
| 	ast_mutex_unlock(&unload_mutex);
 | |
| }
 | |
| 
 | |
| static int unload_module(void)
 | |
| {
 | |
| 	struct mbl_pvt *pvt;
 | |
| 	struct adapter_pvt *adapter;
 | |
| 
 | |
| 	/* First, take us out of the channel loop */
 | |
| 	ast_channel_unregister(&mbl_tech);
 | |
| 
 | |
| 	/* Unregister the CLI & APP */
 | |
| 	ast_cli_unregister_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
 | |
| 	ast_unregister_application(app_mblstatus);
 | |
| 	ast_unregister_application(app_mblsendsms);
 | |
| 
 | |
| 	/* signal everyone we are unloading */
 | |
| 	set_unloading();
 | |
| 
 | |
| 	/* Kill the discovery thread */
 | |
| 	if (discovery_thread != AST_PTHREADT_NULL) {
 | |
| 		pthread_kill(discovery_thread, SIGURG);
 | |
| 		pthread_join(discovery_thread, NULL);
 | |
| 	}
 | |
| 
 | |
| 	/* stop the sco listener threads */
 | |
| 	AST_RWLIST_WRLOCK(&adapters);
 | |
| 	AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
 | |
| 		pthread_kill(adapter->sco_listener_thread, SIGURG);
 | |
| 		pthread_join(adapter->sco_listener_thread, NULL);
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&adapters);
 | |
| 
 | |
| 	/* Destroy the device list */
 | |
| 	AST_RWLIST_WRLOCK(&devices);
 | |
| 	while ((pvt = AST_RWLIST_REMOVE_HEAD(&devices, entry))) {
 | |
| 		if (pvt->monitor_thread != AST_PTHREADT_NULL) {
 | |
| 			pthread_kill(pvt->monitor_thread, SIGURG);
 | |
| 			pthread_join(pvt->monitor_thread, NULL);
 | |
| 		}
 | |
| 
 | |
| 		close(pvt->sco_socket);
 | |
| 		close(pvt->rfcomm_socket);
 | |
| 
 | |
| 		msg_queue_flush(pvt);
 | |
| 
 | |
| 		if (pvt->hfp) {
 | |
| 			ast_free(pvt->hfp);
 | |
| 		}
 | |
| 
 | |
| 		ast_smoother_free(pvt->bt_out_smoother);
 | |
| 		ast_smoother_free(pvt->bt_in_smoother);
 | |
| 		ast_dsp_free(pvt->dsp);
 | |
| 		ast_sched_context_destroy(pvt->sched);
 | |
| 		ast_free(pvt);
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&devices);
 | |
| 
 | |
| 	/* Destroy the adapter list */
 | |
| 	AST_RWLIST_WRLOCK(&adapters);
 | |
| 	while ((adapter = AST_RWLIST_REMOVE_HEAD(&adapters, entry))) {
 | |
| 		close(adapter->sco_socket);
 | |
| 		io_context_destroy(adapter->io);
 | |
| 		io_context_destroy(adapter->accept_io);
 | |
| 		hci_close_dev(adapter->hci_socket);
 | |
| 		ast_free(adapter);
 | |
| 	}
 | |
| 	AST_RWLIST_UNLOCK(&adapters);
 | |
| 
 | |
| 	if (sdp_session)
 | |
| 		sdp_close(sdp_session);
 | |
| 
 | |
| 	ao2_ref(mbl_tech.capabilities, -1);
 | |
| 	mbl_tech.capabilities = NULL;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int load_module(void)
 | |
| {
 | |
| 
 | |
| 	int dev_id, s;
 | |
| 
 | |
| 	if (!(mbl_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
 | |
| 		return AST_MODULE_LOAD_DECLINE;
 | |
| 	}
 | |
| 
 | |
| 	ast_format_cap_append(mbl_tech.capabilities, DEVICE_FRAME_FORMAT, 0);
 | |
| 	/* Check if we have Bluetooth, no point loading otherwise... */
 | |
| 	dev_id = hci_get_route(NULL);
 | |
| 
 | |
| 	s = hci_open_dev(dev_id);
 | |
| 	if (dev_id < 0 || s < 0) {
 | |
| 		ast_log(LOG_ERROR, "No Bluetooth devices found. Not loading module.\n");
 | |
| 		ao2_ref(mbl_tech.capabilities, -1);
 | |
| 		mbl_tech.capabilities = NULL;
 | |
| 		hci_close_dev(s);
 | |
| 		return AST_MODULE_LOAD_DECLINE;
 | |
| 	}
 | |
| 
 | |
| 	hci_close_dev(s);
 | |
| 
 | |
| 	if (mbl_load_config()) {
 | |
| 		ast_log(LOG_ERROR, "Errors reading config file %s. Not loading module.\n", MBL_CONFIG);
 | |
| 		ao2_ref(mbl_tech.capabilities, -1);
 | |
| 		mbl_tech.capabilities = NULL;
 | |
| 		return AST_MODULE_LOAD_DECLINE;
 | |
| 	}
 | |
| 
 | |
| 	sdp_session = sdp_register();
 | |
| 
 | |
| 	/* Spin the discovery thread */
 | |
| 	if (ast_pthread_create_background(&discovery_thread, NULL, do_discovery, NULL) < 0) {
 | |
| 		ast_log(LOG_ERROR, "Unable to create discovery thread.\n");
 | |
| 		goto e_cleanup;
 | |
| 	}
 | |
| 
 | |
| 	/* register our channel type */
 | |
| 	if (ast_channel_register(&mbl_tech)) {
 | |
| 		ast_log(LOG_ERROR, "Unable to register channel class %s\n", "Mobile");
 | |
| 		goto e_cleanup;
 | |
| 	}
 | |
| 
 | |
| 	ast_cli_register_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
 | |
| 	ast_register_application(app_mblstatus, mbl_status_exec, mblstatus_synopsis, mblstatus_desc);
 | |
| 	ast_register_application(app_mblsendsms, mbl_sendsms_exec, mblsendsms_synopsis, mblsendsms_desc);
 | |
| 
 | |
| 	return AST_MODULE_LOAD_SUCCESS;
 | |
| 
 | |
| e_cleanup:
 | |
| 	unload_module();
 | |
| 
 | |
| 	return AST_MODULE_LOAD_DECLINE;
 | |
| }
 | |
| 
 | |
| AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Bluetooth Mobile Device Channel Driver",
 | |
| 	.support_level = AST_MODULE_SUPPORT_EXTENDED,
 | |
| 	.load = load_module,
 | |
| 	.unload = unload_module,
 | |
| 	.load_pri = AST_MODPRI_CHANNEL_DRIVER,
 | |
| );
 |