
/*
 *
 *    Copyright (c) 2002, Smart Link Ltd.
 *    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_ec.c  --  modem error corrector (lapm like) implementation.
 *
 *    Author: Sasha K (sashak@smlink.com)
 *
 *
 */

#include <modem.h>
#include <modem_debug.h>

//#define EC_DEBUG 1

/* LAPM default parameters */
#define LAPM_DFLT_WIN_SIZE   15  /* default win size */
#define LAPM_DFLT_INFO_SIZE 128  /* default info size */

#define LAPM_MAX_RTX          5  /* max num of retransmissions (N400) */

/* timeouts */
// fixme: speed is parameter
#define _TMP_SPEED 28800
// fixme: what is timeout vals should be (2sec?)???
#define T401_TIMEOUT        _TMP_SPEED*2  /* noacks timeout (in ???) */
#define T403_TIMEOUT        _TMP_SPEED*2  /* no frames timeout */

/* LAPM definitions */

/* frame types */
#define FRAME_I            0x00
#define FRAME_S            0x01
#define FRAME_U            0x03

/* supervisory headers */
#define FRAME_S_RR	   0x01  /* cr */
#define FRAME_S_RNR	   0x05  /* cr */
#define FRAME_S_REJ	   0x09  /* cr */
#define FRAME_S_SREJ	   0x0d  /* cr */
/* unnumbered headers */
#define FRAME_U_SABME	   0x6f  /* c  */
#define FRAME_U_DM	   0x0f  /*  r */
#define FRAME_U_UI	   0x03  /* cr */
#define FRAME_U_DISC	   0x43  /* c  */
#define FRAME_U_UA	   0x63  /*  r */
#define FRAME_U_FRMR	   0x87  /*  r */
#define FRAME_U_XID	   0xaf  /* cr */
#define FRAME_U_TEST	   0xe3  /* c  */

/* useful macros */
#define FRAME_ADDR(f) ((f)->buf[0])
#define FRAME_CTRL(f) ((f)->buf[1])
#define FRAME_TYPE(f) ((f)->buf[1]&0x1 ? ((f)->buf[1]&0x3) : FRAME_I)
#define RX_IS_COMMAND(f) ((f)->buf[0] == l->rsp_addr)
#define TX_IS_COMMAND(f) ((f)->buf[0] == l->cmd_addr)
#define FRAME_I_PF(f) ((f)->buf[2]&0x1)
#define FRAME_S_PF(f) ((f)->buf[2]&0x1)
#define FRAME_U_PF(f) ((f)->buf[1]&0x10)
#define FRAME_NS(f)   ((f)->buf[1]>>1)
#define FRAME_NR(f)   ((f)->buf[2]>>1)

/* tx unnumbered */
#define TX_SABME(l) tx_unnum(l,l->cmd_addr,FRAME_U_SABME|0x10,0,0)
#define TX_DISC(l)  tx_unnum(l,l->cmd_addr,FRAME_U_DISC|0x10,0,0)
#define TX_UA(l,pf_mask) tx_unnum(l,l->rsp_addr,FRAME_U_UA|pf_mask,0,0)
#define TX_DM(l,pf_mask) tx_unnum(l,l->rsp_addr,FRAME_U_DM|pf_mask,0,0)
/* tx supervisory */
#define TX_RR(l,addr,pf_mask)  tx_super(l,addr,FRAME_S_RR,pf_mask)
#define TX_RNR(l,addr,pf_mask) tx_super(l,addr,FRAME_S_RNR,pf_mask)
#define TX_REJ(l,addr,pf_mask) tx_super(l,addr,FRAME_S_REJ,pf_mask)

/* timers */
#define T401_START(l) 	{ EC_DBG("T401 start.\n");\
                          (l)->rtx_count = 0; \
			  (l)->modem->bit_timer = T401_TIMEOUT; \
			  (l)->modem->bit_timer_func = t401_timeout; }
#define T401_STOP(l) 	{ EC_DBG("T401 stop.\n"); \
                          (l)->modem->bit_timer = 0; (l)->rtx_count = 0; }
#if T403_IMPLEMENTED
#define TIMER_START(l)  { EC_DBG("T403 stop, T401 start.\n"); \
                          (l)->rtx_count = 0; \
                          (l)->modem->bit_timer = T401_TIMEOUT; \
                          (l)->modem->bit_timer_func = t401_timeout; }
#define TIMER_STOP(l)   { EC_DBG("T401 stop, T403 start.\n"); \
                          (l)->rtx_count = 0; \
                          (l)->modem->bit_timer = T403_TIMEOUT; \
                          (l)->modem->bit_timer_func = t403_timeout; }
#else /* !T403 */
#define TIMER_START(l) T401_START(l)
#define TIMER_STOP(l)  T401_STOP(l)
#endif /* !T403 */

/* xid sub-fields definitions */
#define FI_GENERAL		0x82
#define GI_PARAM_NEGOTIATION	0x80
#define GI_PRIVATE_NEGOTIATION	0xf0
#define GI_USER_DATA		0xff
/* param negotiation */
#define PI_HDLC_OPTFUNCS	0x03
#define PI_TX_INFO_MAXSIZE	0x05
#define PI_RX_INFO_MAXSIZE	0x06
#define PI_TX_WINDOW_SIZE	0x07
#define PI_RX_WINDOW_SIZE	0x08
/* private param negotiation */
#define PI_PARAMETER_SET_ID	0x00
#define PI_V42BIS_REQUEST	0x01
#define PI_V42BIS_CW_NUMBER	0x02
#define PI_V42BIS_MAX_STRING	0x03
/* user data */
/* fixme: ??? */


/* type definitions */
enum LAPM_STATES { LAPM_IDLE, LAPM_ESTAB, LAPM_DATA, LAPM_DISC };

