Skip to content
Snippets Groups Projects
chan_mobile.c 125 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			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) {
    
    Josh Soref's avatar
    Josh Soref committed
    		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)) {
    
    Josh Soref's avatar
    Josh Soref committed
    		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) {
    
    Josh Soref's avatar
    Josh Soref committed
    		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;
    
    	/* 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);
    
    	/* 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;
    
    	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);
    
    		}
    	}
    
    	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;
    
    	}
    
    	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_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;
    
    	if (!(mbl_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
    
    
    	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:
    
    	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,