1974 lines
47 KiB
C
1974 lines
47 KiB
C
|
|
||
|
/*
|
||
|
*
|
||
|
* 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 <unistd.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
#include <termios.h>
|
||
|
#include <sys/ioctl.h>
|
||
|
|
||
|
#include <modem.h>
|
||
|
#include <modem_debug.h>
|
||
|
|
||
|
|
||
|
#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)
|
||
|
|
||
|
/* 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<<MFMT_SHIFT(m->format);
|
||
|
in += cnt<<MFMT_SHIFT(m->format);
|
||
|
}
|
||
|
if(count)
|
||
|
memset(out,0,count<<MFMT_SHIFT(m->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<<MFMT_SHIFT(m->format));
|
||
|
if(m->process)
|
||
|
m->process(m,in,out,count);
|
||
|
else /* mute output */
|
||
|
memset(out,0,count<<MFMT_SHIFT(m->format));
|
||
|
modem_debug_log_data(m,MODEM_DBG_TX_SAMPLES,out,count<<MFMT_SHIFT(m->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(i<n) {
|
||
|
cnt = modem_comp_decode(m,buf[i++],m->comp.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_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)
|
||
|
{
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|