/* prototypes */
static int lapm_connect(struct lapm_state *l);
static int lapm_disconnect(struct lapm_state *l);
static void reset(struct lapm_state *l);

/*
 * service functions
 *
 */

/* le/be value parsing */
extern inline u32 lapm_get_le_val(u8 *buf, int len)
{
	u32 val = 0;
	while(len--) {
		val <<= 8;
		val |= buf[len];
	}
	return val;
}

extern inline u32 lapm_get_be_val(u8 *buf, int len)
{
	u32 val = 0;
	while(len--) {
		val <<= 8;
		val |= *buf++;
	}
	return val;
}

/* debug stuffs */
#define EC_ERR(fmt,arg...) eprintf("%s ec err: " fmt , \
                                   l->modem->name , ##arg)

#ifdef EC_DEBUG
#define EC_DBG(fmt,arg...) dprintf("%s ec: " fmt , \
                                   l->modem->name , ##arg)
#define EC_DBG1(fmt...) // EC_DBG(fmt)

static char print_buf[(LAPM_MAX_INFO_SIZE+3)*2];

/* frame debug prints */
static const char *get_frame_name(frame_t *f)
{
	if (!(f->buf[1]&0x1))
		return "INFO";
	switch(f->buf[1]&0xef) {
	case FRAME_S_RR:    return "RR";
	case FRAME_S_RNR:   return "RNR";
	case FRAME_S_REJ:   return "REJ";
	case FRAME_S_SREJ:  return "SREJ";
	case FRAME_U_SABME: return "SABME";
	case FRAME_U_DM:    return "DM";
	case FRAME_U_UI:    return "UI";
	case FRAME_U_DISC:  return "DISC";
	case FRAME_U_UA:    return "UA";
	case FRAME_U_FRMR:  return "FRMR";
	case FRAME_U_XID:   return "XID";
	case FRAME_U_TEST:  return "TEST";
	default:
		break;
	}
	return "invalid frame";
}

static void print_frame(struct lapm_state *l, char *title, int cmd, frame_t *f)
{
	int i;
	i = sprintf(print_buf,"(va/vs/vr %d/%d/%d) %s: %s %s",
		    l->va,l->vs,l->vr,
		    title,get_frame_name(f),cmd?"cmd":"rsp");
	switch (FRAME_TYPE(f)) {
	case FRAME_I:
		EC_DBG("%s[%d] cnt=%d, (ns/nr %d/%d)...\n",print_buf,
			 FRAME_I_PF(f),f->count-3,FRAME_NS(f),FRAME_NR(f));
		break;
	case FRAME_S:
		EC_DBG("%s[%d], (nr %d)...\n",print_buf,
			 FRAME_S_PF(f), FRAME_NR(f));
		break;
	case FRAME_U:
		EC_DBG("%s[%d]...\n",print_buf, FRAME_U_PF(f));
		break;
	default:
		EC_DBG("%s: unknown frame.\n", print_buf);
		break;
	}
}

#define LAPM_PRINT_FRAME(str,cmd,f) print_frame(l,str,cmd,f)

/* XID debug prints */
static int print_xid_params(char *str_buf, u8 *buf, int len)
{
	u8 pid, plen;
	u32 pval;
	int i = 0;
	while (len > 0) {
		pid  = buf[0];
		plen = buf[1];
		buf += 2;
		len -= 2 + plen;
		if (len < 0)
			break;
		switch (pid) {
		case PI_HDLC_OPTFUNCS:
			i += sprintf(str_buf + i,"HDLC_OPTFUNC = 0x%x, ",
				     lapm_get_le_val(buf,plen));
			break;
		case PI_TX_INFO_MAXSIZE:
			pval = lapm_get_be_val(buf,plen);
			i += sprintf(str_buf + i,"TX_INFOSIZE = %u, ",pval>>3);
			break;
		case PI_RX_INFO_MAXSIZE:
			pval = lapm_get_be_val(buf,plen);
			i += sprintf(str_buf + i,"RX_INFOSIZE = %u, ",pval>>3);
			break;
		case PI_TX_WINDOW_SIZE:
			i += sprintf(str_buf + i,"TX_WINSIZE = %u, ",
				     lapm_get_be_val(buf,plen));
			break;
		case PI_RX_WINDOW_SIZE:
			i += sprintf(str_buf + i,"RX_WINSIZE = %u, ",
				     lapm_get_be_val(buf,plen));
			break;
		default:
			i += sprintf(str_buf + i,"??, ");
			break;
		}
		buf += plen;
	}
	i += sprintf(str_buf+i,"\n");
	return i;
}

static int print_xid_private_params(char *str_buf, u8 *buf, int len)
{
	u8 pid, plen;
	int i = 0;
	while (len > 0) {
		pid  = buf[0];
		plen = buf[1];
		buf += 2;
		len -= 2 + plen;
		if (len < 0)
			break;
		switch (pid) {
		case PI_PARAMETER_SET_ID:
			i += sprintf(str_buf + i,"PARAM_SETID = 0x%x, ",
				     lapm_get_be_val(buf,plen));
			break;
		case PI_V42BIS_REQUEST:
			i += sprintf(str_buf + i,"V42BIS_REQUEST = 0x%x, ",
				     lapm_get_be_val(buf,plen));
			break;
		case PI_V42BIS_CW_NUMBER:
			i += sprintf(str_buf + i,"V42BIS_CWNUM = %d, ",
				     lapm_get_be_val(buf,plen));
			break;
		case PI_V42BIS_MAX_STRING:
			i += sprintf(str_buf + i,"V42BIS_MAXSTR = %d, ",
				     lapm_get_be_val(buf,plen));
			break;
		default:
			i += sprintf(str_buf + i,"??, ");
			break;
		}
		buf += plen;
	}
	i += sprintf(str_buf+i,"\n");
	return i;
}

