/* * * Copyright (c) 2002, Smart Link Ltd. * Copyright (c) 2021, Aon plc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * 3. Neither the name of the Smart Link Ltd. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /* * * modem.c -- modem core module. * * Author: Sasha K (sashak@smlink.com) * * */ #include #include #include #include #include #include #include #include #include #define XMIT_SIZE 4096 #define MODEM_AUTHOR "Smart Link Ltd." #define MODEM_NAME "SmartLink Soft Modem" #define MODEM_VERSION "2.9.11" #define MODEM_DATE __DATE__" "__TIME__ /* event mask */ #define MDMEVENT_RING_CHECK 0x01 #define MDMEVENT_ESCAPE 0x02 /* debug prints */ #define MODEM_INFO(fmt,arg...) printf(fmt , ##arg) ; dprintf(fmt , ##arg) #define MODEM_DBG(fmt,arg...) dprintf("%s: " fmt , m->name , ##arg) #define MODEM_ERR(fmt,arg...) eprintf("%s: " fmt , m->name , ##arg) short _MODEM_DO_ANSWER = 0; /* external symbols */ extern int process_at_command(struct modem *m, char *buf); extern void *dp_runtime_create(struct modem *m); extern void dp_runtime_delete(void *runtime); extern void *dcr_create(); extern void dcr_delete(void *dcr); extern void dcr_process(void *dcr, void *buf, int len); #ifdef MODEM_CONFIG_RING_DETECTOR extern void *RD_create(struct modem *m, unsigned rate); extern void RD_delete(void *obj); extern int RD_process(void *obj, void *in, int count); extern void RD_ring_details(void *obj, long *freq, long *duration); #endif #ifdef MODEM_CONFIG_CID extern void *CID_create(struct modem *m, unsigned rate, unsigned cid_val); extern void CID_delete(void *cid); extern int CID_process(void *cid, void *in, int count); #endif #ifdef MODEM_CONFIG_VOICE extern void *VOICE_create(struct modem *m, unsigned srate); extern void VOICE_delete(void *obj); extern int VOICE_process(void *obj, void *in, void *out, int count); extern int VOICE_command(void *obj, enum VOICE_CMD cmd); #endif #ifdef MODEM_CONFIG_FAX extern void *FAX_create(struct modem *m, unsigned caller, unsigned srate); extern int FAX_process(void *obj, void *in, void *out, int count); extern void FAX_delete(void *obj); #endif /* local prototypes */ int modem_answer(struct modem *m); static int sregs_init(unsigned char sregs[]); static void do_modem_change_dp(struct modem *); static int modem_start(struct modem *); static int modem_stop (struct modem *); static struct dp_operations *get_dp_operations(enum DP_ID id); static int modem_get_chars(struct modem *m, char *buf, int n); static int modem_put_chars(struct modem *m, const char *buf, int n); static int modem_comp_get_chars(struct modem *m, char *buf, int n); static int modem_comp_put_chars(struct modem *m, const char *buf, int n); #ifdef MODEM_CONFIG_CID static int modem_cid_start(struct modem *, unsigned timeout); #endif #ifdef MODEM_CONFIG_FAX static int modem_fax_start(struct modem *m); #endif /* global config data */ const char *modem_default_country = NULL; /* data definitions */ const char modem_author[] = MODEM_AUTHOR; const char modem_name[] = MODEM_NAME; const char modem_version[]= MODEM_VERSION; const char modem_date[] = MODEM_DATE; /* * misc macros */ /* ring parameter */ #define RING_ON_MIN(m) MODEM_HZ*3/20 /* 0.15 sec */ #define RING_ON_MAX(m) MODEM_HZ*5/2 /* 2.5 sec */ #define RING_OFF_MIN(m) MODEM_HZ*3/2 /* 1.5 sec */ #define RING_OFF_MAX(m) MODEM_HZ*13/2 /* 6.5 sec */ #define RING_COUNT_MIN(m) 10 #define TOTAL_RINGS_COUNT(m) ((m)->sregs[SREG_RING_COUNTER]) #define ANSWER_AFTER_RINGS(m) ((m)->sregs[SREG_RINGS_TO_AUTO_ANSWER]) #define ESCAPE_CHAR(m) ((m)->sregs[SREG_ESCAPE_CHAR]) #define CR_CHAR(m) ((m)->sregs[SREG_CR_CHAR]) #define LF_CHAR(m) ((m)->sregs[SREG_LF_CHAR]) #define BS_CHAR(m) ((m)->sregs[SREG_BS_CHAR]) #define IS_ECHO(m) ((m)->sregs[SREG_ECHO]) #define IS_QUIET(m) ((m)->sregs[SREG_QUIET]) #define IS_VERBOSE(m) ((m)->sregs[SREG_VERBOSE]) #define IS_AUTOMODE(m) MODEM_AUTOMODE(m) #define MODEM_EC_ENABLE(m) ((m)->sregs[SREG_EC]) #define MODEM_EC_DETECTOR(m) ((m)->sregs[SREG_EC]) #define MODEM_COMP_ENABLE(m) ((m)->sregs[SREG_COMP]&&MODEM_EC_ENABLE(m)) #define ESCAPE_TIMEOUT(m) (MODEM_HZ/2) #define ANSWER_DELAY(m) ((m)->sregs[SREG_ANS_DELAY]) #define SPEAKER_CONTROL(m) ((m)->sregs[SREG_SPEAKER_CONTROL]) #define SPEAKER_VOLUME(m) ((m)->sregs[SREG_SPEAKER_VOLUME]) #define QC_SKIP_EC_DETECTION(m) ((m)->caller && (m)->dsp_info.qc_lapm) /* * dp operations drivers * */ struct dp_driver modem_dp_drivers[] = { {DP_CALLPROG,"CallProg"}, {DP_DUMMY,"Dummy"}, {DP_AUTOMODE,"Automode"}, {DP_V8,"V8"}, {DP_V17,"V17"}, {DP_V21,"V21","21"}, {DP_V22,"V22","22"}, {DP_V23,"V23","23"}, {DP_V22BIS,"V22bis","122"}, {DP_V32,"V32","32"}, {DP_V32BIS,"V32bis","132"}, {DP_V34,"V34","34"}, {DP_B103,"Bell103","103"}, {DP_B212,"Bell212","212"}, {DP_FAX,"VFax"}, {DP_K56,"K56Flex","56"}, {DP_V8BIS,"V8bis"}, {DP_V90,"V90","90"}, {DP_V92,"V92","92"}, {DP_SINUS,"Sinus"}, {} }; static struct dp_driver *get_dp_driver(enum DP_ID id) { struct dp_driver *p; for(p = modem_dp_drivers ; p->id > 0 ; p++) if (p->id == id) return p; return NULL; } static struct dp_operations *get_dp_operations(enum DP_ID id) { struct dp_driver *p; for(p = modem_dp_drivers ; p->id > 0 ; p++) if (p->id == id) return p->op; return NULL; } int modem_dp_register(enum DP_ID id, struct dp_operations *op) { struct dp_driver *p; int ret = -1; for(p = modem_dp_drivers ; p->id > 0 ; p++) { if (p->id == id && !p->op) { p->op = op; ret = 0; break; } } return ret; } void modem_dp_deregister(enum DP_ID id, struct dp_operations *op) { struct dp_driver *p; for(p = modem_dp_drivers ; p->id > 0 ; p++) if (p->id == id) p->op = NULL; } /* * state and status handling * */ enum MODEM_RESULT { RESULT_OK = 0, RESULT_CONNECT = 1, RESULT_RING = 2, RESULT_NOCARRIER = 3, RESULT_ERROR = 4, RESULT_UNUSED_5 = 5, RESULT_NODIALTONE = 6, RESULT_BUSY = 7, RESULT_NOANSWER = 8, }; static void modem_report_result(struct modem *m, enum MODEM_RESULT code) { #define MODEM_RESPONSE_TEXT(id) modem_responses[id].text #define MODEM_RESPONSE_CODE(id) modem_responses[id].code /* response codes : FIXME */ static const struct modem_response { enum MODEM_RESULT id; char *code; char *text; } modem_responses[] = { {RESULT_OK, "0", "OK"}, {RESULT_CONNECT, "1", "CONNECT"}, {RESULT_RING, "2", "RING"}, {RESULT_NOCARRIER, "3", "NO CARRIER"}, {RESULT_ERROR, "4", "ERROR"}, {RESULT_UNUSED_5, "" , ""}, {RESULT_NODIALTONE, "6", "NO DIALTONE"}, {RESULT_BUSY, "7", "BUSY"}, {RESULT_NOANSWER, "8", "NO ANSWER"}, }; static const char none_str[] = "NONE"; const char *msg; u8 msg_mask; MODEM_DBG("modem report result: %d (%s)\n",code,MODEM_RESPONSE_TEXT(code)); if (IS_QUIET(m)) return; if (IS_VERBOSE(m)) { if(code==RESULT_CONNECT) { msg_mask = modem_get_sreg(m,SREG_CONNNECT_MSG_FORMAT); if(msg_mask&1) { struct dp_driver *dp_drv; modem_put_chars(m,"Modulation: ",12); dp_drv = get_dp_driver(m->dp->id); msg = (dp_drv) ? dp_drv->name : none_str; modem_put_chars(m,msg,strlen(msg)); modem_put_chars(m,CRLF_CHARS(m),2); } if(msg_mask&2) { modem_put_chars(m,"Protocol: ",10); msg = m->cfg.ec ? "LAPM" : none_str; modem_put_chars(m,msg,strlen(msg)); modem_put_chars(m,CRLF_CHARS(m),2); } if((msg_mask&3) == 3) { modem_put_chars(m,"Compression: ",13); msg = (m->cfg.ec && m->cfg.comp)? "V42bis" : none_str; modem_put_chars(m,msg,strlen(msg)); modem_put_chars(m,CRLF_CHARS(m),2); } if(msg_mask&4 && m->tx_rate) { char rate_str[16]; modem_put_chars(m,"TxRate: ",8); sprintf(rate_str,"%d",m->tx_rate); modem_put_chars(m,rate_str,strlen(rate_str)); modem_put_chars(m,CRLF_CHARS(m),2); } } msg = MODEM_RESPONSE_TEXT(code); modem_put_chars(m,msg,strlen(msg)); if (code==RESULT_CONNECT && m->mode == MODEM_MODE_DATA && m->rx_rate && modem_get_sreg(m,SREG_X_CODE) != 0) { char rate_str[16]; sprintf(rate_str," %d",m->rx_rate); modem_put_chars(m,rate_str,strlen(rate_str)); } } else { msg = MODEM_RESPONSE_CODE(code); modem_put_chars(m,msg,strlen(msg)); } modem_put_chars(m,CRLF_CHARS(m),2); } /* TDB: state lifecycle description */ /* modem state constants */ #define STATE_MODEM_IDLE 0x1 #define STATE_DP_ESTAB 0x2 #define STATE_EC_ESTAB 0x4 #define STATE_MODEM_ONLINE 0x5 #define STATE_COMMAND_ONLINE 0x6 #define STATE_EC_DISC 0x7 #define STATE_DP_DISC 0x9 #define STATE_DISC 0x100 #define STATE_ESTAB STATE_DP_ESTAB #define IS_STATE_LINKED(stat) ((stat) > STATE_DP_ESTAB) #define IS_STATE_IDLE(stat) ((stat) == STATE_MODEM_IDLE) #define IS_STATE_CONNECTING(stat) ((stat) > STATE_MODEM_IDLE && (stat) < STATE_MODEM_ONLINE) #define IS_STATE_ONLINE(stat) ((stat) == STATE_MODEM_ONLINE || (stat) == STATE_COMMAND_ONLINE) #define IS_STATE_DISC(stat) ((stat) == STATE_EC_DISC || (stat) == STATE_DP_DISC) static int modem_set_state(struct modem *m, unsigned new_state) { MODEM_DBG("modem set state: %x --> %x...\n", m->state, new_state); if(m->state == new_state) return 0; switch(new_state) { case STATE_MODEM_IDLE: MODEM_DBG("new state: MODEM_IDLE\n"); m->command = 1; m->get_chars = 0; m->put_chars = 0; m->get_bits = 0; m->put_bits = 0; break; case STATE_DP_ESTAB: MODEM_DBG("new state: DP_ESTAB\n"); m->command = 0; m->get_chars = 0; m->put_chars = 0; m->get_bits = 0; m->put_bits = 0; break; case STATE_EC_ESTAB: MODEM_DBG("new state: EC_ESTAB\n"); m->command = 0; m->get_chars = 0; m->put_chars = 0; break; case STATE_MODEM_ONLINE: MODEM_DBG("new state: MODEM_ONLINE\n"); m->command = 0; m->put_chars = (m->cfg.ec && m->cfg.comp&0x1) ? modem_comp_put_chars : modem_put_chars; m->get_chars = (m->cfg.ec && m->cfg.comp&0x2) ? modem_comp_get_chars : modem_get_chars; m->modem_info |= TIOCM_CD; break; case STATE_COMMAND_ONLINE: MODEM_DBG("new state: COMMAND_ONLINE\n"); m->command = 1; m->get_chars = 0; m->put_chars = 0; break; case STATE_EC_DISC: MODEM_DBG("new state: EC_DISC\n"); m->get_chars = 0; m->put_chars = 0; break; case STATE_DP_DISC: MODEM_DBG("new state: DP_DISC\n"); m->get_chars = 0; m->put_chars = 0; m->get_bits = 0; m->put_bits = 0; break; default: MODEM_ERR("invalid state: %d\n", new_state); return -1; } m->state = new_state; return 0; } static void run_modem_stop(struct modem *m) { modem_stop(m); } /* FIXME */ #define schedule_modem_stop(m) { m->sample_timer_func = run_modem_stop; \ m->sample_timer = m->count + 48; } static void modem_hup(struct modem *m, unsigned local) { MODEM_DBG("modem_hup...%d\n",m->state); switch(m->state) { case STATE_MODEM_IDLE: return; case STATE_DP_ESTAB: if(local && m->dp && m->dp->op->hangup ) m->dp->op->hangup(m->dp); modem_set_state(m,STATE_DP_DISC); if (m->result_code == 0) m->result_code = RESULT_NOCARRIER; schedule_modem_stop(m); break; case STATE_EC_ESTAB: case STATE_MODEM_ONLINE: case STATE_COMMAND_ONLINE: if(local && m->cfg.ec) { modem_ec_stop(m); modem_set_state(m,STATE_EC_DISC); } else { if(local && m->dp && m->dp->op->hangup) m->dp->op->hangup(m->dp); modem_set_state(m,STATE_DP_DISC); } if (m->result_code == 0) m->result_code = RESULT_NOCARRIER; schedule_modem_stop(m); #if ALREADY_DONE if(m->modem_info&TIOCM_CD && m->tty && !m->termios->c_cflag&CLOCAL /* !C_CLOCAL(m->tty) */) { MODEM_DBG("tty_hangup...\n"); tty_hangup(m->tty); } #endif m->modem_info &= ~TIOCM_CD; break; case STATE_EC_DISC: if(local && m->dp && m->dp->op->hangup ) m->dp->op->hangup(m->dp); modem_set_state(m,STATE_DP_DISC); break; case STATE_DP_DISC: default: return; } } /* FIXME: find better place */ #define NONEC_DP(id) ((id) == DP_V23 || (id) == DP_B103 || (id) == DP_V21) void modem_update_status(struct modem *m, unsigned status) { MODEM_DBG("modem_update_status: %d\n", status); switch(status) { case STATUS_OK: break; case STATUS_DP_LINK: MODEM_DBG("--> DP LINK\n"); if(NONEC_DP(m->dp->id)) m->cfg.ec = 0; modem_set_state(m,STATE_EC_ESTAB); /* start packer */ if(m->cfg.ec && m->cfg.ec_detector && !QC_SKIP_EC_DETECTION(m) ) { modem_detector_start(m); m->get_bits = modem_detector_get_bits; m->put_bits = modem_detector_put_bits; } else { /* skip pack connect phase */ modem_update_status(m,STATUS_PACK_LINK); } break; case STATUS_PACK_LINK: MODEM_DBG("--> PACK LINK\n"); if(m->cfg.ec) { /* start EC */ modem_hdlc_start(m); m->get_bits = modem_hdlc_get_bits; m->put_bits = modem_hdlc_put_bits; modem_ec_start(m); } else { /* start async, pass EC connect phase */ modem_async_start(m); m->get_bits = modem_async_get_bits; m->put_bits = modem_async_put_bits; modem_update_status(m,STATUS_EC_LINK); } break; /* case STATUS_CONNECT: */ case STATUS_EC_LINK: MODEM_DBG("--> EC LINK\n"); modem_set_state(m,STATE_MODEM_ONLINE); modem_report_result(m,RESULT_CONNECT); // fixme if(SPEAKER_CONTROL(m) == 1) m->driver.ioctl(m,MDMCTL_SPEAKERVOL,0); break; case STATUS_EC_RELEASE: case STATUS_EC_ERROR: MODEM_DBG("--> EC UNLINK\n"); m->result_code = RESULT_NOCARRIER; modem_hup(m,(m->state == STATE_EC_DISC)); break; case STATUS_ERROR: case STATUS_DP_ERROR: case STATUS_NOCARRIER: case STATUS_NODIALTONE: case STATUS_BUSY: case STATUS_NOANSWER: default: MODEM_DBG("--> FINISH.\n"); if(status == STATUS_NODIALTONE) m->result_code = RESULT_NODIALTONE; else if(status == STATUS_BUSY) m->result_code = RESULT_BUSY; else if(status == STATUS_NOANSWER) m->result_code = RESULT_NOANSWER; else m->result_code = RESULT_NOCARRIER; modem_hup(m,IS_STATE_DISC(m->state)); break; } } void modem_update_config(struct modem *m, struct modem_config *cfg) { MODEM_DBG("modem update config...\n"); #if 1 #define PRINT_CONFIG(prm) MODEM_DBG(#prm " = %d (%d)\n",cfg->prm,m->cfg.prm) PRINT_CONFIG(ec); PRINT_CONFIG(ec_detector); PRINT_CONFIG(ec_tx_win_size); PRINT_CONFIG(ec_rx_win_size); PRINT_CONFIG(ec_tx_info_size); PRINT_CONFIG(ec_rx_info_size); PRINT_CONFIG(comp); PRINT_CONFIG(comp_dict_size); PRINT_CONFIG(comp_max_string); #endif // FIXME, FIXME, FIXME !!! #if 0 if(cfg->ec) ; /* TBD: now ec reconfig is handled internally in EC */ #endif if(cfg->comp && m->cfg.comp) { m->cfg.comp = cfg->comp; if(!modem_comp_config(m,cfg->comp_dict_size, cfg->comp_max_string)) { m->cfg.comp_dict_size = cfg->comp_dict_size; m->cfg.comp_max_string = cfg->comp_max_string; } else { /* config failed */ ; /* TBD: probably try to renegotiate */ } } else m->cfg.comp = 0; } /* * Interrupt handlers and services * */ /* get/put bits */ int modem_get_bits(struct modem *m, int nbits, u8 *buf, int n) { if(m->get_bits) n = m->get_bits(m,nbits,buf,n); else memset(buf,0xff,n); modem_debug_log_data(m,MODEM_DBG_TX_BITS,buf,n); /* process bit timer and packer */ if (m->bit_timer && (m->bit_timer -= nbits*n) <= 0) { m->bit_timer = 0; m->bit_timer_func(m); } // FIXME NOW: remove packer_process from modem to EC if(m->packer_process) m->packer_process(m,nbits*n); return n; } int modem_put_bits(struct modem *m, int nbits, u8 *buf, int n) { if(m->put_bits) n = m->put_bits(m,nbits,buf,n); modem_debug_log_data(m,MODEM_DBG_RX_BITS,buf,n); return n; } static unsigned dpstat2status(unsigned dpstat) { switch (dpstat) { case DPSTAT_OK: return STATUS_OK; case DPSTAT_CONNECT: return STATUS_DP_LINK; case DPSTAT_ERROR: return STATUS_DP_ERROR; case DPSTAT_NODIALTONE: return STATUS_NODIALTONE; case DPSTAT_BUSY: return STATUS_BUSY; case DPSTAT_NOANSWER: return STATUS_NOANSWER; case DPSTAT_CHANGEDP: return STATUS_OK; default: return STATUS_ERROR; } } static void modem_dp_process(struct modem *m,void *in,void *out,int count) { int ret; unsigned cnt; //MODEM_DBG("modem_process %d: %d...\n", m->count,count); while(count && m->dp) { cnt = count; if (cnt > m->frag) cnt = m->frag; //MODEM_DBG("%d: running dp %s...\n", m->count,dp->name); ret = m->dp->op->process(m->dp,in,out,cnt); switch(ret) { case DPSTAT_OK: break; case DPSTAT_CONNECT: if(!IS_STATE_LINKED(m->state) && !IS_STATE_DISC(m->state)) { modem_update_status(m,STATUS_DP_LINK); } break; case DPSTAT_CHANGEDP: do_modem_change_dp(m); break; default: /* errors */ modem_update_status(m,dpstat2status(ret)); m->process = NULL; break; } count -= cnt; out += cnt<format); in += cnt<format); } if(count) memset(out,0,count<format)); } void modem_process(struct modem *m,void *in,void *out,int count) { /* clean DC */ dcr_process(m->dcr,in,count); modem_debug_log_data(m,MODEM_DBG_RX_SAMPLES,in,count<format)); if(m->process) m->process(m,in,out,count); else /* mute output */ memset(out,0,count<format)); modem_debug_log_data(m,MODEM_DBG_TX_SAMPLES,out,count<format)); m->count+=count; if( m->sample_timer && m->count >= m->sample_timer ) { void (*f)(struct modem *) = m->sample_timer_func ; m->sample_timer = 0; m->sample_timer_func = NULL; if(f) f(m); } } /* * event processing * */ static void timer_mark_event (void *data) { struct modem *m = data; #ifdef DEBUG_TIMERS MODEM_DBG("timer_mark_event: %x | %x : now = %lu...\n", m->event, m->new_event, get_time()); #endif m->event |= m->new_event; m->new_event = 0; } static void schedule_event(struct modem *m, unsigned mask, unsigned long when) { m->new_event |= mask; m->event_timer.data = m; m->event_timer.func = timer_mark_event; m->event_timer.expires = get_time() + when; #ifdef DEBUG_TIMERS MODEM_DBG("schedule_event: %x | %x : now %lu + %lu = %u...\n", m->new_event, mask, get_time(), when, m->event_timer.expires); #endif timer_add(&m->event_timer); } void modem_event(struct modem *m) { unsigned event = m->event; m->event = 0; MODEM_DBG("modem_event: %x...\n", event); if(event&MDMEVENT_RING_CHECK) { if(m->ring_count == 0) { MODEM_DBG("ring cancel...\n"); TOTAL_RINGS_COUNT(m) = 0; } else if ( time_before(m->ring_last,m->ring_first+RING_ON_MIN(m)) || time_after(m->ring_last,m->ring_first+RING_ON_MAX(m)) || m->ring_count < RING_COUNT_MIN(m)) { MODEM_DBG("ring reject: count %d (%lu-%lu)\n", m->ring_count,m->ring_first,m->ring_last); TOTAL_RINGS_COUNT(m) = 0; m->ring_count = 0; } else { MODEM_DBG("ring valid.\n"); m->ring_count = 0; TOTAL_RINGS_COUNT(m)++; if(TOTAL_RINGS_COUNT(m) == 1) modem_put_chars(m,CRLF_CHARS(m),2); modem_report_result(m,RESULT_RING); if (ANSWER_AFTER_RINGS(m) && #ifdef MODEM_CONFIG_RING_DETECTOR (!m->started || m->rd_obj) && #else !m->started && #endif TOTAL_RINGS_COUNT(m) >= ANSWER_AFTER_RINGS(m)) { TOTAL_RINGS_COUNT(m) = 0; modem_answer(m); } else { schedule_event(m,MDMEVENT_RING_CHECK, RING_OFF_MAX(m) + 1); } } } if(event&MDMEVENT_ESCAPE) { MODEM_DBG("modem_escape (count %d)...\n", m->escape_count); if (m->state == STATE_MODEM_ONLINE && m->escape_count == 3) { modem_set_state(m, STATE_COMMAND_ONLINE); m->xmit.head = m->xmit.tail = m->xmit.count = 0; modem_put_chars(m,CRLF_CHARS(m),2); modem_report_result(m,RESULT_OK); m->command = 1; } m->escape_count = 0; } } void modem_ring(struct modem *m) { unsigned long now = get_time(); if (m->state != STATE_MODEM_IDLE) return; m->ring_count++; if(m->ring_count == 1) { MODEM_DBG("modem_ring...\n"); if(time_before(now,m->ring_last + RING_OFF_MIN(m))) { MODEM_DBG("bad ring: now %lu, last %lu.\n", now,m->ring_last); m->ring_count = 0; } else { m->ring_first = now; schedule_event(m,MDMEVENT_RING_CHECK, RING_ON_MAX(m) + 1); #ifdef MODEM_CONFIG_CID if (TOTAL_RINGS_COUNT(m) == 0 && m->cid_requested) modem_cid_start(m, RING_ON_MAX(m) + RING_OFF_MIN(m)); #endif } } m->ring_last = now; } void modem_error(struct modem *m) { MODEM_DBG("modem error...\n"); } /* command mode processing */ static void modem_at_process(void *data) { struct modem *m = (struct modem *)data; int echo = IS_ECHO(m); char lf = LF_CHAR(m), cr = CR_CHAR(m), bs = BS_CHAR(m); int ret; char ch; while(1) { if(modem_get_chars(m,&ch,1) <= 0) break; if (ch == '/' && m->at_count == 1 && toupper(m->at_line[0]) == 'A') { m->at_count = strlen(m->at_line); if(echo) modem_put_chars(m,m->at_line+1,m->at_count-1); ch = cr; } if (echo) modem_put_chars(m,&ch,1); if (ch == cr || ch == lf) { int i; char *p = m->at_cmd; m->at_line[m->at_count] = '\0'; for(i = 0 ; i < m->at_count ; i++) { if( m->at_line[i] == ' ' || m->at_line[i] == '\t' ) continue; *p++ = m->at_line[i]; } *p = '\0'; if (strlen(m->at_cmd) < 2 || toupper(m->at_cmd[0]) != 'A' || toupper(m->at_cmd[1]) != 'T' ) { memset(m->at_line,' ',m->at_count); if(echo) { modem_put_chars(m,&cr,1); modem_put_chars(m,m->at_line,m->at_count); modem_put_chars(m,&cr,1); } m->at_count = 0; continue; } if (echo) modem_put_chars(m,CRLF_CHARS(m),2); MODEM_DBG("run cmd: %s\n",m->at_cmd); ret = process_at_command(m, m->at_cmd); if (ret < 0) modem_report_result(m,RESULT_ERROR); else if (ret==0) modem_report_result(m,RESULT_OK); m->at_line[m->at_count] = '\0'; m->at_count = 0; echo = IS_ECHO(m); cr = CR_CHAR(m), lf = LF_CHAR(m), bs = BS_CHAR(m); } else if (ch == bs && m->at_count) { m->at_count--; if(echo) { ch = ' '; modem_put_chars(m,&ch,1); modem_put_chars(m,&bs,1); } } else if (m->at_count == sizeof(m->at_line) - 1) { if (echo) modem_put_chars(m,CRLF_CHARS(m),2); m->at_count = 0; m->at_line[1] = '\0'; modem_report_result(m,RESULT_ERROR); } else { m->at_line[m->at_count++] = ch; } } } /* * TTY procedures * */ /* get chars */ static int modem_get_chars(struct modem *m, char *buf, int n) { int ret = 0, cnt; while(n) { cnt = n; if (cnt > m->xmit.count) cnt = m->xmit.count; if (cnt > m->xmit.size - m->xmit.tail) cnt = m->xmit.size - m->xmit.tail; if (cnt <= 0) { break; } memcpy(buf, m->xmit.buf + m->xmit.tail, cnt); ret += cnt; n -= cnt; buf += cnt; m->xmit.count -= cnt; m->xmit.tail = (m->xmit.tail+cnt)%m->xmit.size; } //MODEM_DBG("modem_get_chars: %d...\n", n); return ret; } /* put chars */ static int modem_put_chars(struct modem *m, const char *buf, int n) { int ret = write(m->pty,buf,n); if(ret < 0) { /* perror("write"); */ ret = 0; } #if 0 if(ret>0) { //MODEM_DBG("modem_comp_put_chars: %d...\n",i); modem_debug_log_data(m, MODEM_DBG_RX_CHARS,buf,i); } #endif return ret; } /* compressor get/put chars */ /* fixme: unify interfaces: modem tx -> comp -> ec -> hdlc --> modem tx -> ec -> hdlc --> modem tx -> async --> modem tx --> raw output */ static int modem_comp_get_chars(struct modem *m, char *buf, int n) { int ret = 0, cnt; char ch; while(ret < n) { cnt = modem_get_chars(m,&ch,1); if(cnt <= 0) break; cnt = modem_comp_encode(m,ch,buf+ret,n-ret); if(cnt < 0) { break; } ret += cnt; } if(ret < n) { cnt = modem_comp_flush_encoder(m,buf+ret,n-ret); ret += cnt; } if(ret > 0) { //MODEM_DBG("modem_comp_get_chars: %d(%d)...\n",ret,count); modem_debug_log_data(m,MODEM_DBG_TX_DATA,buf,ret); } return ret; } static int comp_send_output(struct modem *m) { int cnt; if(m->comp.count > 0) { cnt = modem_put_chars(m,m->comp.buf+m->comp.head,m->comp.count); m->comp.count -= cnt; m->comp.head += cnt; if(m->comp.count > 0) return 0; else m->comp.head = 0; } return 1; } static int modem_comp_put_chars(struct modem *m, const char *buf, int n) { int i=0, cnt; if(!comp_send_output(m)) return 0; while(icomp.buf,sizeof(m->comp.buf)); if(cnt < 0) { MODEM_DBG("decoder error. %d(%d)\n",i,n); modem_update_status(m,STATUS_ERROR); break; } m->comp.count += cnt; if(!comp_send_output(m)) break; } if(i==n) { cnt = modem_comp_flush_decoder(m,m->comp.buf,sizeof(m->comp.buf)); m->comp.count += cnt; comp_send_output(m); } if(i>0) { //MODEM_DBG("modem_comp_put_chars: %d...\n",i); modem_debug_log_data(m,MODEM_DBG_RX_DATA,buf,i); } return i; } /* * internal procedures * */ #define IS_FAST_DP(id) (id == DP_V34 || id == DP_V90 || id == DP_V92) static void do_modem_change_dp (struct modem *m) { struct dp_operations *op; struct dp *old; old = m->dp; if (m->mode == MODEM_MODE_FAX) { modem_fax_start(m); m->dp = NULL; } else { struct dp *dp = NULL; int dp_id,id; dp_id = m->dp_requested; m->dp_requested = 0; id = dp_id; if(dp_id == 0) { dp_id = MODEM_DP(m); id = IS_FAST_DP(dp_id) ? DP_V8 : dp_id; } MODEM_DBG("%ld: change dp: --> %d...\n", m->count, id); op = get_dp_operations(id); if (op && op->create) dp = op->create(m,dp_id, m->caller,m->srate, m->frag,op); if (!dp) { MODEM_ERR("change dp -> %d error.\n", id); modem_hup(m,1); return; } m->dp = dp; m->process = modem_dp_process; } if ( old ) { old->op->delete(old); } } static int modem_set_hook(struct modem *m, unsigned hook_state) { int ret; MODEM_DBG("modem set hook: %d --> %d...\n", m->hook, hook_state); if ( m->hook == hook_state ) return 0; ret = m->driver.ioctl(m, MDMCTL_HOOKSTATE,hook_state); if (!ret) m->hook = hook_state; return ret; } static void modem_setup_config(struct modem *m) { m->cfg.ec = MODEM_EC_ENABLE(m); m->cfg.ec_detector = MODEM_EC_DETECTOR(m); m->cfg.ec_tx_win_size = LAPM_MAX_WIN_SIZE; m->cfg.ec_rx_win_size = LAPM_MAX_WIN_SIZE; m->cfg.ec_tx_info_size = LAPM_MAX_INFO_SIZE; m->cfg.ec_rx_info_size = LAPM_MAX_INFO_SIZE; if(m->cfg.ec && MODEM_COMP_ENABLE(m)) m->cfg.comp = 0x3; /* bi-directional v42bis */ else m->cfg.comp = 0; m->cfg.comp_dict_size = COMP_MAX_CODEWORDS; m->cfg.comp_max_string = COMP_MAX_STRING; /* setup dsp data */ m->dsp_info.qc_lapm = m->cfg.ec && m->cfg.ec_detector ; } static int do_modem_start(struct modem *m) { int ret; ret = m->driver.ioctl(m, MDMCTL_SPEED, m->srate); ret = m->driver.ioctl(m, MDMCTL_SETFRAG, m->frag); m->count = 0; ret = m->driver.start(m); m->started = !ret; return ret; } static int modem_start (struct modem *m) { int ret; MODEM_DBG("modem_start..\n"); if(m->started && modem_stop(m)) return -1; m->result_code = 0; modem_setup_config(m); modem_set_state(m, STATE_ESTAB); if(SPEAKER_CONTROL(m) == 1) m->driver.ioctl(m,MDMCTL_SPEAKERVOL,SPEAKER_VOLUME(m)); ret = modem_set_hook(m, MODEM_HOOK_OFF); if (ret) goto error; m->xmit.head = m->xmit.tail = m->xmit.count = 0; m->command = 0; if( !(m->dcr= dcr_create()) || !(m->dp_runtime = dp_runtime_create(m))) { ret = -1; goto error; } if ( m->cfg.ec && m->cfg.comp && (ret = modem_comp_init(m)) ) goto error; if ( m->cfg.ec && (ret = modem_ec_init(m))) goto error; /* clear rings and all events */ TOTAL_RINGS_COUNT(m) = 0; m->ring_count = 0; m->event = m->new_event = 0; timer_del(&m->event_timer); ret = do_modem_start(m); if (!ret) return 0; error: MODEM_ERR("modem start = %d: cannot start device.\n",ret); m->result_code = RESULT_NOCARRIER; modem_stop(m); return ret; } static int modem_stop (struct modem *m) { int ret = 0; MODEM_DBG("modem_stop..\n"); m->process = NULL; if(m->started) { ret = m->driver.stop(m); m->started = ret; if ( ret ) { MODEM_ERR("modem stop = %d: cannot stop device.\n",ret); } } modem_set_hook(m, MODEM_HOOK_ON); if(SPEAKER_CONTROL(m) == 1) m->driver.ioctl(m,MDMCTL_SPEAKERVOL,0); m->caller = 0; m->command = 1; // FIXME: If ec,comp were allocated handled inside _exit() - improve? modem_ec_exit(m); modem_comp_exit(m); if (m->dp) { struct dp *dp; dp = m->dp; m->dp = 0; if(dp) dp->op->delete(dp); } if (m->dp_runtime) { dp_runtime_delete(m->dp_runtime); m->dp_runtime = NULL; } if (m->dcr) { dcr_delete(m->dcr); m->dcr = NULL; } #ifdef MODEM_CONFIG_RING_DETECTOR if(m->rd_obj) { RD_delete(m->rd_obj); m->rd_obj = NULL; } #endif #ifdef MODEM_CONFIG_CID if(m->cid) { CID_delete(m->cid); m->cid = NULL; } #endif #ifdef MODEM_CONFIG_VOICE if(m->voice_obj) { VOICE_delete(m->voice_obj); m->voice_obj = NULL; } #endif #ifdef MODEM_CONFIG_FAX if(m->fax_obj) { FAX_delete(m->fax_obj); m->fax_obj = NULL; } #endif modem_set_state(m, STATE_MODEM_IDLE); if (m->result_code) { modem_report_result(m, m->result_code); m->result_code = 0; } m->count = 0; return m->started; } /* * Caller ID * */ #ifdef MODEM_CONFIG_CID static void modem_cid_stop(struct modem *m) { MODEM_DBG("modem_cid_stop...\n"); m->sample_timer = 0; m->sample_timer_func = NULL; #ifdef MODEM_CONFIG_RING_DETECTOR if(!m->rd_obj) { m->process = NULL; modem_stop(m); } else { CID_delete(m->cid); m->cid = NULL; } #else m->process = NULL; modem_stop(m); #endif /* continue with answer if need */ if (ANSWER_AFTER_RINGS(m) && TOTAL_RINGS_COUNT(m) >= ANSWER_AFTER_RINGS(m)) { TOTAL_RINGS_COUNT(m) = 0; modem_answer(m); } } static void modem_cid_process(struct modem *m, void *in, void *out, int count) { int status; //MODEM_DBG("modem_cid_process: %d...\n", count); memset(out,0,count*2); status = CID_process(m->cid, in, count); if(status) { if(status < 0) MODEM_DBG("CID failed.\n"); modem_cid_stop(m); } } static int modem_cid_start(struct modem *m, unsigned timeout) { MODEM_DBG("modem_cid_start: timeout = %d...\n", timeout); #ifdef MODEM_CONFIG_RING_DETECTOR if(m->started && !m->rd_obj) #else if(m->started) #endif return -1; m->cid = CID_create(m, m->srate, m->cid_requested); if(!m->cid) return -1; m->sample_timer = m->count + m->srate*timeout/MODEM_HZ ; m->sample_timer_func = modem_cid_stop; #ifdef MODEM_CONFIG_RING_DETECTOR if(m->started && m->rd_obj) return 0; #endif m->process = modem_cid_process; modem_set_hook(m, MODEM_HOOK_SNOOPING); return do_modem_start(m); } #endif /* MODEM_CONFIG_CID */ /* * Ring detector (internal) * */ #ifdef MODEM_CONFIG_RING_DETECTOR static void modem_ring_detector_process(struct modem *m, void *in, void *out, int count) { int ret; memset(out, 0, count*2); ret = RD_process(m->rd_obj, in, count); if (ret) { long freq, duration; RD_ring_details(m->rd_obj, &freq, &duration); MODEM_DBG("ring details: freq = %ld, duration = %ld\n", freq, duration); if(freq == 0) { MODEM_DBG("report ring start...\n"); modem_ring(m); } else if (freq > 0) { MODEM_DBG("report ring end...\n"); /* ring finishing */ m->event |= MDMEVENT_RING_CHECK; if (m->ring_count <= 1) m->ring_count = duration * freq / 1000 ; m->ring_last = get_time(); } else MODEM_ERR("RD returns %ld freq. (duration %ld)\n", freq, duration); } #ifdef MODEM_CONFIG_CID if(m->cid) modem_cid_process(m, in, out, count); #endif } int modem_ring_detector_start(struct modem *m) { MODEM_DBG("modem_ring_detector_start...\n"); if(m->rd_obj) { MODEM_ERR("modem_ring_detector_start: rd_obj already exists!\n"); return -1; } m->rd_obj = RD_create(m, m->srate); m->process = modem_ring_detector_process; modem_set_hook(m, MODEM_HOOK_SNOOPING); return do_modem_start(m); } #endif /* MODEM_CONFIG_RING_DETECTOR */ /* * Voice modem * */ #ifdef MODEM_CONFIG_VOICE extern void modem_voice_stop(struct modem *m); static void modem_voice_process(struct modem *m, void *in, void *out, int count) { int status; //MODEM_DBG("modem_voice_process: %d...\n", count); status = VOICE_process(m->voice_obj, in, out, count); if(status) { MODEM_DBG("modem_voice_process: status %d\n", status); switch(status) { case VOICE_STATUS_OK: modem_report_result(m, RESULT_OK); m->command = 1; break; case VOICE_STATUS_CONNECT: modem_report_result(m, RESULT_CONNECT); m->command = 0; break; case VOICE_STATUS_ERROR: modem_report_result(m, RESULT_ERROR); m->command = 1; break; default: break; } if(status < 0) { MODEM_DBG("VOICE failed.\n"); modem_voice_stop(m); } } } int modem_voice_command(struct modem *m, enum VOICE_CMD cmd) { MODEM_DBG("modem_voice_command: %u...\n", cmd); switch(cmd) { case VOICE_CMD_BEEP: case VOICE_CMD_DTMF: case VOICE_CMD_STATE_RX: case VOICE_CMD_STATE_TX: if(!m->voice_obj) return -1; m->command = 0; VOICE_command(m->voice_obj, cmd); return 1; default: return -1; } return 0; } int modem_voice_start(struct modem *m) { MODEM_DBG("modem_voice_start...\n"); if(m->voice_obj) return 0; if(m->started) { #ifdef MODEM_CONFIG_RING_DETECTOR if(m->rd_obj) modem_stop(m); else #endif return -1; } m->voice_obj = VOICE_create(m, m->srate); if(!m->voice_obj) return -1; m->sample_timer = 0 /* m->srate*timeout/MODEM_HZ */ ; m->sample_timer_func = NULL /* modem_voice_stop */ ; m->process = modem_voice_process; modem_set_hook(m, MODEM_HOOK_OFF); return do_modem_start(m); } void modem_voice_stop(struct modem *m) { MODEM_DBG("modem_voice_stop...\n"); m->process = NULL; m->sample_timer = 0; m->sample_timer_func = NULL; if(m->voice_obj) { VOICE_delete(m->voice_obj); m->voice_obj = NULL; } modem_stop(m); modem_set_hook(m, MODEM_HOOK_ON); } int modem_voice_set_device(struct modem *m, unsigned device) { MODEM_DBG("modem_voice_set_device: dev = %u...\n", device); if (device == VOICE_DEVICE_NONE) { if(m->voice_obj) modem_voice_stop(m); return 0; } else if (device == VOICE_DEVICE_LINE) { if(m->voice_obj) return 0; else if(modem_voice_start(m)) return -1; return 1; } return -1; } int modem_voice_init(struct modem *m) { m->voice_info.comp_method = 1; m->voice_info.sample_rate = 8000; m->voice_info.rx_gain = 128; m->voice_info.tx_gain = 128; m->voice_info.dtmf_symbol = 0; m->voice_info.tone1_freq = 933; m->voice_info.tone2_freq = 0; m->voice_info.tone_duration = 150; m->voice_info.inactivity_timer = 0; m->voice_info.silence_detect_sensitivity = 128; m->voice_info.silence_detect_period = 50; return 0; } #endif /* MODEM_CONFIG_VOICE */ /* * Fax modem * */ #ifdef MODEM_CONFIG_FAX static void modem_fax_process(struct modem *m, void *in, void *out, int count) { int ret; //MODEM_DBG("modem_fax_process: %d...\n", count); ret = FAX_process(m->fax_obj, in, out, count); if(ret) { MODEM_DBG("fax_process: %d\n", ret); switch(ret) { case FAX_STATUS_OK: modem_set_state(m, STATE_COMMAND_ONLINE); modem_report_result(m, RESULT_OK); m->command = 1; break; case FAX_STATUS_CONNECT: modem_set_state(m,STATE_MODEM_ONLINE); modem_report_result(m, RESULT_CONNECT); m->command = 0; break; case FAX_STATUS_NOCARRIER: modem_set_state(m, STATE_COMMAND_ONLINE); modem_report_result(m, RESULT_NOCARRIER); m->command = 1; break; case FAX_STATUS_ERROR: modem_update_status(m, STATUS_ERROR); break; default: break; } if(ret < 0) { MODEM_DBG("FAX failed.\n"); modem_stop(m); } } } static int modem_fax_start(struct modem *m) { MODEM_DBG("modem_fax_start...\n"); m->fax_obj = FAX_create(m, m->caller, m->srate); if(!m->fax_obj) return -1; m->sample_timer = 0; m->sample_timer_func = NULL; m->process = modem_fax_process; return 0; } #endif /* MODEM_CONFIG_FAX */ /* * Commands * */ int modem_send_to_tty(struct modem *m, const char *buf, int n) { return modem_put_chars(m, buf, n); } int modem_recv_from_tty(struct modem *m, char *buf, int n) { return modem_get_chars(m, buf, n); } int modem_answer(struct modem *m) { _MODEM_DO_ANSWER = 1; MODEM_DBG("modem answer...\n"); if ( m->dp ) { MODEM_ERR("dp %d is already exists.\n", m->dp->id); return -1; } if(m->started) modem_stop(m); m->dp_requested = 0; m->automode_requested = 0; /* MODEM_AUTOMODE(m) */ m->sample_timer = ANSWER_DELAY(m) ? ANSWER_DELAY(m)*m->srate : m->srate/100; m->sample_timer_func = do_modem_change_dp; return modem_start(m); } static int modem_dial_start(struct modem *m) { struct dp_operations *op; struct dp *dp = NULL; MODEM_DBG("modem_dial_start...\n"); if(m->dp) { return -1; } if (m->started) modem_stop(m); m->caller = 1; op = get_dp_operations(DP_CALLPROG); char save = m->dial_string[0]; // hide the dial string so no DTMF is generated m->dial_string[0] = 0; if (op && op->create) dp = op->create(m,DP_CALLPROG, m->caller,m->srate, m->frag,op); m->dial_string[0] = save; if (!dp) { MODEM_ERR("cannot create CALLPROG.\n"); return -1; } m->dp = dp; m->process = modem_dp_process; return 0; } int modem_dial(struct modem *m) { _MODEM_DO_ANSWER = 0; int ret; MODEM_DBG("modem dial: %s...\n", m->dial_string); m->dp_requested = 0; m->automode_requested = 0; ret = modem_dial_start(m); if(ret) return -1; return modem_start(m); } int modem_hook(struct modem *m, unsigned hook_state) { MODEM_DBG("modem hook...\n"); if ( m->hook == hook_state ) return 0; if (!IS_STATE_IDLE(m->state)) modem_hup(m,1); return modem_set_hook(m,hook_state); } int modem_online(struct modem *m) { MODEM_DBG("modem online...\n"); if (m->state != STATE_COMMAND_ONLINE) return -1; m->command = 0; modem_set_state(m, STATE_MODEM_ONLINE); modem_report_result(m,RESULT_CONNECT); // fixme return 0; } void modem_update_speaker(struct modem *m) { //MODEM_DBG("modem update speaker...\n"); m->driver.ioctl(m,MDMCTL_SPEAKERVOL, SPEAKER_CONTROL(m) == 2 ? SPEAKER_VOLUME(m):0); } int modem_get_sreg(struct modem *m, unsigned int num) { if (num >= sizeof(m->sregs)) return -1; return m->sregs[num]; } int modem_set_sreg(struct modem *m, unsigned int num, int val) { if (num >= sizeof(m->sregs)) return -1; m->sregs[num] = val; return 0; } /* homolog set initialization */ int modem_homolog_init(struct modem *m, int id, const char *name) { const struct homolog_set *set; for(set=homolog_set;set->name;set++) { if(set->id == id || (name && *name && !strcmp(name,set->name))) { m->homolog = set; modem_set_sreg(m,SREG_DIAL_PAUSE_TIME, set->params->DialPauseTime); modem_set_sreg(m,SREG_FLASH_TIMER, set->params->HookFlashTime); return 0; } } return -1; } int modem_set_mode(struct modem *m, enum MODEM_MODE mode) { MODEM_DBG("modem set mode: -> %d...\n", mode); if (m->mode == mode) return 0; #ifdef MODEM_CONFIG_VOICE if (m->mode == MODEM_MODE_VOICE && m->voice_obj) { modem_voice_stop(m); } #endif m->mode = mode; return 0; } int modem_reset(struct modem *m) { MODEM_DBG("modem reset...\n"); if(m->state != STATE_MODEM_IDLE) modem_hup(m,1); else if(m->started) modem_stop(m); else if(m->hook) modem_set_hook(m,MODEM_HOOK_ON); modem_set_state(m, STATE_MODEM_IDLE); m->command = 1; m->min_rate = MODEM_MIN_RATE; m->max_rate = MODEM_MAX_RATE; sregs_init(m->sregs); modem_homolog_init(m,m->homolog->id,NULL); modem_set_mode(m, MODEM_MODE_DATA); return 0; } /* * Init functions * */ /* set default init values */ static int sregs_init(unsigned char sregs[]) { sregs[SREG_ESCAPE_CHAR] = '+' ; /* escape char */ sregs[SREG_CR_CHAR] = '\r'; /* cr char */ sregs[SREG_LF_CHAR] = '\n'; /* lf char */ sregs[SREG_BS_CHAR] = '\b'; /* bs char */ sregs[SREG_DIAL_TONE_WAIT_TIME] = 2; /* seconds */ sregs[SREG_WAIT_CARRIER_AFTER_DIAL] = 60; /* seconds */ sregs[SREG_DIAL_PAUSE_TIME] = 2; /* seconds */ sregs[SREG_CARRIER_DETECT_RESPONSE_TIME] = 6; /* 0.1 sec */ sregs[SREG_CARRIER_LOSS_DISCONNECT_TIME] = 7; /* 0.1 sec */ sregs[SREG_DTMF_DURATION] = 100; /* ms */ sregs[SREG_ESCAPE_PROMPT_DELAY] = 50; /* ms */ sregs[SREG_FLASH_TIMER] = 20; /* 10ms */ sregs[SREG_ECHO] = 1; /* yes */ sregs[SREG_QUIET] = 0; /* no */ sregs[SREG_VERBOSE] = 1; /* yes */ sregs[SREG_TONE_OR_PULSE] = 1; /* tone */ sregs[SREG_X_CODE] = 4; sregs[SREG_SPEAKER_CONTROL] = 1; /* yes */ sregs[SREG_SPEAKER_VOLUME] = 3; /* max */ sregs[SREG_AUTOMODE] = 1; /* yes */ sregs[SREG_DP] = DP_V92; sregs[SREG_ANS_DELAY] = 2; /* seconds */ sregs[SREG_LINE_QUALITY_CONTROL] = 0; sregs[SREG_CD] = 0; sregs[SREG_FLOW_CONTROL] = 0; sregs[SREG_CONNNECT_MSG_FORMAT] = 0; sregs[SREG_CONNNECT_MSG_SPEED_SRC] = 0; /* new sregs */ sregs[SREG_EC] = 1; sregs[SREG_COMP] = 0x3; return 0; } /* ---------------------------------------------------------------- */ void modem_hangup(struct modem *m) { modem_hup(m,1); } void modem_update_termios(struct modem *m, struct termios *tios) { MODEM_DBG("update termios...\n"); if( cfgetispeed(tios) == B0 || cfgetospeed(tios) == B0 ) { MODEM_DBG("modem_update_termios: hangup.\n"); if(m->state!=STATE_MODEM_IDLE) modem_hup(m,1); // TBD: drop DTR? } else m->modem_info |= TIOCM_DTR; m->termios = *tios; } /* * * modem_write() * */ static int modem_check_escape(struct modem *m, const char *buf, int count) { unsigned long now = get_time(); int i; if(count + m->escape_count > 3 ) goto noescape_out; for( i = 0 ; i < count ; i++) { if(buf[i] != ESCAPE_CHAR(m)) goto noescape_out; } if(m->escape_count == 0) { if(time_before(now, m->last_esc_check + ESCAPE_TIMEOUT(m))) goto noescape_out; } else if (time_after(now, m->last_esc_check + ESCAPE_TIMEOUT(m))) goto noescape_out; m->escape_count += count; if(m->escape_count == 3) schedule_event(m,MDMEVENT_ESCAPE,ESCAPE_TIMEOUT(m)); m->last_esc_check = now; return m->escape_count; noescape_out: m->escape_count = 0; m->last_esc_check = now; return 0; } int modem_write(struct modem *m, const char *buffer, int count) { const char *buf; int cnt, ret = 0; if(IS_STATE_CONNECTING(m->state)) { MODEM_DBG("modem_tty_write: hangup...\n"); modem_hup(m,1); return count; } if(m->state == STATE_MODEM_ONLINE) modem_check_escape(m,buffer,count); while(count) { cnt = count; if(cnt > m->xmit.size - m->xmit.count) cnt = m->xmit.size - m->xmit.count; if(cnt > m->xmit.size - m->xmit.head) cnt = m->xmit.size - m->xmit.head; if(cnt <= 0) { MODEM_DBG("modem_write: overflow!\n"); break; } buf = buffer; memcpy(m->xmit.buf + m->xmit.head, buf, cnt); m->xmit.count += cnt; m->xmit.head = (m->xmit.head + cnt)%m->xmit.size; ret += cnt; buffer += cnt; count -= cnt; } if(m->command) modem_at_process(m); return ret; } void modem_print_version() { MODEM_INFO("%s: version %s %s\n", modem_name,modem_version,modem_date); } struct modem *modem_create(struct modem_driver *drv, const char *name) { struct modem *m; modem_print_version(); m = malloc(sizeof(*m)); if (!m) return NULL; memset(m,0,sizeof(*m)); m->name = name; m->driver = *drv; m->modem_info = 0; m->state = STATE_MODEM_IDLE; m->command= 1; m->hook = MODEM_HOOK_ON; m->caller = 0; m->min_rate = MODEM_MIN_RATE; m->max_rate = MODEM_MAX_RATE; m->modem_info |= (TIOCM_DSR|TIOCM_CTS); sregs_init(m->sregs); if(modem_homolog_init(m, MODEM_DEFAULT_COUNTRY_CODE, NULL)) { MODEM_ERR("bad default country code `%x'!\n", MODEM_DEFAULT_COUNTRY_CODE); free(m); return NULL; } if(modem_default_country && modem_homolog_init(m, -1, modem_default_country)) { MODEM_INFO("bad country name `%s', using default by code!\n", modem_default_country); } m->ring_last = get_time(); timer_init(&m->event_timer); /* setup config */ modem_setup_config(m); /* packer initializations */ // fixme: TBD /* dp initialization */ m->format = MODEM_FORMAT; m->srate = MODEM_RATE; m->frag = MODEM_FRAG; /* in samples */ m->dp = NULL; MODEM_DBG("startup modem...\n"); m->xmit.buf = malloc(XMIT_SIZE); if ( !m->xmit.buf ) { free(m); return NULL; } m->xmit.size = XMIT_SIZE; m->xmit.head = m->xmit.tail = m->xmit.count = 0; m->modem_info |= TIOCM_DTR|TIOCM_RTS; // TODO: update speed,DTR according to termios #ifdef MODEM_CONFIG_VOICE modem_voice_init(m); #endif return m; } void modem_delete(struct modem *m) { MODEM_DBG("modem_delete...\n"); if(m->started) { MODEM_DBG("shutdown modem...\n"); if(m->state != STATE_MODEM_IDLE) m->result_code = RESULT_NOCARRIER; modem_stop(m); } m->xmit.size = 0; m->xmit.count = m->xmit.tail = m->xmit.head = 0; free(m->xmit.buf); m->xmit.buf = 0; timer_del(&m->event_timer); free(m); }