/* * chan_dongle * * Copyright (C) 2011-2015 * bg * http://www.e1550.mobi * * chan_dongle is based on chan_datacard by * * Artem Makhutov * http://www.makhutov.org * * Dmitry Vagin * * chan_datacard is based on chan_mobile by Digium * (Mark Spencer ) * * 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 UMTS Voice Dongle channel driver * * \author Artem Makhutov * \author Dave Bowerman * \author Dmitry Vagin * \author bg * \author Max von Buelow * * \ingroup channel_drivers */ #include "ast_config.h" #if ASTERISK_VERSION_NUM < 140000 /* 14- */ ASTERISK_FILE_VERSION(__FILE__, "$Rev: " PACKAGE_REVISION " $") #endif /* 14- */ #include /* AST_DECLARE_STRING_FIELDS for asterisk/manager.h */ #include #include #include #include /* AST_MODULE_LOAD_DECLINE ... */ #include /* ast_timer_open() ast_timer_fd() */ #include /* S_IRUSR | S_IRGRP | S_IROTH */ #include /* struct termios tcgetattr() tcsetattr() */ #include /* pthread_t pthread_kill() pthread_join() */ #include /* O_RDWR O_NOCTTY */ #include /* SIGURG */ #include "ast_compat.h" /* asterisk compatibility fixes */ #if ASTERISK_VERSION_NUM >= 130000 /* 13+ */ #include #include #endif /* ^13+ */ #include "chan_dongle.h" #include "at_response.h" /* at_res_t */ #include "at_queue.h" /* struct at_queue_task_cmd at_queue_head_cmd() */ #include "at_command.h" /* at_cmd2str() */ #include "mutils.h" /* ITEMS_OF() */ #include "at_read.h" #include "cli.h" #include "app.h" #include "manager.h" #include "channel.h" /* channel_queue_hangup() */ #include "dc_config.h" /* dc_uconfig_fill() dc_gconfig_fill() dc_sconfig_fill() */ #include "pdiscovery.h" /* pdiscovery_lookup() pdiscovery_init() pdiscovery_fini() */ #include "smsdb.h" #include "error.h" EXPORT_DEF const char * const dev_state_strs[4] = { "stop", "restart", "remove", "start" }; EXPORT_DEF public_state_t * gpublic; #if ASTERISK_VERSION_NUM >= 100000 && ASTERISK_VERSION_NUM < 130000 /* 10-13 */ EXPORT_DEF struct ast_format chan_dongle_format; EXPORT_DEF struct ast_format_cap * chan_dongle_format_cap; #endif /* ^10-13 */ static int public_state_init(struct public_state * state); /*! * Get status of the dongle. It might happen that the device disappears * (e.g. due to a USB unplug). * * \return 0 if device seems ok, non-0 if it seems not available */ static int port_status (int fd) { struct termios t; if (fd < 0) { return -1; } return tcgetattr (fd, &t); } #/* return length of lockname */ static int lock_build(const char * devname, char * buf, unsigned length) { const char * basename; char resolved_path[PATH_MAX]; /* follow symlinks */ if(realpath(devname, resolved_path) != NULL) devname = resolved_path; basename = strrchr(devname, '/'); if(basename) basename++; else basename = devname; /* NOTE: use system system wide lock directory */ #if defined(__FreeBSD__) return snprintf(buf, length, "/var/spool/lock/LCK..%s", basename); #else return snprintf(buf, length, "/var/lock/LCK..%s", basename); #endif } #/* return 0 on error */ static int lock_create(const char * lockfile) { int fd; int len = 0; char pidb[21]; fd = open(lockfile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); if(fd >= 0) { /* NOTE: bg: i assume next open reuse same fd - not thread-safe */ len = snprintf(pidb, sizeof(pidb), "%d %d", getpid(), fd); len = write(fd, pidb, len); close(fd); } else { ast_log(LOG_ERROR, "open('%s') failed: %s\n", lockfile, strerror(errno)); } return len; } #/* return pid of owner, 0 if free */ EXPORT_DEF int lock_try(const char * devname, char ** lockname) { int fd; int len; int pid = 0; int assigned; int fd2; char name[1024]; char buffer[65]; lock_build(devname, name, sizeof(name)); /* FIXME: rise conditions: some time between lock check and got lock */ fd = open(name, O_RDONLY); if(fd >= 0) { len = read(fd, buffer, sizeof(buffer) - 1); if(len > 0) { buffer[len] = 0; assigned = sscanf(buffer, "%d %d", &len, &fd2); if(assigned > 0 && kill(len, 0) == 0) { if(len == getpid() && assigned > 1) { if(port_status(fd2) == 0) pid = len; } else pid = len; } } close(fd); } if(pid == 0) { unlink(name); lock_create(name); *lockname = ast_strdup(name); } return pid; } #/* */ EXPORT_DEF void closetty(int fd, char ** lockfname) { close(fd); /* remove lock */ unlink(*lockfname); ast_free(*lockfname); *lockfname = NULL; } EXPORT_DEF int opentty (const char* dev, char ** lockfile) { int flags; int pid; int fd; struct termios term_attr; char buf[40]; pid = lock_try(dev, lockfile); if(pid != 0) { ast_log (LOG_WARNING, "%s already used by process %d\n", dev, pid); return -1; } fd = open(dev, O_RDWR | O_NOCTTY); if (fd < 0) { flags = errno; closetty(fd, lockfile); snprintf(buf, sizeof(buf), "Open Failed\r\nErrorCode: %d", flags); manager_event_message_raw("DonglePortFail", dev, buf); ast_log (LOG_WARNING, "unable to open %s: %s\n", dev, strerror(flags)); return -1; } /* Put the terminal into exclusive mode. All other open(2)s by * non-root will fail with EBUSY. */ if (ioctl(fd, TIOCEXCL) != 0) { ast_log(LOG_WARNING, "ioctl(TIOCEXCL) failed for %s: %s\n", dev, strerror(errno)); } flags = fcntl(fd, F_GETFD); if (flags == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { flags = errno; closetty(fd, lockfile); ast_log (LOG_WARNING, "fcntl(F_GETFD/F_SETFD) failed for %s: %s\n", dev, strerror(flags)); return -1; } if (tcgetattr (fd, &term_attr) != 0) { flags = errno; closetty(fd, lockfile); ast_log (LOG_WARNING, "tcgetattr() failed for %s: %s\n", dev, strerror(flags)); return -1; } term_attr.c_cflag = B115200 | CS8 | CREAD | CRTSCTS; term_attr.c_iflag = 0; term_attr.c_oflag = 0; term_attr.c_lflag = 0; term_attr.c_cc[VMIN] = 1; term_attr.c_cc[VTIME] = 0; if (tcsetattr (fd, TCSAFLUSH, &term_attr) != 0) { ast_log (LOG_WARNING, "tcsetattr(TCSAFLUSH) failed for %s: %s\n", dev, strerror(errno)); } return fd; } #/* phone monitor thread pvt cleanup */ static void disconnect_dongle (struct pvt* pvt) { struct cpvt * cpvt, * next; if (!PVT_NO_CHANS(pvt)) { ast_debug (1, "[%s] Dongle disconnecting, hanging up channels\n", PVT_ID(pvt)); for(cpvt = pvt->chans.first; cpvt; cpvt = next) { next = cpvt->entry.next; at_hangup_immediality(cpvt); CPVT_RESET_FLAGS(cpvt, CALL_FLAG_NEED_HANGUP); change_channel_state(cpvt, CALL_STATE_RELEASED, 0); } } at_queue_flush(pvt); pvt->last_dialed_cpvt = NULL; closetty (pvt->audio_fd, &pvt->alock); closetty (pvt->data_fd, &pvt->dlock); pvt->data_fd = -1; pvt->audio_fd = -1; if(pvt->dsp) ast_dsp_digitreset(pvt->dsp); pvt_on_remove_last_channel(pvt); /* pvt->a_write_rb */ pvt->dtmf_digit = 0; pvt->rings = 0; // else { /* unaffected in case of restart */ pvt->gsm_reg_status = -1; pvt->rssi = 0; pvt->linkmode = 0; pvt->linksubmode = 0; ast_copy_string (pvt->provider_name, "NONE", sizeof (pvt->provider_name)); pvt->manufacturer[0] = '\0'; pvt->model[0] = '\0'; pvt->firmware[0] = '\0'; pvt->imei[0] = '\0'; pvt->imsi[0] = '\0'; pvt->has_subscriber_number = 0; ast_copy_string (pvt->subscriber_number, "Unknown", sizeof (pvt->subscriber_number)); pvt->location_area_code[0] = '\0'; pvt->cell_id[0] = '\0'; pvt->sms_scenter[0] = '\0'; pvt->gsm_registered = 0; pvt->has_sms = 0; pvt->has_voice = 0; pvt->has_call_waiting = 0; } pvt->connected = 0; pvt->initialized = 0; pvt->has_call_waiting = 0; /* FIXME: LOST real device state */ pvt->dialing = 0; pvt->ring = 0; pvt->cwaiting = 0; pvt->outgoing_sms = 0; pvt->incoming_sms_index = -1U; pvt->volume_sync_step = VOLUME_SYNC_BEGIN; pvt->current_state = DEV_STATE_STOPPED; /* clear statictics */ memset(&pvt->stat, 0, sizeof(pvt->stat)); ast_copy_string (PVT_STATE(pvt, data_tty), CONF_UNIQ(pvt, data_tty), sizeof (PVT_STATE(pvt, data_tty))); ast_copy_string (PVT_STATE(pvt, audio_tty), CONF_UNIQ(pvt, audio_tty), sizeof (PVT_STATE(pvt, audio_tty))); ast_verb (3, "[%s] Dongle has disconnected\n", PVT_ID(pvt)); manager_event_device_status(PVT_ID(pvt), "Disconnect"); } #define SMS_INBOX_BIT(index) ((sms_inbox_item_type)(1) << (index % SMS_INBOX_ITEM_BITS)) #define SMS_INBOX_INDEX(index) (index / SMS_INBOX_ITEM_BITS) static int is_sms_inbox_index_valid(const struct pvt* pvt, int index) { if (index < 0 || index >= (int) SMS_INDEX_MAX) { ast_log(LOG_WARNING, "[%s] SMS index [%d] out of range\n", PVT_ID(pvt), index); return 0; } return 1; } EXPORT_DEF int sms_inbox_set(struct pvt* pvt, int index) { if (!is_sms_inbox_index_valid(pvt, index)) { return 0; } pvt->incoming_sms_inbox[SMS_INBOX_INDEX(index)] |= SMS_INBOX_BIT(index); return 1; } EXPORT_DEF int sms_inbox_clear(struct pvt* pvt, int index) { if (!is_sms_inbox_index_valid(pvt, index)) { return 0; } pvt->incoming_sms_inbox[SMS_INBOX_INDEX(index)] &= ~SMS_INBOX_BIT(index); return 1; } EXPORT_DEF int is_sms_inbox_set(const struct pvt* pvt, int index) { if (!is_sms_inbox_index_valid(pvt, index)) { return 0; } return pvt->incoming_sms_inbox[SMS_INBOX_INDEX(index)] & SMS_INBOX_BIT(index); } #undef SMS_INBOX_INDEX #undef SMS_INBOX_BIT /* anybody wrote some to device before me, and not read results, clean pending results here */ #/* */ EXPORT_DEF void clean_read_data(const char * devname, int fd) { char buf[2*1024]; struct ringbuffer rb; int iovcnt; int t; rb_init (&rb, buf, sizeof (buf)); for (t = 0; at_wait(fd, &t); t = 0) { iovcnt = at_read (fd, devname, &rb); ast_debug (4, "[%s] drop %u bytes of pending data before initialization\n", devname, (unsigned)rb_used(&rb)); /* drop read */ rb_init (&rb, buf, sizeof (buf)); if (iovcnt == 0) break; } } static void handle_expired_reports(struct pvt *pvt) { char dst[SMSDB_DST_MAX_LEN]; char payload[SMSDB_PAYLOAD_MAX_LEN]; ssize_t payload_len = smsdb_outgoing_purge_one(dst, payload); if (payload_len >= 0) { ast_verb (3, "[%s] TTL payload: %.*s\n", PVT_ID(pvt), (int) payload_len, payload); channel_var_t vars[] = { { "SMS_REPORT_PAYLOAD", payload }, { "SMS_REPORT_TS", "" }, { "SMS_REPORT_DT", "" }, { "SMS_REPORT_SUCCESS", "0" }, { "SMS_REPORT_TYPE", "t" }, { "SMS_REPORT", "" }, { NULL, NULL }, }; start_local_channel(pvt, "report", dst, vars); manager_event_report(PVT_ID(pvt), payload, payload_len, "", "", 0, 2, ""); } } /*! * \brief Check if the module is unloading. * \retval 0 not unloading * \retval 1 unloading */ static void* do_monitor_phone (void* data) { struct pvt* pvt = (struct pvt*) data; at_res_t at_res; const struct at_queue_cmd * ecmd; int t; char buf[2*1024]; struct ringbuffer rb; struct iovec iov[2]; int iovcnt; char dev[sizeof(PVT_ID(pvt))]; int fd; int read_result = 0; pvt->timeout = DATA_READ_TIMEOUT; rb_init (&rb, buf, sizeof (buf)); ast_mutex_lock (&pvt->lock); /* 4 reduce locking time make copy of this readonly fields */ fd = pvt->data_fd; ast_copy_string(dev, PVT_ID(pvt), sizeof(dev)); clean_read_data(dev, fd); /* schedule dongle initilization */ if (at_enqueue_initialization(&pvt->sys_chan, CMD_AT)) { ast_log (LOG_ERROR, "[%s] Error adding initialization commands to queue\n", dev); goto e_cleanup; } /* Poll first SMS, if any */ if (at_poll_sms(pvt) == 0) { ast_debug (1, "[%s] Polling first SMS message\n", PVT_ID(pvt)); } ast_mutex_unlock (&pvt->lock); while (1) { ast_mutex_lock (&pvt->lock); handle_expired_reports(pvt); if (port_status (pvt->data_fd) || port_status (pvt->audio_fd)) { ast_log (LOG_ERROR, "[%s] Lost connection to Dongle\n", dev); goto e_cleanup; } if(pvt->terminate_monitor) { ast_log (LOG_NOTICE, "[%s] stopping by %s request\n", dev, dev_state2str(pvt->desired_state)); goto e_restart; } t = at_queue_timeout(pvt); if(t < 0) t = pvt->timeout; ast_mutex_unlock (&pvt->lock); if (!at_wait (fd, &t)) { ast_mutex_lock (&pvt->lock); ecmd = at_queue_head_cmd (pvt); if(ecmd) { ast_log (LOG_ERROR, "[%s] timedout while waiting '%s' in response to '%s'\n", dev, at_res2str (ecmd->res), at_cmd2str (ecmd->cmd)); goto e_cleanup; } at_enqueue_ping(&pvt->sys_chan); ast_mutex_unlock (&pvt->lock); continue; } /* FIXME: access to device not locked */ iovcnt = at_read (fd, dev, &rb); if (iovcnt < 0) { break; } ast_mutex_lock (&pvt->lock); PVT_STAT(pvt, d_read_bytes) += iovcnt; ast_mutex_unlock (&pvt->lock); while ((iovcnt = at_read_result_iov (dev, &read_result, &rb, iov)) > 0) { at_res = at_read_result_classification (&rb, iov[0].iov_len + iov[1].iov_len); ast_mutex_lock (&pvt->lock); PVT_STAT(pvt, at_responses) ++; if (at_response (pvt, iov, iovcnt, at_res) || at_queue_run(pvt)) { goto e_cleanup; } ast_mutex_unlock (&pvt->lock); } } ast_mutex_lock (&pvt->lock); e_cleanup: if (!pvt->initialized) { // TODO: send monitor event ast_verb (3, "[%s] Error initializing Dongle\n", dev); } /* it real, unsolicited disconnect */ pvt->terminate_monitor = 0; e_restart: disconnect_dongle (pvt); // pvt->monitor_running = 0; ast_mutex_unlock (&pvt->lock); /* TODO: wakeup discovery thread after some delay */ return NULL; } static inline int start_monitor (struct pvt * pvt) { if (ast_pthread_create_background (&pvt->monitor_thread, NULL, do_monitor_phone, pvt) < 0) { pvt->monitor_thread = AST_PTHREADT_NULL; return 0; } return 1; } #/* */ static void pvt_stop(struct pvt * pvt) { pthread_t id; if(pvt->monitor_thread != AST_PTHREADT_NULL) { pvt->terminate_monitor = 1; pthread_kill (pvt->monitor_thread, SIGURG); id = pvt->monitor_thread; ast_mutex_unlock (&pvt->lock); pthread_join (id, NULL); ast_mutex_lock (&pvt->lock); pvt->terminate_monitor = 0; pvt->monitor_thread = AST_PTHREADT_NULL; } // pvt->current_state = DEV_STATE_STOPPED; } #/* called with pvt lock hold */ static int pvt_discovery(struct pvt * pvt) { char devname[DEVNAMELEN]; char imei[IMEI_SIZE+1]; char imsi[IMSI_SIZE+1]; int resolved; if(CONF_UNIQ(pvt, data_tty)[0] == 0 && CONF_UNIQ(pvt, audio_tty)[0] == 0) { char * data_tty; char * audio_tty; ast_copy_string(devname, PVT_ID(pvt), sizeof(devname)); ast_copy_string(imei, CONF_UNIQ(pvt, imei), sizeof(imei)); ast_copy_string(imsi, CONF_UNIQ(pvt, imsi), sizeof(imsi)); ast_debug(3, "[%s] Trying ports discovery for%s%s%s%s\n", PVT_ID(pvt), imei[0] == 0 ? "" : " IMEI ", imei, imsi[0] == 0 ? "" : " IMSI ", imsi ); ast_mutex_unlock (&pvt->lock); //sleep(10); // test resolved = pdiscovery_lookup(devname, imei, imsi, &data_tty, &audio_tty); ast_mutex_lock (&pvt->lock); if(resolved) { ast_copy_string (PVT_STATE(pvt, data_tty), data_tty, sizeof (PVT_STATE(pvt, data_tty))); ast_copy_string (PVT_STATE(pvt, audio_tty), audio_tty, sizeof (PVT_STATE(pvt, audio_tty))); ast_free(audio_tty); ast_free(data_tty); ast_verb (3, "[%s]%s%s%s%s found on data_tty=%s audio_tty=%s\n", PVT_ID(pvt), imei[0] == 0 ? "" : " IMEI ", imei, imsi[0] == 0 ? "" : " IMSI ", imsi, PVT_STATE(pvt, data_tty), PVT_STATE(pvt, audio_tty) ); } else { ast_debug(3, "[%s] Not found ports for%s%s%s%s\n", PVT_ID(pvt), imei[0] == 0 ? "" : " IMEI ", imei, imsi[0] == 0 ? "" : " IMSI ", imsi ); } } else { ast_copy_string (PVT_STATE(pvt, data_tty), CONF_UNIQ(pvt, data_tty), sizeof (PVT_STATE(pvt, data_tty))); ast_copy_string (PVT_STATE(pvt, audio_tty), CONF_UNIQ(pvt, audio_tty), sizeof (PVT_STATE(pvt, audio_tty))); resolved = 1; } return ! resolved; } #/* */ static void pvt_start(struct pvt * pvt) { long flags; /* prevent start_monitor() multiple times and on turned off devices */ if (pvt->connected || pvt->desired_state != DEV_STATE_STARTED) { // || (pvt->monitor_thread != AST_PTHREADT_NULL && // (pthread_kill(pvt->monitor_thread, 0) == 0 || errno != ESRCH)) return; } pvt_stop(pvt); if (pvt_discovery(pvt)) { return; } ast_verb(3, "[%s] Trying to connect on %s...\n", PVT_ID(pvt), PVT_STATE(pvt, data_tty)); pvt->data_fd = opentty(PVT_STATE(pvt, data_tty), &pvt->dlock); if (pvt->data_fd < 0) { return; } // TODO: delay until device activate voice call or at pvt_on_create_1st_channel() pvt->audio_fd = opentty(PVT_STATE(pvt, audio_tty), &pvt->alock); if (pvt->audio_fd < 0) { goto cleanup_datafd; } if (!start_monitor(pvt)) { goto cleanup_audiofd; } /* Set data_fd and audio_fd to non-blocking. This appears to fix * incidental deadlocks occurring with Asterisk 12+ or with * jitterbuffer enabled. Apparently Asterisk can call the * (audio) read function for sockets that don't have data to * read(). */ flags = fcntl(pvt->data_fd, F_GETFL); fcntl(pvt->data_fd, F_SETFL, flags | O_NONBLOCK); flags = fcntl(pvt->audio_fd, F_GETFL); fcntl(pvt->audio_fd, F_SETFL, flags | O_NONBLOCK); pvt->connected = 1; pvt->current_state = DEV_STATE_STARTED; manager_event_device_status(PVT_ID(pvt), "Connect"); ast_verb(3, "[%s] Dongle has connected, initializing...\n", PVT_ID(pvt)); return; cleanup_audiofd: closetty(pvt->audio_fd, &pvt->alock); cleanup_datafd: closetty(pvt->data_fd, &pvt->dlock); } #/* */ static void pvt_free(struct pvt * pvt) { at_queue_flush(pvt); if(pvt->dsp) ast_dsp_free(pvt->dsp); ast_mutex_unlock(&pvt->lock); ast_free(pvt); } #/* */ static void pvt_destroy(struct pvt * pvt) { ast_mutex_lock(&pvt->lock); pvt_stop(pvt); pvt_free(pvt); } static void * do_discovery(void * arg) { struct public_state * state = (struct public_state *) arg; struct pvt * pvt; while(state->unloading_flag == 0) { /* read lock for avoid deadlock when IMEI/IMSI discovery */ AST_RWLIST_RDLOCK(&state->devices); AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); pvt->must_remove = 0; if(pvt->restart_time == RESTATE_TIME_NOW && pvt->desired_state != pvt->current_state) { switch(pvt->desired_state) { case DEV_STATE_RESTARTED: pvt_stop(pvt); pvt->desired_state = DEV_STATE_STARTED; /* fall through */ case DEV_STATE_STARTED: pvt_start(pvt); break; case DEV_STATE_REMOVED: pvt_stop(pvt); pvt->must_remove = 1; break; case DEV_STATE_STOPPED: pvt_stop(pvt); } } ast_mutex_unlock (&pvt->lock); } AST_RWLIST_UNLOCK (&state->devices); /* actual device removal here for avoid long (discovery) time write lock on device list in loop above */ AST_RWLIST_WRLOCK(&state->devices); AST_RWLIST_TRAVERSE_SAFE_BEGIN(&state->devices, pvt, entry) { ast_mutex_lock(&pvt->lock); if(pvt->must_remove) { AST_RWLIST_REMOVE_CURRENT(entry); pvt_free(pvt); } else ast_mutex_unlock(&pvt->lock); } AST_RWLIST_TRAVERSE_SAFE_END; AST_RWLIST_UNLOCK(&state->devices); /* Go to sleep (only if we are not unloading) */ if (state->unloading_flag == 0) { sleep(SCONF_GLOBAL(state, discovery_interval)); } } return NULL; } #/* */ static int discovery_restart(public_state_t * state) { if(state->discovery_thread == AST_PTHREADT_STOP) return 0; ast_mutex_lock(&state->discovery_lock); if (state->discovery_thread == pthread_self()) { ast_mutex_unlock(&state->discovery_lock); ast_log(LOG_WARNING, "Cannot kill myself\n"); return -1; } if (state->discovery_thread != AST_PTHREADT_NULL) { /* Wake up the thread */ pthread_kill(state->discovery_thread, SIGURG); } else { /* Start a new monitor */ if (ast_pthread_create_background(&state->discovery_thread, NULL, do_discovery, state) < 0) { ast_mutex_unlock(&state->discovery_lock); ast_log(LOG_ERROR, "Unable to start discovery thread\n"); return -1; } } ast_mutex_unlock(&state->discovery_lock); return 0; } #/* */ static void discovery_stop(public_state_t * state) { /* signal for discovery unloading */ state->unloading_flag = 1; ast_mutex_lock(&state->discovery_lock); if (state->discovery_thread && (state->discovery_thread != AST_PTHREADT_STOP) && (state->discovery_thread != AST_PTHREADT_NULL)) { // pthread_cancel(state->discovery_thread); pthread_kill(state->discovery_thread, SIGURG); pthread_join(state->discovery_thread, NULL); } state->discovery_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&state->discovery_lock); } #/* */ EXPORT_DEF void pvt_on_create_1st_channel(struct pvt* pvt) { mixb_init (&pvt->a_write_mixb, pvt->a_write_buf, sizeof (pvt->a_write_buf)); // rb_init (&pvt->a_write_rb, pvt->a_write_buf, sizeof (pvt->a_write_buf)); if(!pvt->a_timer) pvt->a_timer = ast_timer_open (); /* FIXME: do on each channel switch */ if(pvt->dsp) ast_dsp_digitreset (pvt->dsp); pvt->dtmf_digit = 0; pvt->dtmf_begin_time.tv_sec = 0; pvt->dtmf_begin_time.tv_usec = 0; pvt->dtmf_end_time.tv_sec = 0; pvt->dtmf_end_time.tv_usec = 0; manager_event_device_status(PVT_ID(pvt), "Used"); } #/* */ EXPORT_DEF void pvt_on_remove_last_channel(struct pvt* pvt) { if (pvt->a_timer) { ast_timer_close(pvt->a_timer); pvt->a_timer = NULL; } manager_event_device_status(PVT_ID(pvt), "Free"); } #define SET_BIT(dw_array,bitno) do { (dw_array)[(bitno) >> 5] |= 1 << ((bitno) & 31) ; } while(0) #define TEST_BIT(dw_array,bitno) ((dw_array)[(bitno) >> 5] & 1 << ((bitno) & 31)) #/* */ EXPORT_DEF int pvt_get_pseudo_call_idx(const struct pvt * pvt) { struct cpvt * cpvt; int * bits; int dwords = ((MAX_CALL_IDX + sizeof(*bits) - 1) / sizeof(*bits)); bits = alloca(dwords * sizeof(*bits)); memset(bits, 0, dwords * sizeof(*bits)); AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry) { SET_BIT(bits, cpvt->call_idx); } for(dwords = 1; dwords <= MAX_CALL_IDX; dwords++) { if(!TEST_BIT(bits, dwords)) return dwords; } return 0; } #undef TEST_BIT #undef SET_BIT #/* */ static int is_dial_possible2(const struct pvt * pvt, int opts, const struct cpvt * ignore_cpvt) { struct cpvt * cpvt; int hold = 0; int active = 0; // FIXME: allow HOLD states for CONFERENCE int use_call_waiting = opts & CALL_FLAG_HOLD_OTHER; if(pvt->ring || pvt->cwaiting || pvt->dialing) return 0; AST_LIST_TRAVERSE(&pvt->chans, cpvt, entry) { switch(cpvt->state) { case CALL_STATE_INIT: if(cpvt != ignore_cpvt) return 0; break; case CALL_STATE_DIALING: case CALL_STATE_ALERTING: case CALL_STATE_INCOMING: case CALL_STATE_WAITING: return 0; case CALL_STATE_ACTIVE: if(hold || !use_call_waiting) return 0; active++; break; case CALL_STATE_ONHOLD: if(active || !use_call_waiting) return 0; hold++; break; case CALL_STATE_RELEASED: ; } } return 1; } #/* */ EXPORT_DEF int is_dial_possible(const struct pvt * pvt, int opts) { return is_dial_possible2(pvt, opts, NULL); } #/* */ EXPORT_DECL int pvt_enabled(const struct pvt * pvt) { return pvt->current_state == DEV_STATE_STARTED && (pvt->desired_state == pvt->current_state || pvt->restart_time == RESTATE_TIME_CONVENIENT); } #/* */ EXPORT_DEF int ready4voice_call(const struct pvt* pvt, const struct cpvt * current_cpvt, int opts) { if(!pvt->connected || !pvt->initialized || !pvt->has_voice || !pvt->gsm_registered || !pvt_enabled(pvt)) { return 0; } return is_dial_possible2(pvt, opts, current_cpvt); } #/* */ static int can_dial(struct pvt* pvt, int opts, const struct ast_channel * requestor) { /* not allow hold requester channel :) */ /* FIXME: requestor may be just proxy/masquerade for real channel */ // use ast_bridged_channel(chan) ? // use requestor->tech->get_base_channel() ? if((opts & CALL_FLAG_HOLD_OTHER) == CALL_FLAG_HOLD_OTHER && channels_loop(pvt, requestor)) return 0; return ready4voice_call(pvt, NULL, opts); } #/* return locked pvt or NULL */ EXPORT_DEF struct pvt * find_device_ex(struct public_state * state, const char * name) { struct pvt * pvt; AST_RWLIST_RDLOCK(&state->devices); AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); if (!strcmp (PVT_ID(pvt), name)) { break; } ast_mutex_unlock (&pvt->lock); } AST_RWLIST_UNLOCK(&state->devices); return pvt; } #/* return locked pvt or NULL */ EXPORT_DEF struct pvt * find_device_ext (const char * name) { struct pvt * pvt = find_device(name); if (pvt) { if (!pvt_enabled(pvt)) { ast_mutex_unlock (&pvt->lock); chan_dongle_err = E_DEVICE_DISABLED; pvt = NULL; } } else { chan_dongle_err = E_DEVICE_NOT_FOUND; } return pvt; } #/* like find_device but for resource spec; return locked! pvt or NULL */ EXPORT_DEF struct pvt * find_device_by_resource_ex(struct public_state * state, const char * resource, int opts, const struct ast_channel * requestor, int * exists) { int group; size_t i; size_t j; size_t c; size_t last_used; struct pvt * pvt; struct pvt * found = NULL; struct pvt * round_robin[MAXDONGLEDEVICES]; *exists = 0; /* Find requested device and make sure it's connected and initialized. */ AST_RWLIST_RDLOCK(&state->devices); if (((resource[0] == 'g') || (resource[0] == 'G')) && ((resource[1] >= '0') && (resource[1] <= '9'))) { errno = 0; group = (int) strtol (&resource[1], (char**) NULL, 10); if (errno != EINVAL) { AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); if (CONF_SHARED(pvt, group) == group) { *exists = 1; if(can_dial(pvt, opts, requestor)) { found = pvt; break; } } ast_mutex_unlock (&pvt->lock); } } } else if (((resource[0] == 'r') || (resource[0] == 'R')) && ((resource[1] >= '0') && (resource[1] <= '9'))) { errno = 0; group = (int) strtol (&resource[1], (char**) NULL, 10); if (errno != EINVAL) { /* Generate a list of all available devices */ j = ITEMS_OF (round_robin); c = 0; last_used = 0; AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); if (CONF_SHARED(pvt, group) == group) { round_robin[c] = pvt; if (pvt->group_last_used == 1) { pvt->group_last_used = 0; last_used = c; } c++; if (c == j) { ast_mutex_unlock (&pvt->lock); break; } } ast_mutex_unlock (&pvt->lock); } /* Search for a available device starting at the last used device */ for (i = 0, j = last_used + 1; i < c; i++, j++) { if (j == c) { j = 0; } pvt = round_robin[j]; *exists = 1; ast_mutex_lock (&pvt->lock); if (can_dial(pvt, opts, requestor)) { pvt->group_last_used = 1; found = pvt; break; } ast_mutex_unlock (&pvt->lock); } } } else if (((resource[0] == 'p') || (resource[0] == 'P')) && resource[1] == ':') { /* Generate a list of all available devices */ j = ITEMS_OF(round_robin); c = 0; last_used = 0; AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); if (!strcmp (pvt->provider_name, &resource[2])) { round_robin[c] = pvt; if (pvt->prov_last_used == 1) { pvt->prov_last_used = 0; last_used = c; } c++; if (c == j) { ast_mutex_unlock (&pvt->lock); break; } } ast_mutex_unlock (&pvt->lock); } /* Search for a available device starting at the last used device */ for (i = 0, j = last_used + 1; i < c; i++, j++) { if (j == c) { j = 0; } pvt = round_robin[j]; *exists = 1; ast_mutex_lock (&pvt->lock); if (can_dial(pvt, opts, requestor)) { pvt->prov_last_used = 1; found = pvt; break; } ast_mutex_unlock (&pvt->lock); } } else if (((resource[0] == 's') || (resource[0] == 'S')) && resource[1] == ':') { /* Generate a list of all available devices */ j = ITEMS_OF(round_robin); c = 0; last_used = 0; i = strlen (&resource[2]); AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); if (!strncmp (pvt->imsi, &resource[2], i)) { round_robin[c] = pvt; if (pvt->sim_last_used == 1) { pvt->sim_last_used = 0; last_used = c; } c++; if (c == j) { ast_mutex_unlock (&pvt->lock); break; } } ast_mutex_unlock (&pvt->lock); } /* Search for a available device starting at the last used device */ for (i = 0, j = last_used + 1; i < c; i++, j++) { if (j == c) { j = 0; } pvt = round_robin[j]; *exists = 1; ast_mutex_lock (&pvt->lock); if (can_dial(pvt, opts, requestor)) { pvt->sim_last_used = 1; found = pvt; break; } ast_mutex_unlock (&pvt->lock); } } else if (((resource[0] == 'i') || (resource[0] == 'I')) && resource[1] == ':') { AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); if (!strcmp(pvt->imei, &resource[2])) { *exists = 1; if(can_dial(pvt, opts, requestor)) { found = pvt; break; } } ast_mutex_unlock (&pvt->lock); } } else { AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock (&pvt->lock); if (!strcmp (PVT_ID(pvt), resource)) { *exists = 1; if(can_dial(pvt, opts, requestor)) { found = pvt; break; } } ast_mutex_unlock (&pvt->lock); } } AST_RWLIST_UNLOCK(&state->devices); return found; } #/* */ static const char * pvt_state_base(const struct pvt * pvt) { const char * state = NULL; // length is "AAAAAAAAAA" if(pvt->current_state == DEV_STATE_STOPPED && pvt->desired_state == DEV_STATE_STOPPED) state = "Stopped"; else if(!pvt->connected) state = "Not connected"; else if(!pvt->initialized) state = "Not initialized"; else if(!pvt->gsm_registered) state = "GSM not registered"; return state; } #/* */ EXPORT_DEF const char* pvt_str_state(const struct pvt* pvt) { const char * state = pvt_state_base(pvt); if(!state) { if(pvt->ring || PVT_STATE(pvt, chan_count[CALL_STATE_INCOMING])) state = "Ring"; else if(pvt->cwaiting || PVT_STATE(pvt, chan_count[CALL_STATE_WAITING])) state = "Waiting"; else if(pvt->dialing || (PVT_STATE(pvt, chan_count[CALL_STATE_INIT]) + PVT_STATE(pvt, chan_count[CALL_STATE_DIALING]) + PVT_STATE(pvt, chan_count[CALL_STATE_ALERTING])) > 0) state = "Dialing"; else if(PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE]) > 0) // state = "Active"; state = pvt_call_dir(pvt); else if(PVT_STATE(pvt, chan_count[CALL_STATE_ONHOLD]) > 0) state = "Held"; else if(pvt->outgoing_sms || pvt->incoming_sms_index != -1U) state = "SMS"; else state = "Free"; } return state; } #/* */ EXPORT_DEF struct ast_str* pvt_str_state_ex(const struct pvt* pvt) { struct ast_str* buf = ast_str_create (256); const char * state = pvt_state_base(pvt); if(state) ast_str_append (&buf, 0, "%s", state); else { if(pvt->ring || PVT_STATE(pvt, chan_count[CALL_STATE_INCOMING])) ast_str_append (&buf, 0, "Ring "); if(pvt->dialing || (PVT_STATE(pvt, chan_count[CALL_STATE_INIT]) + PVT_STATE(pvt, chan_count[CALL_STATE_DIALING]) + PVT_STATE(pvt, chan_count[CALL_STATE_ALERTING])) > 0) ast_str_append (&buf, 0, "Dialing "); if(pvt->cwaiting || PVT_STATE(pvt, chan_count[CALL_STATE_WAITING])) ast_str_append (&buf, 0, "Waiting "); if(PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE]) > 0) ast_str_append (&buf, 0, "Active %u ", PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE])); if(PVT_STATE(pvt, chan_count[CALL_STATE_ONHOLD]) > 0) ast_str_append (&buf, 0, "Held %u ", PVT_STATE(pvt, chan_count[CALL_STATE_ONHOLD])); if(pvt->incoming_sms_index != -1U) ast_str_append (&buf, 0, "Incoming SMS "); if(pvt->outgoing_sms) ast_str_append (&buf, 0, "Outgoing SMS"); if(ast_str_strlen(buf) == 0) { ast_str_append (&buf, 0, "%s", "Free"); } } if(pvt->desired_state != pvt->current_state) ast_str_append (&buf, 0, " %s", dev_state2str_msg(pvt->desired_state)); return buf; } #/* */ EXPORT_DEF const char* GSM_regstate2str(int gsm_reg_status) { static const char * const gsm_states[] = { "Not registered, not searching", "Registered, home network", "Not registered, but searching", "Registration denied", "Unknown", "Registered, roaming", }; return enum2str_def(gsm_reg_status, gsm_states, ITEMS_OF (gsm_states), "Unknown"); } #/* */ EXPORT_DEF const char* sys_mode2str(int sys_mode) { static const char * const sys_modes[] = { "No Service", "AMPS", "CDMA", "GSM/GPRS", "HDR", "WCDMA", "GPS", }; return enum2str_def(sys_mode, sys_modes, ITEMS_OF (sys_modes), "Unknown"); } #/* */ EXPORT_DEF const char * sys_submode2str(int sys_submode) { static const char * const sys_submodes[] = { "No service", "GSM", "GPRS", "EDGE", "WCDMA", "HSDPA", "HSUPA", "HSDPA and HSUPA", }; return enum2str_def(sys_submode, sys_submodes, ITEMS_OF (sys_submodes), "Unknown"); } #/* BUGFIX of https://code.google.com/p/asterisk-chan-dongle/issues/detail?id=118 */ EXPORT_DEF char* rssi2dBm(int rssi, char * buf, unsigned len) { #if 0 /* old code */ if(rssi <= 0) { snprintf(buf, len, "<= -125 dBm"); } else if(rssi <= 30) { snprintf(buf, len, "%d dBm", 31 * rssi / 50 - 125); } else if(rssi == 31) { snprintf(buf, len, ">= -75 dBm"); } else { snprintf(buf, len, "unknown"); } #else if(rssi <= 0) { snprintf(buf, len, "<= -113 dBm"); } else if(rssi <= 30) { snprintf(buf, len, "%d dBm", 2 * rssi - 113); } else if(rssi == 31) { snprintf(buf, len, ">= -51 dBm"); } else { snprintf(buf, len, "unknown or unmeasurable"); } #endif return buf; } /* Module */ #/* */ EXPORT_DEF void pvt_dsp_setup(struct pvt * pvt, const char * id, dc_dtmf_setting_t dtmf_new) { /* first remove dsp if off or changed */ if(dtmf_new != CONF_SHARED(pvt, dtmf)) { if(pvt->dsp) { ast_dsp_free(pvt->dsp); pvt->dsp = NULL; } } /* wake up and initialize dsp */ if(dtmf_new != DC_DTMF_SETTING_OFF) { pvt->dsp = ast_dsp_new(); if(pvt->dsp) { int digitmode = DSP_DIGITMODE_DTMF; if(dtmf_new == DC_DTMF_SETTING_RELAX) digitmode |= DSP_DIGITMODE_RELAXDTMF; ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DIGIT_DETECT); ast_dsp_set_digitmode(pvt->dsp, digitmode); } else { ast_log(LOG_ERROR, "[%s] Can't setup dsp for dtmf detection, ignored\n", id); } } pvt->real_dtmf = dtmf_new; } static struct pvt * pvt_create(const pvt_config_t * settings) { struct pvt * pvt = ast_calloc (1, sizeof (*pvt)); if(pvt) { ast_mutex_init (&pvt->lock); AST_LIST_HEAD_INIT_NOLOCK (&pvt->at_queue); AST_LIST_HEAD_INIT_NOLOCK (&pvt->chans); pvt->sys_chan.pvt = pvt; pvt->sys_chan.state = CALL_STATE_RELEASED; pvt->monitor_thread = AST_PTHREADT_NULL; pvt->audio_fd = -1; pvt->data_fd = -1; pvt->timeout = DATA_READ_TIMEOUT; pvt->gsm_reg_status = -1; pvt->incoming_sms_index = -1U; ast_copy_string (pvt->provider_name, "NONE", sizeof (pvt->provider_name)); ast_copy_string (pvt->subscriber_number, "Unknown", sizeof (pvt->subscriber_number)); pvt->has_subscriber_number = 0; pvt->desired_state = SCONFIG(settings, initstate); pvt_dsp_setup(pvt, UCONFIG(settings, id), SCONFIG(settings, dtmf)); /* and copy settings */ memcpy(&pvt->settings, settings, sizeof(pvt->settings)); return pvt; } else { ast_log (LOG_ERROR, "[%s] Skipping device: Error allocating memory\n", UCONFIG(settings, id)); } return NULL; } #/* */ static int pvt_time4restate(const struct pvt * pvt) { if(pvt->desired_state != pvt->current_state) { if(pvt->restart_time == RESTATE_TIME_NOW || (PVT_NO_CHANS(pvt) && !pvt->outgoing_sms && pvt->incoming_sms_index == -1U)) return 1; } return 0; } #/* */ EXPORT_DEF void pvt_try_restate(struct pvt * pvt) { if(pvt_time4restate(pvt)) { pvt->restart_time = RESTATE_TIME_NOW; discovery_restart(gpublic); } } #/* assume caller hold lock */ static int pvt_reconfigure(struct pvt * pvt, const pvt_config_t * settings, restate_time_t when) { int rv = 0; if(SCONFIG(settings, initstate) == DEV_STATE_REMOVED) { /* handle later, in one place */ pvt->must_remove = 1; } else { /* check what changes require starting or stopping */ if(pvt->desired_state != SCONFIG(settings, initstate)) { pvt->desired_state = SCONFIG(settings, initstate); rv = pvt_time4restate(pvt); pvt->restart_time = rv ? RESTATE_TIME_NOW : when; } /* check what config changes require restaring */ else if(strcmp(UCONFIG(settings, audio_tty), CONF_UNIQ(pvt, audio_tty)) || strcmp(UCONFIG(settings, data_tty), CONF_UNIQ(pvt, data_tty)) || strcmp(UCONFIG(settings, imei), CONF_UNIQ(pvt, imei)) || strcmp(UCONFIG(settings, imsi), CONF_UNIQ(pvt, imsi)) || SCONFIG(settings, u2diag) != CONF_SHARED(pvt, u2diag) || SCONFIG(settings, resetdongle) != CONF_SHARED(pvt, resetdongle) || SCONFIG(settings, callwaiting) != CONF_SHARED(pvt, callwaiting)) { /* TODO: schedule restart */ pvt->desired_state = DEV_STATE_RESTARTED; rv = pvt_time4restate(pvt); pvt->restart_time = rv ? RESTATE_TIME_NOW : when; } pvt_dsp_setup(pvt, UCONFIG(settings, id), SCONFIG(settings, dtmf)); /* and copy settings */ memcpy(&pvt->settings, settings, sizeof(pvt->settings)); } return rv; } #/* */ static int reload_config(public_state_t * state, int recofigure, restate_time_t when, unsigned * reload_immediality) { struct ast_config * cfg; const char * cat; struct ast_flags config_flags = { 0 }; struct dc_sconfig config_defaults; pvt_config_t settings; int err; struct pvt * pvt; unsigned reload_now = 0; if ((cfg = ast_config_load (CONFIG_FILE, config_flags)) == NULL) { return -1; } /* read global config */ dc_gconfig_fill(cfg, "general", &state->global_settings); /* read defaults */ dc_sconfig_fill_defaults(&config_defaults); dc_sconfig_fill(cfg, "defaults", &config_defaults); /* FIXME: deadlock avoid ? */ AST_RWLIST_RDLOCK(&state->devices); AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock(&pvt->lock); pvt->must_remove = 1; ast_mutex_unlock(&pvt->lock); } AST_RWLIST_UNLOCK(&state->devices); /* now load devices */ for (cat = ast_category_browse (cfg, NULL); cat; cat = ast_category_browse (cfg, cat)) { if (strcasecmp (cat, "general") && strcasecmp (cat, "defaults")) { err = dc_config_fill(cfg, cat, &config_defaults, &settings); if(!err) { pvt = find_device(UCONFIG(&settings, id)); if(pvt) { if(!recofigure) { ast_log (LOG_ERROR, "device %s already exists, duplicate in config file\n", cat); } else { pvt->must_remove = 0; reload_now += pvt_reconfigure(pvt, &settings, when); } ast_mutex_unlock(&pvt->lock); } else { /* new device */ if(SCONFIG(&settings, initstate) == DEV_STATE_REMOVED) { ast_log (LOG_NOTICE, "Skipping device %s as disabled\n", cat); } else { pvt = pvt_create(&settings); if(pvt) { /* FIXME: deadlock avoid ? */ AST_RWLIST_WRLOCK(&state->devices); AST_RWLIST_INSERT_TAIL(&state->devices, pvt, entry); AST_RWLIST_UNLOCK(&state->devices); reload_now++; ast_log (LOG_NOTICE, "[%s] Loaded device\n", PVT_ID(pvt)); } } } } } } ast_config_destroy (cfg); /* FIXME: deadlock avoid ? */ /* schedule removal of devices not listed in config file or disabled */ AST_RWLIST_RDLOCK(&state->devices); AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { ast_mutex_lock(&pvt->lock); if(pvt->must_remove) { pvt->desired_state = DEV_STATE_REMOVED; if(pvt_time4restate(pvt)) { pvt->restart_time = RESTATE_TIME_NOW; reload_now++; } else pvt->restart_time = when; } ast_mutex_unlock(&pvt->lock); } AST_RWLIST_UNLOCK (&state->devices); if(reload_immediality) *reload_immediality = reload_now; return 0; } #/* */ static void devices_destroy(public_state_t * state) { struct pvt * pvt; /* Destroy the device list */ AST_RWLIST_WRLOCK(&state->devices); while((pvt = AST_RWLIST_REMOVE_HEAD(&state->devices, entry))) { pvt_destroy(pvt); } AST_RWLIST_UNLOCK(&state->devices); } static int load_module() { int rv; gpublic = ast_calloc(1, sizeof(*gpublic)); if(gpublic) { pdiscovery_init(); rv = public_state_init(gpublic); if(rv != AST_MODULE_LOAD_SUCCESS) ast_free(gpublic); } else { ast_log (LOG_ERROR, "Unable to allocate global state structure\n"); rv = AST_MODULE_LOAD_DECLINE; } return rv; } #/* */ static int public_state_init(struct public_state * state) { int rv = AST_MODULE_LOAD_DECLINE; AST_RWLIST_HEAD_INIT(&state->devices); ast_mutex_init(&state->discovery_lock); state->discovery_thread = AST_PTHREADT_NULL; if(reload_config(state, 0, RESTATE_TIME_NOW, NULL) == 0) { rv = AST_MODULE_LOAD_FAILURE; if(discovery_restart(state) == 0) { /* set preferred capabilities */ #if ASTERISK_VERSION_NUM >= 130000 /* 13+ */ if (!(channel_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_FAILURE; } ast_format_cap_append(channel_tech.capabilities, ast_format_slin, 0); #elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */ ast_format_set(&chan_dongle_format, AST_FORMAT_SLINEAR, 0); # if ASTERISK_VERSION_NUM >= 120000 /* 12+ */ if (!(channel_tech.capabilities = ast_format_cap_alloc(0))) { return AST_MODULE_LOAD_FAILURE; } # else if (!(channel_tech.capabilities = ast_format_cap_alloc())) { return AST_MODULE_LOAD_FAILURE; } # endif ast_format_cap_add(channel_tech.capabilities, &chan_dongle_format); chan_dongle_format_cap = channel_tech.capabilities; #endif /* ^10-13 */ /* register our channel type */ if(ast_channel_register(&channel_tech) == 0) { if (smsdb_init() != 0) { ast_log(LOG_ERROR, "Unable to init smsdb\n"); } cli_register(); app_register(); manager_register(); return AST_MODULE_LOAD_SUCCESS; } else { #if ASTERISK_VERSION_NUM >= 130000 /* 13+ */ ao2_cleanup(channel_tech.capabilities); channel_tech.capabilities = NULL; #elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */ channel_tech.capabilities = ast_format_cap_destroy(channel_tech.capabilities); #endif /* ^10-13 */ ast_log (LOG_ERROR, "Unable to register channel class %s\n", channel_tech.type); } discovery_stop(state); } else { ast_log (LOG_ERROR, "Unable to create discovery thread\n"); } devices_destroy(state); } else { ast_log (LOG_ERROR, "Errors reading config file " CONFIG_FILE ", Not loading module\n"); } ast_mutex_destroy(&state->discovery_lock); AST_RWLIST_HEAD_DESTROY(&state->devices); return rv; } #/* */ static void public_state_fini(struct public_state * state) { /* First, take us out of the channel loop */ ast_channel_unregister (&channel_tech); #if ASTERISK_VERSION_NUM >= 130000 /* 13+ */ ao2_cleanup(channel_tech.capabilities); channel_tech.capabilities = NULL; #elif ASTERISK_VERSION_NUM >= 100000 /* 10-13 */ channel_tech.capabilities = ast_format_cap_destroy(channel_tech.capabilities); #endif /* ^10-13 */ /* Unregister the CLI & APP & MANAGER */ manager_unregister(); app_unregister(); cli_unregister(); discovery_stop(state); devices_destroy(state); ast_mutex_destroy(&state->discovery_lock); AST_RWLIST_HEAD_DESTROY(&state->devices); } static int unload_module() { public_state_fini(gpublic); pdiscovery_fini(); ast_free(gpublic); smsdb_atexit(); gpublic = NULL; return 0; } #/* */ EXPORT_DEF void pvt_reload(restate_time_t when) { unsigned dev_reload = 0; reload_config(gpublic, 1, when, &dev_reload); if(dev_reload > 0) discovery_restart(gpublic); } #/* */ static int reload_module() { pvt_reload(RESTATE_TIME_GRACEFULLY); return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, MODULE_DESCRIPTION, #if ASTERISK_VERSION_NUM >= 130000 /* 13+ */ .support_level = AST_MODULE_SUPPORT_EXTENDED, #endif /* ^13+ */ .load = load_module, .unload = unload_module, .reload = reload_module, #if ASTERISK_VERSION_NUM >= 130000 /* 13+ */ .load_pri = AST_MODPRI_CHANNEL_DRIVER, #endif /* ^13+ */ ); //AST_MODULE_INFO_STANDARD (ASTERISK_GPL_KEY, MODULE_DESCRIPTION); EXPORT_DEF struct ast_module* self_module(void) { return ast_module_info->self; }