static int print_xid(struct lapm_state *l,char *str,frame_t *f, int len)
{
	u8 *buf = f->buf + 2;
	int i = 0;

	i = sprintf(print_buf,"XID: FID %02x;\n",*buf);

	buf++; len--;
	while(len > 0) {
		u8  gid = buf[0];
		u16 glen = buf[1];
		glen = glen << 8 | buf[2];
		buf += 3;
		len -= 3 + glen;
		if (len < 0)
			break;
		switch (gid) {
		case GI_PARAM_NEGOTIATION:
			i += sprintf(print_buf+i,"GI_PARAMS(%d): ",glen);
			i += print_xid_params(print_buf+i,buf,glen);
			break;
		case GI_PRIVATE_NEGOTIATION:
			i += sprintf(print_buf+i,"GI_PRIVATE_PARAMS(%d): ",glen);
			i += print_xid_private_params(print_buf+i,buf,glen);
			break;
		default:
			i += sprintf(print_buf+i,"unknown GI = 0x%x (%d) ??\n",
					gid,glen);
			break;
		}
		buf += glen;
	}
	EC_DBG("%s : %s\n", str, print_buf);
	return 0;
}

#define LAPM_PRINT_XID(str,f,len) print_xid(l,str,f,len);

#else /* !LAPM_DEBUG */

#define EC_DBG(fmt...)
#define EC_DBG1(fmt...)
#define LAPM_PRINT_FRAME(str,cmd,f)
#define LAPM_PRINT_XID(str,f,len)

#endif /* !LAPM_DEBUG */



/*
 * service functions
 *
 */

/* get frame from ctrl list */
static inline frame_t *get_ctrl_frame(struct lapm_state *l)
{
	frame_t *f;
	if ( l->ctrl_list->next == l->tx_ctrl ) {
		return 0;
	}
	f = l->ctrl_list;
	l->ctrl_list = l->ctrl_list->next;
	l->ctrl_count--; // debug only
	return f;
}



/* U transmission */
static int tx_unnum(struct lapm_state *l, u8 addr, u8 ctrl, u8 *info, int len)
{
	frame_t *f = get_ctrl_frame(l);
	if (!f) {
		EC_ERR("tx_unnum: cannot alloc frame.\n");
		return -1;
	}
	f->buf[0] = addr;
	f->buf[1] = ctrl;
	if (info && len)
		memcpy(f->buf+2,info,len);
	f->size = 2 + len;
	return 0;
}

/* S transmission */
static int tx_super(struct lapm_state *l, u8 addr, u8 ctrl, u8 pf_mask)
{
	frame_t *f = get_ctrl_frame(l);
	if (!f) {
		EC_ERR("tx_super: cannot alloc frame.\n");
		return -1;
	}
	f->buf[0] = addr;
	f->buf[1] = ctrl;
	f->buf[2] = (l->vr << 1)|pf_mask;
	f->size = 3;
	return 0;
}


/* parse xid */
#define SET_PARAM(param,value,default) { \
	if (((value)<(default) && (param)>=(default)) || \
            ((value)>=(default) && (param)<(default))) \
                (param) = (default); \
	else if (((value)<(default) && (param)<(value)) || \
                 ((value)>=(default) && (param)>(value))) \
                (param) = (value); }

static int parse_xid_params(struct lapm_state *l, u8 *buf, int len)
{
	u32 pval;
	u8 pid, plen;
	while (len > 0) {
		pid  = buf[0];
		plen = buf[1];
		buf += 2;
		len -= 2 + plen;
		if (len < 0)
			break;
		switch (pid) {
		case PI_HDLC_OPTFUNCS:
			pval = lapm_get_le_val(buf,plen);
			break;
		case PI_TX_INFO_MAXSIZE:
			pval = lapm_get_be_val(buf,plen);
			pval >>= 3;
			SET_PARAM(l->tx_info_size,pval,LAPM_DFLT_INFO_SIZE);
			break;
		case PI_RX_INFO_MAXSIZE:
			pval = lapm_get_be_val(buf,plen);
			pval >>= 3;
			SET_PARAM(l->rx_info_size,pval,LAPM_DFLT_INFO_SIZE);
			break;
		case PI_TX_WINDOW_SIZE:
			pval = lapm_get_be_val(buf,plen);
			SET_PARAM(l->tx_win_size,pval,LAPM_DFLT_WIN_SIZE);
			break;
		case PI_RX_WINDOW_SIZE:
			pval = lapm_get_be_val(buf,plen);
			SET_PARAM(l->rx_win_size,pval,LAPM_DFLT_WIN_SIZE);
			break;
		default:
			break;
		}
		buf += plen;
	}
	return 0;
}


static int parse_xid_private_params(struct lapm_state *l, struct modem_config *cfg, u8 *buf, int len)
{
	u8 pid, plen;
	while (len > 0) {
		pid  = buf[0];
		plen = buf[1];
		buf += 2;
		len -= 2 + plen;
		if (len < 0)
			break;
		switch (pid) {
		case PI_PARAMETER_SET_ID:
			//pval = lapm_get_be_val(buf,plen);
			break;
		case PI_V42BIS_REQUEST:
			cfg->comp = lapm_get_le_val(buf,plen);
			break;
		case PI_V42BIS_CW_NUMBER:
			cfg->comp_dict_size = lapm_get_be_val(buf,plen);
			break;
		case PI_V42BIS_MAX_STRING:
			cfg->comp_max_string = lapm_get_be_val(buf,plen);
			break;
		default:
			break;
		}
		buf += plen;
	}
	return 0;
}

#define DEFAULT_MODEM_EC_CONFIG {1,1,15,15,128,128,0,512,6}

static int rx_xid(struct lapm_state *l, frame_t *f)
{
	struct modem_config cfg = DEFAULT_MODEM_EC_CONFIG;
	u8 *buf = f->buf;
	int len = f->count;

	/* skip addr and ctrl */
	buf += 2; len -= 2;

	LAPM_PRINT_XID("rx_xid",f,len);
	if (*buf != FI_GENERAL)
		return -1;
	buf++; len--;
	while(len > 0) {
		u8  gid = buf[0];
		u16 glen = buf[1];
		glen = glen << 8 | buf[2];
		buf += 3;
		len -= 3 + glen;
		if (len < 0)
			break;
		switch (gid) {
		case GI_PARAM_NEGOTIATION:
			parse_xid_params(l,buf,glen);
			break;
		case GI_PRIVATE_NEGOTIATION:
			parse_xid_private_params(l,&cfg,buf,glen);
			break;
		default:
			break;
		}
		buf += glen;
	}
	cfg.ec_tx_win_size  = l->tx_win_size;
	cfg.ec_rx_win_size  = l->rx_win_size;
	cfg.ec_tx_info_size = l->tx_info_size;
	cfg.ec_rx_info_size = l->rx_info_size;
	modem_update_config(l->modem,&cfg);
	return 0;
}

static int tx_xid(struct lapm_state *l, u8 addr)
{
	u8 *buf;
	int size = 2 + 1 + (3+19) + (3+15);
	int len = 0;
	u32 pval;
	int glen;
	frame_t *f = get_ctrl_frame(l);
	if (!f) {
		EC_ERR("tx_xid: cannot alloc frame.\n");
		return -1;
	}

	buf = f->buf;

	*buf++ = addr; /* addr */
	*buf++ = FRAME_U_XID;   /* ctrl */
	*buf++ = FI_GENERAL;  /* fid */
	len += 3;
	/* param negotiation group */
	*buf++ = GI_PARAM_NEGOTIATION; /* group id */
	glen = 19;                     /* group length */  // v42vmi: 19
	*buf++ = (glen>>8)&0xff;
	*buf++ = glen&0xff;
	len += 3;
	*buf++ = PI_HDLC_OPTFUNCS;
	*buf++ = 3; /* p length */ // v42vmi : 3
	*buf++ = 0x8a; /* p value */
	*buf++ = 0x89;
	*buf++ = 0x00; 
	//*buf++ = 0x00;
	*buf++ = PI_TX_INFO_MAXSIZE;
	pval = l->tx_info_size<<3;
	*buf++ = 2; /* p length */
	*buf++ = (pval>>8)&0xff; /* p value */
	*buf++ = (pval&0xff);
	*buf++ = PI_RX_INFO_MAXSIZE;
	pval = l->rx_info_size<<3;
	*buf++ = 2; /* p length */
	*buf++ = (pval>>8)&0xff; /* p value */
	*buf++ = (pval&0xff);
	*buf++ = PI_TX_WINDOW_SIZE;
	*buf++ = 1; /* p length */
	*buf++ = l->tx_win_size; /* p value */
	*buf++ = PI_RX_WINDOW_SIZE;
	*buf++ = 1; /* p length */
	*buf++ = l->rx_win_size; /* p value */
	len += glen;

#if 1 /* v42bis-like compressor */
	if(l->modem->cfg.comp) {
		/* private param negotiation group */
		*buf++ = GI_PRIVATE_NEGOTIATION; /* group id */
		glen = 15;                       /* group length (msb first) */
		*buf++ = (glen>>8)&0xff;
		*buf++ = glen&0xff;
		len += 3;
		*buf++ = PI_PARAMETER_SET_ID;
		*buf++ = 3;    /* p length */
		*buf++ = 0x56;/*old 0x26 */ /* p value */ // v42vmi: 0x56
		*buf++ = 0x34;
		*buf++ = 0x32;
		*buf++ = PI_V42BIS_REQUEST;
		*buf++ = 1; /* p length */
		*buf++ = l->modem->cfg.comp; /* 3 - compression in both direction */
		*buf++ = PI_V42BIS_CW_NUMBER;
		*buf++ = 2; /* p length */
		pval = l->modem->cfg.comp_dict_size;
		*buf++ = (pval>>8)&0xff; /* p value */
		*buf++ = (pval&0xff);
		*buf++ = PI_V42BIS_MAX_STRING;
		*buf++ = 1; /* p length */
		*buf++ = l->modem->cfg.comp_max_string;
		len += glen;
	}
#endif

	/* user data */
	/* ... */
	if (len > size) {
		EC_ERR("tx_xid: result length(%d)  > size (%d).\n", len, size);
		return -1;
	}
	f->size = len;
	LAPM_PRINT_XID("tx_xid",f,len);
	return 0;
}


/* timeout procedures */

static void t401_timeout(struct modem *m)
{
	struct lapm_state *l = &m->ec.lapm;
	EC_DBG("t401_timeout: %d...\n",l->rtx_count);
	if (l->rtx_count > LAPM_MAX_RTX ) {
		EC_DBG("t401: max retransmission count was reached.\n");
		l->rtx_count = 0;
		// TBD
		switch(l->state) {
		case LAPM_ESTAB:
		case LAPM_DISC:
			l->state = LAPM_IDLE;
			modem_update_status(l->modem,STATUS_EC_RELEASE);
			break;
		case LAPM_DATA:
			// TBD: disconnect
			EC_DBG("t401: max data rtx was reached: disconnect\n");
			lapm_disconnect(l);
			break;
		}
		return ;
	}
	l->rtx_count++;
	if (l->config) {
		tx_xid(l,l->cmd_addr);
	}
	else {
		switch(l->state) {
		case LAPM_ESTAB:
			TX_SABME(l);
			break;
		case LAPM_DISC:
			TX_DISC(l);
			break;
		case LAPM_DATA:
			if (l->busy)
				TX_RNR(l,l->cmd_addr,1);
			else
				TX_RR(l,l->cmd_addr,1);
			break;
		}
	}
	m->bit_timer      = T401_TIMEOUT;
	m->bit_timer_func = t401_timeout;
}

#if T403_IMPLEMENTED
static void t403_timeout(struct lapm_state *l)
{
	EC_DBG("t403_timeout...\n");
	if (l->state != LAPM_DATA) {
		EC_ERR("t403: no in data state.\n");
		return;
	}
	if (l->busy)
		TX_RNR(l,l->cmd_addr,1);
	else
		TX_RR(l,l->cmd_addr,1);
	T401_START(l);
	l->rtx_count = 1;
}
#endif


/* transmit info */
static int tx_info(struct lapm_state *l)
{
	frame_t *f;
	int n = 0;
	if (l->peer_busy ||
	    ((l->vs - l->va)&0x7f) >= l->tx_win_size) {
		return 0;
	}
	if (l->tx_info == l->info_list) { /* empty tx */
		if (l->info_list->next == l->tx_info ||
		    l->info_list->next == l->sent_info) {
			EC_ERR("tx_info: cannot alloc frame.\n");
			return 0;
		}
		f = l->info_list;
		if(!l->modem->get_chars)
			return 0;
		n = l->modem->get_chars(l->modem,f->buf+3,l->tx_info_size);
		if (n < 0) { /* error */
			EC_ERR("tx_info: get chars error.\n");
			modem_update_status(l->modem,STATUS_EC_ERROR);
			return 0;
		}
		if ( n == 0) { /* no data */
			return 0;
		}
			
		f->size = n + 3;
		l->info_list = l->info_list->next;
		l->info_count--; // debug
		l->tx_count++;   // debug
		LAPM_PRINT_FRAME("tx_info",1,f);
	}

	/* start T401 */
	//if (!l->timer)
	//	TIMER_START(l);
	return 1;
}

/* reject all noacked frames (>va) */
static int reject_info(struct lapm_state *l)
{
	u8 n = (l->vs-l->va)&0x7f;
	if (l->state != LAPM_DATA) /* ignore retransmission */
		return 0;
	EC_DBG("(va/vs/vr %d/%d/%d) reject info: reject %d frames...\n",
		 l->va,l->vs,l->vr,n);
	l->vs = l->va;
	l->tx_info = l->sent_info;
	l->tx_count += l->sent_count; // debug
	l->sent_count = 0;            // debug
	return n;
}

/* ack info */
static int ack_info(struct lapm_state *l, u8 nr)
{
	int n = 0;
	//EC_DBG1("ack info: nr %d...\n", nr);
	/* check for valid nr ( va <= nr <= vs && vs-va<= k ) */
	// fixme: optimize this
	if (!( ((nr-l->va)&0x7f) >= 0 &&
	       ((l->vs-nr)&0x7f) >= 0 &&
	       ((l->vs-l->va)&0x7f) <= l->tx_win_size) ) {
		EC_ERR("nr sequince error. disconnect!\n");
		// TBD notify  CF
		lapm_disconnect(l);
		return -1;
	}
	while(l->va != nr) {
		if (l->sent_info == l->tx_info) { /* debug only ? */
			EC_ERR("ack info: VERY BAD: empty sent list: va %d, vs %d\n",
				 l->va,l->vs);
			break;
		}
		l->sent_info = l->sent_info->next;
		l->sent_count--; // debug
		l->info_count++; // debug
		l->va = (l->va+1)&0x7f;
		n++;
	}
	/* stop T401 , start T403 */
#if 1
	if(n > 0 && !l->rtx_count) {
#else
	if(n > 0 && !l->rtx_count && !l->peer_busy) {
#endif
		TIMER_STOP(l);
		if ((l->vs-l->va)&0x7f)
			TIMER_START(l); // exp.8.4.8
	}
	return n;
}


/* push rest data (when busy) */
static void push_rest_data(struct modem *m, int bits)
{
	struct lapm_state *l = &m->ec.lapm;
	if (!l->state == LAPM_DATA)
		return;
	if (l->rx_count && l->modem->put_chars) {
		int ret = l->modem->put_chars(l->modem,
					      l->rx_buf+l->rx_head,
					      l->rx_count);
		if (ret > 0) {
			l->rx_head += ret;
			l->rx_count -= ret;
			if (!l->rx_count) {
				l->rx_head = 0;
				/* reset busy state */
				l->busy = 0;
				TX_RR(l,l->cmd_addr,0);
				l->modem->packer_process = NULL;
			}
		}
	}
}

/* info frame process */
static int rx_info(struct lapm_state *l, frame_t *f)
{
	int ret = 0, n;
	//LAPM_PRINT_FRAME("rx_info",1,f);

	/* ack I frames: nr -1 */
	n = ack_info(l,FRAME_NR(f));
	/* busy */
	if (l->busy) {
		if(FRAME_I_PF(f)) /* 8.4.7 */
			TX_RNR(l,l->rsp_addr,1);
		return 0;
	}
	/* NS sequence error */
	if (FRAME_NS(f)!= l->vr) {
		/* TBD */ /* is info may be send */
		EC_DBG("seq.error: ns %d, vr %d\n",FRAME_NS(f),l->vr);
		// reject should be sent
		if (l->reject) /* already sent */
			return -1;
		EC_DBG("tx reject...\n");
		TX_REJ(l,l->rsp_addr,FRAME_I_PF(f));
		/* TX_REJ(l,l->cmd_addr,1); */
		l->reject = 1;
		// fixme: why to start timer
		//if (!l->timer)
		//	TIMER_START(l);
		return -1;
	}
	l->reject = 0;
	/* recv data */
	
	if(l->modem->put_chars)
		ret = l->modem->put_chars(l->modem,f->buf+3,f->count-3);
	if(ret<0) {
		/* FIXME: handle error*/ ;
	}
	if (ret != f->count-3) {
		/* save rest data, enter busy state */
		l->rx_head = 0;
		l->rx_count = f->count - 3 - ret;
		memcpy(l->rx_buf,f->buf+3+ret,l->rx_count);
		// TBD
		l->busy = 1;
		// try to push rest data later
		l->modem->packer_process = push_rest_data;
	}
	/* increment vr */
	l->vr = (l->vr+1)&0x7f;
	/* response I,RR,RNR */
	if (l->busy)
		TX_RNR(l,FRAME_ADDR(f),FRAME_I_PF(f));
	else if ( l->peer_busy || FRAME_I_PF(f) ||
		  !tx_info(l) )
		TX_RR(l,FRAME_ADDR(f),FRAME_I_PF(f));
	return 0;
}

/* rx supervisory frame */

static void rx_super_cmd(struct lapm_state *l, frame_t *f)
{
	int n;
	//LAPM_PRINT_FRAME("rx super:",1,f);

	/* note 8.4.7:
	   if l->busy each RR,RNR,REJ with p=1 should be replied by RNR with f=1 */
	switch(FRAME_CTRL(f)) {
	case FRAME_S_RR:
		/* clear peer_busy state */
		l->peer_busy = 0;
		n = ack_info(l,FRAME_NR(f));
		/* p=1 may be used for status checking */
		if (FRAME_S_PF(f) || !tx_info(l)) {
			if (l->busy)
				TX_RNR(l,FRAME_ADDR(f),FRAME_S_PF(f));
			else
				TX_RR(l,FRAME_ADDR(f),FRAME_S_PF(f));
		}
		break;
	case FRAME_S_RNR:
		/* going to peer_busy state */
		l->peer_busy = 1;
		n = ack_info(l,FRAME_NR(f));
		/* if p=1 may be used for status checking ?? */
		if (FRAME_S_PF(f)) {
			if (l->busy)
				TX_RNR(l,l->rsp_addr,FRAME_S_PF(f));
			else
				TX_RR(l,l->rsp_addr,FRAME_S_PF(f));
		}
		break;
	case FRAME_S_REJ:
		/* clear peer_busy state */
		l->peer_busy = 0;
		n = ack_info(l,FRAME_NR(f));
		if (!l->rtx_count) {
			TIMER_STOP(l);
			reject_info(l);
		}
		/* p=1 may be used for status checking */
		if (FRAME_S_PF(f) || !tx_info(l)) {
			if (l->busy)
				TX_RNR(l,FRAME_ADDR(f),FRAME_S_PF(f));
			else
				TX_RR(l,FRAME_ADDR(f),FRAME_S_PF(f));
		}
		break;
	case FRAME_S_SREJ:
		/* TBD, optional */
		EC_ERR("unsupported SREJ command!\n");
		return;
	default:
		EC_ERR("unknown s frame: %02x.\n",FRAME_CTRL(f));
		return;
	}
}

static void rx_super_rsp(struct lapm_state *l, frame_t *f)
{
	int n;
	//LAPM_PRINT_FRAME("rx_super",0,f);

	if (!l->rtx_count && FRAME_S_PF(f)) {
		EC_ERR("rx_super: unsolisited final response!\n");
		return;
	}
	/* ack I frames <= nr -1 */
	switch(FRAME_CTRL(f)) {
	case FRAME_S_RR:
		l->peer_busy = 0;
		n = ack_info(l,FRAME_NR(f));
		if (l->rtx_count && FRAME_S_PF(f)) {
			reject_info(l);
			TIMER_STOP(l);
		}
		break;
	case FRAME_S_RNR:
		l->peer_busy = 1;
		n = ack_info(l,FRAME_NR(f));
		if (l->rtx_count && FRAME_S_PF(f)) {
			reject_info(l);
			TIMER_STOP(l); // fixme: need stop even on RNR ??
		}
		if (!l->rtx_count)
			TIMER_START(l);
		break;
	case FRAME_S_REJ:
		l->peer_busy = 0;
		n = ack_info(l,FRAME_NR(f));
		if (!l->rtx_count || FRAME_S_PF(f)) {
			reject_info(l);
			TIMER_STOP(l);
		}
		break;
	case FRAME_S_SREJ:
		/* TBD, optional */
		EC_ERR("unsupported SREJ response!\n");
		return;
	default:
		EC_ERR("rx_s_rsp: unknown header %02x.\n", FRAME_CTRL(f));
		return;
	}
}


/* rx unnumbered frame */

static int rx_unnum_cmd(struct lapm_state *l, frame_t *f)
{
	//LAPM_PRINT_FRAME("rx unnum:",1,f);
	switch(FRAME_CTRL(f)&0xef) {
	case FRAME_U_SABME:
		EC_DBG("sabme received.\n");
		/* discard unacked I frames,
		   reset vs,vr,va, clear exceptions */
		reset(l);
		/* going to connected state */
		l->state = LAPM_DATA;
		/* respond UA (or DM on error) */
		// fixme: why may be error and TX_DM ??
		TX_UA(l,FRAME_U_PF(f));
		TIMER_STOP(l);
		modem_update_status(l->modem,STATUS_EC_LINK);
		break;
	case FRAME_U_UI:
		/* break signal */
		/* pf = 0 */
		/* TBD */
		break;
	case FRAME_U_DISC:
		/* respond UA (or DM) */
		if (l->state == LAPM_IDLE)
			TX_DM(l,1);
		else { /* going to disconnected state,
			  discard unacked I frames, reset all  */
			l->state = LAPM_IDLE;
			reset(l);
			TX_UA(l,FRAME_U_PF(f));
			T401_STOP(l);
			// TBD notify CF
			modem_update_status(l->modem,STATUS_EC_RELEASE);
		}
		break;
	case FRAME_U_XID:
		/* exchange general id info */
		rx_xid(l,f);
		tx_xid(l,l->rsp_addr);
		break;
	case FRAME_U_TEST:
		/* TBD */
		/* optional */
		break;
	default:
		EC_ERR("rx unnum cmd: unknown frame 0x%02x\n", FRAME_CTRL(f));
		return -1;
	}
	return 0;
}

static int rx_unnum_rsp(struct lapm_state *l, frame_t *f)
{
	//LAPM_PRINT_FRAME("rx unnum",0, f);
	switch(FRAME_CTRL(f)&0xef) {
	case FRAME_U_DM:
		/* notify peer about disconnected state */
		/* TBD */
		switch(l->state) {
		case LAPM_ESTAB:
		case LAPM_DISC:
			if (FRAME_U_PF(f)) {
				l->state = LAPM_IDLE;
				reset(l);
				T401_STOP(l);
				// TBD notify CF
				modem_update_status(l->modem,STATUS_EC_RELEASE);
			}
			break;
		case LAPM_DATA:
			if (l->rtx_count || !FRAME_U_PF(f)) {
				l->state = LAPM_IDLE;
				reset(l);
				// TBD: notify CF
				modem_update_status(l->modem,STATUS_EC_RELEASE);
			}
			break;
		case LAPM_IDLE:
			if (!FRAME_U_PF(f)) {
				// TBD: notify CF
				// to establish connection
				modem_update_status(l->modem,STATUS_EC_LINK);
			}
			break;
		default:
			break;
		}
		break;
	case FRAME_U_UI:
		/* break signal */
		/* pf = 0 */
		/* TBD */
		break;
	case FRAME_U_UA:
		/* notify us about mode change, responds SABME,DISC */
		switch(l->state) {
		case LAPM_ESTAB:
			l->state = LAPM_DATA;
			reset(l);
			TIMER_STOP(l);
			modem_update_status(l->modem,STATUS_EC_LINK);
			break;
		case LAPM_DISC:
			// TBD
			l->state = LAPM_IDLE;
			reset(l);
			T401_STOP(l);
			modem_update_status(l->modem,STATUS_EC_RELEASE);
			break;
		default:
			// TBD
			/* handle unsolicited UA */
			break;
		}
		/* clear all exceptions, busy states (self and peer) */
		/* reset vars */
		break;
	case FRAME_U_FRMR:
		/* not recovarable error */
		/* TBD */
		break;
	case FRAME_U_XID:
		if (l->config) {
			rx_xid(l,f);
			l->config = 0;
			T401_STOP(l);
			if (l->state == LAPM_IDLE) {
				lapm_connect(l);
			}
			else if (l->state == LAPM_DATA) {
				l->busy = 0;
				TX_RR(l,l->cmd_addr,0);
			}
		}
		break;
	default:
		EC_ERR("rx unnum rsp: unknown frame 0x%02x\n", FRAME_CTRL(f));
		break;
	}
	return 0;
}


/* lapm framer interface functions */

static frame_t *lapm_get_tx_frame(void *framer)
{
	struct lapm_state *l = framer;
	frame_t *f;
	/* get ctrl frame */
	if ( l->tx_ctrl != l->ctrl_list ) {
		f = l->tx_ctrl;
		l->tx_ctrl = l->tx_ctrl->next;
		return f;
	}
	/* get info frame */
	if ( l->peer_busy || l->config || l->state != LAPM_DATA)
		return 0;
	if ( l->tx_info == l->info_list && !tx_info(l) )
		return 0;
	f = l->tx_info;
	l->tx_info = l->tx_info->next;
	
	f->buf[0] = l->cmd_addr;
	f->buf[1] = l->vs << 1;
	f->buf[2] = l->vr << 1;
	l->vs = (l->vs + 1)&0x7f;
	if (!l->modem->bit_timer)
		TIMER_START(l);
	//LAPM_PRINT_FRAME("get_tx_frame",1,f);

	//EC_DBG1("get_tx_frame: sent %d, tx %d, free %d...\n",
	//  	  l->sent_count,l->tx_count,l->info_count);
	return f;
}

static void lapm_tx_complete(void *framer, frame_t *f)
{
	struct lapm_state *l = framer;
	LAPM_PRINT_FRAME("tx",TX_IS_COMMAND(f),f);
	switch(FRAME_TYPE(f)) {
	case FRAME_I:
		l->sent_count++; // debug
		l->tx_count--;   // debug
		//EC_DBG1("tx_info_complete: sent %d, tx %d, free %d.\n",
		//	 l->sent_count,l->tx_count,l->info_count);
		break;
	case FRAME_S:
	case FRAME_U:
		l->ctrl_count++; // debug
		break;
	default:
		EC_ERR("unknown frame type.\n");
		break;
	}
}


static int valid_data_state(struct lapm_state *l)
{
	switch (l->state) {
	case LAPM_ESTAB:
		reset(l);
		l->state = LAPM_DATA;
		modem_update_status(l->modem,STATUS_EC_LINK);
	case LAPM_DATA:
		return 1;
	case LAPM_DISC:
		reset(l);
		l->state = LAPM_IDLE;
		modem_update_status(l->modem,STATUS_EC_RELEASE);
	case LAPM_IDLE:
		return 0;
	default:
		EC_ERR("valid_data_state: unknown state %d.\n", l->state);
		return 0;
	}
	return 0;

}

static void lapm_rx_complete(void *framer, frame_t *f)
{
	struct lapm_state *l = framer;
	LAPM_PRINT_FRAME("rx",RX_IS_COMMAND(f),f);
	switch(FRAME_TYPE(f)) {
	case FRAME_I:
		if(!valid_data_state(l))
			return;
		rx_info(l,f);
		break;
	case FRAME_S:
		if(!valid_data_state(l))
			return;
		if (RX_IS_COMMAND(f))
			rx_super_cmd(l,f);
		else
			rx_super_rsp(l,f);
		break;
	case FRAME_U:
		if (RX_IS_COMMAND(f))
			rx_unnum_cmd(l,f);
		else
			rx_unnum_rsp(l,f);
		break;
	default:
		EC_ERR("unknown frame type.\n");
		break;
	}
}


/* lapm api functions */

static int lapm_connect(struct lapm_state *l)
{
	if (!l->state == LAPM_IDLE)
		return -1;

	/* negotiate params */
	//tx_xid(l,l->cmd_addr);

	/* clear all */
	reset(l);
	/* connect */
	l->state = LAPM_ESTAB;
	TX_SABME(l);
	/* start t401 (and not t403) */
	T401_START(l);
	return 0;
}

/* negotiate params */
static int lapm_config(struct lapm_state *l)
{
	l->config = 1;
	if (l->state == LAPM_DATA) {
		l->busy = 1;
		TX_RNR(l,l->cmd_addr,1);
	}
	tx_xid(l,l->cmd_addr);
	T401_START(l);
	return 0;
}

static int lapm_disconnect(struct lapm_state *l)
{
	l->state = LAPM_DISC;
	TX_DISC(l);
	/* start T401 (and not T403) */
	T401_START(l);
	return 0;
}


static void reset(struct lapm_state *l)
{
	l->busy = 0;
	l->peer_busy = 0;
	l->vs = 0;
	l->va = 0;
	l->vr = 0;
	/* discard pended data */
	// l->rx_head = l->rx_count = 0;
	/* discard info frames */
	l->sent_info = l->info_list;
	l->tx_info   = l->info_list;
	l->sent_count = l->tx_count = 0;
	l->info_count = LAPM_INFO_FRAMES;
	/* discard ctrl frames */
	// TBD ???
}

/* fixme: optimize mem usage */
static int alloc_frames(struct lapm_state *l)
{
#define arrsize(a) (sizeof(a)/sizeof((a)[0]))
	frame_t *f;
	int i;
	EC_DBG("alloc_frames: lapm size %d (info %d, ctrl %d).\n",
	       sizeof(*l), sizeof(l->info_buf), sizeof(l->ctrl_buf));
	/* init info list */
	l->info_list = &l->info_buf[0].frame;
	for (i=0;i<arrsize(l->info_buf);i++) {
		f = &l->info_buf[i].frame;
		f->next = &l->info_buf[i+1].frame;
		f->buf  = l->info_buf[i].ctrl;
		f->size = sizeof(l->info_buf[0]) - sizeof(frame_t);
	}
	f->next = l->info_list;
	l->info_count= arrsize(l->info_buf); // debug
	l->tx_info   = l->info_list;
	l->sent_info = l->info_list;
	/* init  ctrl list */
	l->ctrl_list = &l->ctrl_buf[0].frame;
	for (i=0;i<arrsize(l->ctrl_buf);i++) {
		f = &l->ctrl_buf[i].frame;
		f->next = &l->ctrl_buf[i+1].frame;
		f->buf  = l->ctrl_buf[i].ctrl;
		f->size = sizeof(l->ctrl_buf[0]) - sizeof(frame_t);
	}
	f->next = l->ctrl_list;
	l->ctrl_count= arrsize(l->ctrl_buf); // debug
	l->tx_ctrl   = l->ctrl_list;
	return 0;
}


static int lapm_init(struct lapm_state *l, struct modem *m)
{
	memset(l,0,sizeof(*l));
	l->modem = m;
	l->state      = LAPM_IDLE;
	l->busy       = 0;
	l->peer_busy  = 0;
	/* set originator/answer addresses */
	l->cmd_addr = m->caller ? 0x3 : 0x1;
	l->rsp_addr = m->caller ? 0x1 : 0x3;

	l->tx_win_size  = m->cfg.ec_tx_win_size;
	l->rx_win_size  = m->cfg.ec_rx_win_size;
	l->tx_info_size = m->cfg.ec_tx_info_size;
	l->rx_info_size = m->cfg.ec_rx_info_size;

	alloc_frames(l);
	reset(l);
	return 0;
}

static int lapm_exit(struct lapm_state *l)
{
	reset(l);
	l->info_list = 0;
	l->ctrl_list = 0;
	return 0;
}



/* modem ec interface func */
static void modem_ec_negotiate(struct modem *m)
{
	m->packer.hdlc.tx_complete  = lapm_tx_complete;
	m->packer.hdlc.get_tx_frame = lapm_get_tx_frame;
	lapm_config(&m->ec.lapm);
}

void modem_ec_start(struct modem *m)
{
	struct hdlc_state *h = &m->packer.hdlc;
	struct lapm_state *l = &m->ec.lapm;
	EC_DBG("modem_ec_start...\n");
	h->framer = l;
	h->tx_complete  = NULL;
	h->rx_complete  = lapm_rx_complete;
	h->get_tx_frame = NULL;
	if(m->caller) {
		m->bit_timer = 48*8;
		m->bit_timer_func = modem_ec_negotiate;
	}
	else {
		h->tx_complete  = lapm_tx_complete;
		h->get_tx_frame = lapm_get_tx_frame;
	}
	// fixme: improve BUSY becoming, remove packer_process()
	m->packer_process = NULL;
}

void modem_ec_stop(struct modem *m)
{
	struct lapm_state *l = &m->ec.lapm;
	EC_DBG("modem_ec_stop...\n");
	m->bit_timer = 0;
	m->packer_process = NULL;
	lapm_disconnect(l);
}

int modem_ec_init(struct modem *m)
{
	struct lapm_state *l = &m->ec.lapm;
	lapm_init(l,m);
	return 0;
}

void modem_ec_exit(struct modem *m)
{
	struct lapm_state *l = &m->ec.lapm;
	lapm_exit(l);
}

