/* $Id: eicon_idi.c,v 1.41.6.2 2001/04/07 21:41:44 armin Exp $
 *
 * ISDN lowlevel-module for Eicon active cards.
 *        IDI interface 
 *
 * Copyright 1998-2000  by Armin Schindler (mac@melware.de)
 * Copyright 1999,2000  Cytronics & Melware (info@melware.de)
 *
 * Thanks to	Deutsche Mailbox Saar-Lor-Lux GmbH
 *		for sponsoring and testing fax
 *		capabilities with Diva Server cards.
 *		(dor@deutschemailbox.de)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include <linux/config.h>
#define __NO_VERSION__
#include "eicon.h"
#include "eicon_idi.h"
#include "eicon_dsp.h"
#include "uxio.h"

#undef EICON_FULL_SERVICE_OKTETT

char *eicon_idi_revision = "$Revision: 1.41.6.2 $";

eicon_manifbuf *manbuf;

int eicon_idi_manage_assign(eicon_card *card);
int eicon_idi_manage_remove(eicon_card *card);
int idi_fill_in_T30(eicon_chan *chan, unsigned char *buffer);

int
idi_assign_req(eicon_REQ *reqbuf, int signet, eicon_chan *chan)
{
	int l = 0;
	int tmp;

	tmp = 0;
  if (!signet) {
	/* Signal Layer */
	reqbuf->XBuffer.P[l++] = CAI;
	reqbuf->XBuffer.P[l++] = 1;
	reqbuf->XBuffer.P[l++] = 0;
	reqbuf->XBuffer.P[l++] = KEY;
	reqbuf->XBuffer.P[l++] = 3;
	reqbuf->XBuffer.P[l++] = 'I';
	reqbuf->XBuffer.P[l++] = '4';
	reqbuf->XBuffer.P[l++] = 'L';
	reqbuf->XBuffer.P[l++] = SHIFT|6;
	reqbuf->XBuffer.P[l++] = SIN;
	reqbuf->XBuffer.P[l++] = 2;
	reqbuf->XBuffer.P[l++] = 0;
	reqbuf->XBuffer.P[l++] = 0;
	reqbuf->XBuffer.P[l++] = 0; /* end */
	reqbuf->Req = ASSIGN;
	reqbuf->ReqCh = 0;
	reqbuf->ReqId = DSIG_ID;
	reqbuf->XBuffer.length = l;
	reqbuf->Reference = 0; /* Sig Entity */
  }
  else {
	/* Network Layer */
	reqbuf->XBuffer.P[l++] = CAI;
	reqbuf->XBuffer.P[l++] = 1;
	reqbuf->XBuffer.P[l++] = chan->e.D3Id;
	reqbuf->XBuffer.P[l++] = LLC;
	reqbuf->XBuffer.P[l++] = 2;
	switch(chan->l2prot) {
		case ISDN_PROTO_L2_V11096:
		case ISDN_PROTO_L2_V11019:
		case ISDN_PROTO_L2_V11038:
		case ISDN_PROTO_L2_TRANS:
			reqbuf->XBuffer.P[l++] = 2; /* transparent */
			break;
		case ISDN_PROTO_L2_X75I:
		case ISDN_PROTO_L2_X75UI:
		case ISDN_PROTO_L2_X75BUI:
			reqbuf->XBuffer.P[l++] = 5; /* X.75 */ 
			break;
		case ISDN_PROTO_L2_MODEM:
  			if (chan->fsm_state == EICON_STATE_IWAIT)
				reqbuf->XBuffer.P[l++] = 9; /* V.42 incoming */
			else
				reqbuf->XBuffer.P[l++] = 10; /* V.42 */
			break;
		case ISDN_PROTO_L2_HDLC:
		case ISDN_PROTO_L2_FAX:
  			if (chan->fsm_state == EICON_STATE_IWAIT)
				reqbuf->XBuffer.P[l++] = 3; /* autoconnect on incoming */
			else
				reqbuf->XBuffer.P[l++] = 2; /* transparent */
			break;
		default:
			reqbuf->XBuffer.P[l++] = 1;
	}
	switch(chan->l3prot) {
		case ISDN_PROTO_L3_FCLASS2:
#ifdef CONFIG_ISDN_TTY_FAX
			reqbuf->XBuffer.P[l++] = 6;
			reqbuf->XBuffer.P[l++] = NLC;
			tmp = idi_fill_in_T30(chan, &reqbuf->XBuffer.P[l+1]);
			reqbuf->XBuffer.P[l++] = tmp; 
			l += tmp;
			break;
#endif
		case ISDN_PROTO_L3_TRANS:
		default:
			reqbuf->XBuffer.P[l++] = 4;
	}
	reqbuf->XBuffer.P[l++] = 0; /* end */
	reqbuf->Req = ASSIGN;
	reqbuf->ReqCh = 0;
	reqbuf->ReqId = NL_ID;
	reqbuf->XBuffer.length = l;
	reqbuf->Reference = 1; /* Net Entity */
  }
   return(0);
}

int
idi_put_req(eicon_REQ *reqbuf, int rq, int signet, int Ch)
{
	reqbuf->Req = rq;
	reqbuf->ReqCh = Ch;
	reqbuf->ReqId = 1;
	reqbuf->XBuffer.length = 1;
	reqbuf->XBuffer.P[0] = 0;
	reqbuf->Reference = signet;
   return(0);
}

int
idi_put_suspend_req(eicon_REQ *reqbuf, eicon_chan *chan)
{
	reqbuf->Req = SUSPEND;
	reqbuf->ReqCh = 0;
	reqbuf->ReqId = 1;
	reqbuf->XBuffer.P[0] = CAI;
	reqbuf->XBuffer.P[1] = 1;
	reqbuf->XBuffer.P[2] = chan->No;
	reqbuf->XBuffer.P[3] = 0;
	reqbuf->XBuffer.length = 4;
	reqbuf->Reference = 0; /* Sig Entity */
   return(0);
}

int
idi_call_res_req(eicon_REQ *reqbuf, eicon_chan *chan)
{
	int l = 9;
	reqbuf->Req = CALL_RES;
	reqbuf->ReqCh = 0;
	reqbuf->ReqId = 1;
	reqbuf->XBuffer.P[0] = CAI;
	reqbuf->XBuffer.P[1] = 6;
	reqbuf->XBuffer.P[2] = 9;
	reqbuf->XBuffer.P[3] = 0;
	reqbuf->XBuffer.P[4] = 0;
	reqbuf->XBuffer.P[5] = 0;
	reqbuf->XBuffer.P[6] = 32;
	reqbuf->XBuffer.P[7] = 0;
	switch(chan->l2prot) {
		case ISDN_PROTO_L2_X75I:
		case ISDN_PROTO_L2_X75UI:
		case ISDN_PROTO_L2_X75BUI:
		case ISDN_PROTO_L2_HDLC:
			reqbuf->XBuffer.P[1] = 1;
			reqbuf->XBuffer.P[2] = 0x05;
			l = 4;
			break;
		case ISDN_PROTO_L2_V11096:
			reqbuf->XBuffer.P[2] = 0x0d;
			reqbuf->XBuffer.P[3] = 5;
			reqbuf->XBuffer.P[4] = 0;
			break;
		case ISDN_PROTO_L2_V11019:
			reqbuf->XBuffer.P[2] = 0x0d;
			reqbuf->XBuffer.P[3] = 6;
			reqbuf->XBuffer.P[4] = 0;
			break;
		case ISDN_PROTO_L2_V11038:
			reqbuf->XBuffer.P[2] = 0x0d;
			reqbuf->XBuffer.P[3] = 7;
			reqbuf->XBuffer.P[4] = 0;
			break;
		case ISDN_PROTO_L2_MODEM:
			reqbuf->XBuffer.P[2] = 0x11;
			reqbuf->XBuffer.P[3] = 7;
			reqbuf->XBuffer.P[4] = 0;
			reqbuf->XBuffer.P[5] = 0;
			reqbuf->XBuffer.P[6] = 128;
			reqbuf->XBuffer.P[7] = 0;
			break;
		case ISDN_PROTO_L2_FAX:
			reqbuf->XBuffer.P[2] = 0x10;
			reqbuf->XBuffer.P[3] = 0;
			reqbuf->XBuffer.P[4] = 0;
			reqbuf->XBuffer.P[5] = 0;
			reqbuf->XBuffer.P[6] = 128;
			reqbuf->XBuffer.P[7] = 0;
			break;
		case ISDN_PROTO_L2_TRANS:
			switch(chan->l3prot) {
				case ISDN_PROTO_L3_TRANSDSP:
					reqbuf->XBuffer.P[2] = 22; /* DTMF, audio events on */
			}
			break;
	}
	reqbuf->XBuffer.P[8] = 0;
	reqbuf->XBuffer.length = l;
	reqbuf->Reference = 0; /* Sig Entity */
	eicon_log(NULL, 8, "idi_req: Ch%d: Call_Res\n", chan->No);
   return(0);
}

int
idi_do_req(eicon_card *card, eicon_chan *chan, int cmd, int layer)
{
        struct sk_buff *skb;
        struct sk_buff *skb2;
	eicon_REQ *reqbuf;
	eicon_chan_ptr *chan2;

        skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC);
        skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

        if ((!skb) || (!skb2)) {
               	eicon_log(card, 1, "idi_err: Ch%d: alloc_skb failed in do_req()\n", chan->No);
		if (skb) 
			dev_kfree_skb(skb);
		if (skb2) 
			dev_kfree_skb(skb2);
                return -ENOMEM; 
	}

	chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
	chan2->ptr = chan;

	reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ));
	eicon_log(card, 8, "idi_req: Ch%d: req %x (%s)\n", chan->No, cmd, (layer)?"Net":"Sig");
	if (layer) cmd |= 0x700;
	switch(cmd) {
		case ASSIGN:
		case ASSIGN|0x700:
			idi_assign_req(reqbuf, layer, chan);
			break;
		case REMOVE:
		case REMOVE|0x700:
			idi_put_req(reqbuf, REMOVE, layer, 0);
			break;
		case INDICATE_REQ:
			idi_put_req(reqbuf, INDICATE_REQ, 0, 0);
			break;
		case HANGUP:
			idi_put_req(reqbuf, HANGUP, 0, 0);
			break;
		case SUSPEND:
			idi_put_suspend_req(reqbuf, chan);
			break;
		case RESUME:
			idi_put_req(reqbuf, RESUME, 0 ,0);
			break;
		case REJECT:
			idi_put_req(reqbuf, REJECT, 0 ,0);
			break;
		case CALL_ALERT:
			idi_put_req(reqbuf, CALL_ALERT, 0, 0);
			break;
		case CALL_RES:
			idi_call_res_req(reqbuf, chan);
			break;
		case CALL_HOLD:
			idi_put_req(reqbuf, CALL_HOLD, 0, 0);
			break;
		case N_CONNECT|0x700:
			idi_put_req(reqbuf, N_CONNECT, 1, 0);
			break;
		case N_CONNECT_ACK|0x700:
			idi_put_req(reqbuf, N_CONNECT_ACK, 1, 0);
			break;
		case N_DISC|0x700:
			idi_put_req(reqbuf, N_DISC, 1, chan->e.IndCh);
			break;
		case N_DISC_ACK|0x700:
			idi_put_req(reqbuf, N_DISC_ACK, 1, chan->e.IndCh);
			break;
		default:
			eicon_log(card, 1, "idi_req: Ch%d: Unknown request\n", chan->No);
			dev_kfree_skb(skb);
			dev_kfree_skb(skb2);
			return(-1);
	}

	skb_queue_tail(&chan->e.X, skb);
	skb_queue_tail(&card->sndq, skb2); 
	eicon_schedule_tx(card);
	return(0);
}

int
eicon_idi_listen_req(eicon_card *card, eicon_chan *chan)
{
	if ((!card) || (!chan))
		return 1;

	eicon_log(card, 16, "idi_req: Ch%d: Listen_Req eazmask=0x%x\n",chan->No, chan->eazmask);
	if (!chan->e.D3Id) {
		idi_do_req(card, chan, ASSIGN, 0); 
	}
	if (chan->fsm_state == EICON_STATE_NULL) {
		if (!(chan->statectrl & HAVE_CONN_REQ)) {
			idi_do_req(card, chan, INDICATE_REQ, 0);
			chan->fsm_state = EICON_STATE_LISTEN;
		}
	}
  return(0);
}

unsigned char
idi_si2bc(int si1, int si2, char *bc, char *hlc)
{
  hlc[0] = 0;
  switch(si1) {
	case 1:
		bc[0] = 0x90;		/* 3,1 kHz audio */
		bc[1] = 0x90;		/* 64 kbit/s */
		bc[2] = 0xa3;		/* G.711 A-law */
#ifdef EICON_FULL_SERVICE_OKTETT
		if (si2 == 1) {
			bc[0] = 0x80;	/* Speech */
			hlc[0] = 0x02;	/* hlc len */
			hlc[1] = 0x91;	/* first hic */
			hlc[2] = 0x81;	/* Telephony */
		}
#endif
		return(3);
	case 2:
		bc[0] = 0x90;		/* 3,1 kHz audio */
		bc[1] = 0x90;		/* 64 kbit/s */
		bc[2] = 0xa3;		/* G.711 A-law */
#ifdef EICON_FULL_SERVICE_OKTETT
		if (si2 == 2) {
			hlc[0] = 0x02;	/* hlc len */
			hlc[1] = 0x91;	/* first hic */
			hlc[2] = 0x84;	/* Fax Gr.2/3 */
		}
#endif
		return(3);
	case 5:
	case 7:
	default:
		bc[0] = 0x88;
		bc[1] = 0x90;
		return(2);
  }
 return (0);
}

int
idi_hangup(eicon_card *card, eicon_chan *chan)
{
	if ((!card) || (!chan))
		return 1;

	if ((chan->fsm_state == EICON_STATE_ACTIVE) ||
	    (chan->fsm_state == EICON_STATE_WMCONN)) {
  		if (chan->e.B2Id) idi_do_req(card, chan, N_DISC, 1);
	}
	if (chan->e.B2Id) idi_do_req(card, chan, REMOVE, 1);
	if (chan->fsm_state != EICON_STATE_NULL) {
		chan->statectrl |= WAITING_FOR_HANGUP;
		idi_do_req(card, chan, HANGUP, 0);
		chan->fsm_state = EICON_STATE_NULL;
	}
	eicon_log(card, 8, "idi_req: Ch%d: Hangup\n", chan->No);
#ifdef CONFIG_ISDN_TTY_FAX
	chan->fax = 0;
#endif
  return(0);
}

int
capipmsg(eicon_card *card, eicon_chan *chan, capi_msg *cm)
{
	if ((cm->para[0] != 3) || (cm->para[1] != 0))
		return -1;
	if (cm->para[2] < 3)
		return -1;
	if (cm->para[4] != 0)
		return -1;
	switch(cm->para[3]) {
		case 4: /* Suspend */
			eicon_log(card, 8, "idi_req: Ch%d: Call Suspend\n", chan->No);
			if (cm->para[5]) {
				idi_do_req(card, chan, SUSPEND, 0);
			} else {
				idi_do_req(card, chan, CALL_HOLD, 0);
			}
			break;
		case 5: /* Resume */
			eicon_log(card, 8, "idi_req: Ch%d: Call Resume\n", chan->No);
			idi_do_req(card, chan, RESUME, 0);
			break;
        }
	return 0;
}

int
idi_connect_res(eicon_card *card, eicon_chan *chan)
{
	if ((!card) || (!chan))
		return 1;

	chan->fsm_state = EICON_STATE_IWAIT;
	
	/* check if old NetID has been removed */
	if (chan->e.B2Id) {
		eicon_log(card, 1, "eicon: Ch%d: old net_id %x still exist, removing.\n",
			chan->No, chan->e.B2Id);
		idi_do_req(card, chan, REMOVE, 1);
	}

	idi_do_req(card, chan, ASSIGN, 1);
	idi_do_req(card, chan, CALL_RES, 0);
	return(0);
}

int
idi_connect_req(eicon_card *card, eicon_chan *chan, char *phone,
                    char *eazmsn, int si1, int si2)
{
	int l = 0;
	int i;
	unsigned char tmp;
	unsigned char *sub, *sp;
	unsigned char bc[5];
	unsigned char hlc[5];
        struct sk_buff *skb;
        struct sk_buff *skb2;
	eicon_REQ *reqbuf;
	eicon_chan_ptr *chan2;

	if ((!card) || (!chan))
		return 1;

        skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC);
        skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

        if ((!skb) || (!skb2)) {
               	eicon_log(card, 1, "idi_err: Ch%d: alloc_skb failed in connect_req()\n", chan->No);
		if (skb) 
			dev_kfree_skb(skb);
		if (skb2) 
			dev_kfree_skb(skb2);
                return -ENOMEM; 
	}

	chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
	chan2->ptr = chan;

	reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ));
	reqbuf->Req = CALL_REQ;
	reqbuf->ReqCh = 0;
	reqbuf->ReqId = 1;

	sub = NULL;
	sp = phone;
	while (*sp) {
		if (*sp == '.') {
			sub = sp + 1;
			*sp = 0;
		} else
			sp++;
	}
	reqbuf->XBuffer.P[l++] = CPN;
	reqbuf->XBuffer.P[l++] = strlen(phone) + 1;
	reqbuf->XBuffer.P[l++] = 0x81;
	for(i=0; i<strlen(phone);i++) 
		reqbuf->XBuffer.P[l++] = phone[i] & 0x7f;
	if (sub) {
		reqbuf->XBuffer.P[l++] = DSA;
		reqbuf->XBuffer.P[l++] = strlen(sub) + 2;
		reqbuf->XBuffer.P[l++] = 0x80; /* NSAP coded */
		reqbuf->XBuffer.P[l++] = 0x50; /* local IDI format */
		while (*sub)
			reqbuf->XBuffer.P[l++] = *sub++ & 0x7f;
	}

	sub = NULL;
	sp = eazmsn;
	while (*sp) {
		if (*sp == '.') {
			sub = sp + 1;
			*sp = 0;
		} else
			sp++;
	}
	reqbuf->XBuffer.P[l++] = OAD;
	reqbuf->XBuffer.P[l++] = strlen(eazmsn) + 2;
	reqbuf->XBuffer.P[l++] = 0x01;
	reqbuf->XBuffer.P[l++] = 0x80;
	for(i=0; i<strlen(eazmsn);i++) 
		reqbuf->XBuffer.P[l++] = eazmsn[i] & 0x7f;
	if (sub) {
		reqbuf->XBuffer.P[l++] = OSA;
		reqbuf->XBuffer.P[l++] = strlen(sub) + 2;
		reqbuf->XBuffer.P[l++] = 0x80; /* NSAP coded */
		reqbuf->XBuffer.P[l++] = 0x50; /* local IDI format */
		while (*sub)
			reqbuf->XBuffer.P[l++] = *sub++ & 0x7f;
	}

	if (si2 > 2) {
		reqbuf->XBuffer.P[l++] = SHIFT|6;
		reqbuf->XBuffer.P[l++] = SIN;
		reqbuf->XBuffer.P[l++] = 2;
		reqbuf->XBuffer.P[l++] = si1;
		reqbuf->XBuffer.P[l++] = si2;
	}
	else if ((tmp = idi_si2bc(si1, si2, bc, hlc)) > 0) {
		reqbuf->XBuffer.P[l++] = BC;
		reqbuf->XBuffer.P[l++] = tmp;
		for(i=0; i<tmp;i++) 
			reqbuf->XBuffer.P[l++] = bc[i];
		if ((tmp=hlc[0])) {
			reqbuf->XBuffer.P[l++] = HLC;
			reqbuf->XBuffer.P[l++] = tmp;
			for(i=1; i<=tmp;i++) 
				reqbuf->XBuffer.P[l++] = hlc[i];
		}
	}

        reqbuf->XBuffer.P[l++] = CAI;
        reqbuf->XBuffer.P[l++] = 6;
        reqbuf->XBuffer.P[l++] = 0x09;
	reqbuf->XBuffer.P[l++] = 0;
	reqbuf->XBuffer.P[l++] = 0;
	reqbuf->XBuffer.P[l++] = 0;
	reqbuf->XBuffer.P[l++] = 32;
	reqbuf->XBuffer.P[l++] = 0;
        switch(chan->l2prot) {
		case ISDN_PROTO_L2_X75I:
		case ISDN_PROTO_L2_X75UI:
		case ISDN_PROTO_L2_X75BUI:
                case ISDN_PROTO_L2_HDLC:
                        reqbuf->XBuffer.P[l-6] = 5;
                        reqbuf->XBuffer.P[l-7] = 1;
			l -= 5; 
                        break;
                case ISDN_PROTO_L2_V11096:
                        reqbuf->XBuffer.P[l-7] = 3;
                        reqbuf->XBuffer.P[l-6] = 0x0d;
                        reqbuf->XBuffer.P[l-5] = 5;
                        reqbuf->XBuffer.P[l-4] = 0;
                        l -= 3;
                        break;
                case ISDN_PROTO_L2_V11019:
                        reqbuf->XBuffer.P[l-7] = 3;
                        reqbuf->XBuffer.P[l-6] = 0x0d;
                        reqbuf->XBuffer.P[l-5] = 6;
                        reqbuf->XBuffer.P[l-4] = 0;
                        l -= 3;
                        break;
                case ISDN_PROTO_L2_V11038:
                        reqbuf->XBuffer.P[l-7] = 3;
                        reqbuf->XBuffer.P[l-6] = 0x0d;
                        reqbuf->XBuffer.P[l-5] = 7;
                        reqbuf->XBuffer.P[l-4] = 0;
                        l -= 3;
                        break;
                case ISDN_PROTO_L2_MODEM:
			reqbuf->XBuffer.P[l-6] = 0x11;
			reqbuf->XBuffer.P[l-5] = 7;
			reqbuf->XBuffer.P[l-4] = 0;
			reqbuf->XBuffer.P[l-3] = 0;
			reqbuf->XBuffer.P[l-2] = 128;
			reqbuf->XBuffer.P[l-1] = 0;
                        break;
                case ISDN_PROTO_L2_FAX:
			reqbuf->XBuffer.P[l-6] = 0x10;
			reqbuf->XBuffer.P[l-5] = 0;
			reqbuf->XBuffer.P[l-4] = 0;
			reqbuf->XBuffer.P[l-3] = 0;
			reqbuf->XBuffer.P[l-2] = 128;
			reqbuf->XBuffer.P[l-1] = 0;
                        break;
		case ISDN_PROTO_L2_TRANS:
			switch(chan->l3prot) {
				case ISDN_PROTO_L3_TRANSDSP:
					reqbuf->XBuffer.P[l-6] = 22; /* DTMF, audio events on */
			}
			break;
        }
	
	reqbuf->XBuffer.P[l++] = 0; /* end */
	reqbuf->XBuffer.length = l;
	reqbuf->Reference = 0; /* Sig Entity */

	if (chan->statectrl & WAITING_FOR_HANGUP) {
		/*	If the line did not disconnect yet,
			we have to delay this command		*/
		eicon_log(card, 32, "idi_req: Ch%d: delaying conn_req\n", chan->No);
		chan->statectrl |= HAVE_CONN_REQ;
		chan->tskb1 = skb;
		chan->tskb2 = skb2;
	} else {
		skb_queue_tail(&chan->e.X, skb);
		skb_queue_tail(&card->sndq, skb2); 
		eicon_schedule_tx(card);
	}

	eicon_log(card, 8, "idi_req: Ch%d: Conn_Req %s -> %s\n",chan->No, eazmsn, phone);
   return(0);
}


void
idi_IndParse(eicon_card *ccard, eicon_chan *chan, idi_ind_message *message, unsigned char *buffer, int len)
{
	int i,j;
	int pos = 0;
	int codeset = 0;
	int wlen = 0;
	int lock = 0;
	__u8 w;
	__u16 code;
	isdn_ctrl cmd;

	memset(message, 0, sizeof(idi_ind_message));

	if ((!len) || (!buffer[pos])) return;

  while(pos <= len) {
	w = buffer[pos++];
	if (!w) return;
	if (w & 0x80) {
		wlen = 0;
	}
	else {
		wlen = buffer[pos++];
	}

	if (pos > len) return;

	if (lock & 0x80) lock &= 0x7f;
	else codeset = lock;

	if((w&0xf0) == SHIFT) {
		codeset = w;
		if(!(codeset & 0x08)) lock = codeset & 7;
		codeset &= 7;
		lock |= 0x80;
	}
	else {
		if (w==ESC && wlen >=2) {
			code = buffer[pos++]|0x800;
			wlen--;
		}
		else code = w;
		code |= (codeset<<8);

		if (pos + wlen > len) {
			eicon_log(ccard, 1, "idi_err: Ch%d: IElen %d of %x exceeds Ind_Length (+%d)\n", chan->No, 
					wlen, code, (pos + wlen) - len);
			return;
		}

		switch(code) {
			case OAD:
				if (wlen > sizeof(message->oad)) {
					pos += wlen;
					break;
				}
				j = 1;
				if (wlen) {
					message->plan = buffer[pos++];
					if (message->plan &0x80) 
						message->screen = 0;
					else {
						message->screen = buffer[pos++];
						j = 2;
					}
				}
				for(i=0; i < wlen-j; i++) 
					message->oad[i] = buffer[pos++];
				eicon_log(ccard, 2, "idi_inf: Ch%d: OAD=(0x%02x,0x%02x) %s\n", chan->No, 
					message->plan, message->screen, message->oad);
				break;
			case RDN:
				if (wlen > sizeof(message->rdn)) {
					pos += wlen;
					break;
				}
				j = 1;
				if (wlen) {
					if (!(buffer[pos++] & 0x80)) {
						pos++; 
						j = 2;
					}
				}
				for(i=0; i < wlen-j; i++) 
					message->rdn[i] = buffer[pos++];
				eicon_log(ccard, 2, "idi_inf: Ch%d: RDN= %s\n", chan->No, 
						message->rdn);
				break;
			case CPN:
				if (wlen > sizeof(message->cpn)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->cpn[i] = buffer[pos++];
				eicon_log(ccard, 2, "idi_inf: Ch%d: CPN=(0x%02x) %s\n", chan->No,
					(__u8)message->cpn[0], message->cpn + 1);
				break;
			case DSA:
				if (wlen > sizeof(message->dsa)) {
					pos += wlen;
					break;
				}
				pos += 2;
				for(i=0; i < wlen-2; i++) 
					message->dsa[i] = buffer[pos++];
				eicon_log(ccard, 2, "idi_inf: Ch%d: DSA=%s\n", chan->No, message->dsa);
				break;
			case OSA:
				if (wlen > sizeof(message->osa)) {
					pos += wlen;
					break;
				}
				pos += 2;
				for(i=0; i < wlen-2; i++) 
					message->osa[i] = buffer[pos++];
				eicon_log(ccard, 2, "idi_inf: Ch%d: OSA=%s\n", chan->No, message->osa);
				break;
			case CAD:
				pos += wlen;
				eicon_log(ccard, 2, "idi_inf: Ch%d: Connected Address in ind, len:%x\n", 
					chan->No, wlen);
				break;
			case BC:
				if (wlen > sizeof(message->bc)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->bc[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: BC = 0x%02x 0x%02x 0x%02x\n", chan->No,
					message->bc[0],message->bc[1],message->bc[2]);
				break;
			case 0x800|BC:
				if (wlen > sizeof(message->e_bc)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->e_bc[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: ESC/BC=%d\n", chan->No, message->bc[0]);
				break;
			case LLC:
				if (wlen > sizeof(message->llc)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->llc[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: LLC=%d %d %d %d ...\n", chan->No, message->llc[0],
					message->llc[1],message->llc[2],message->llc[3]);
				break;
			case HLC:
				if (wlen > sizeof(message->hlc)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->hlc[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: HLC=%x %x %x %x %x ...\n", chan->No,
					message->hlc[0], message->hlc[1],
					message->hlc[2], message->hlc[3], message->hlc[4]);
				break;
			case DSP:
			case 0x600|DSP:
				if (wlen > sizeof(message->display)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->display[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: Display: %s\n", chan->No,
					message->display);
				break;
			case 0x600|KEY:
				if (wlen > sizeof(message->keypad)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->keypad[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: Keypad: %s\n", chan->No,
					message->keypad);
				break;
			case NI:
			case 0x600|NI:
				if (wlen) {
					switch(buffer[pos] & 127) {
						case 0:
							eicon_log(ccard, 4, "idi_inf: Ch%d: User suspended.\n", chan->No);
							break;
						case 1:
							eicon_log(ccard, 4, "idi_inf: Ch%d: User resumed.\n", chan->No);
							break;
						case 2:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Bearer service change.\n", chan->No);
							break;
						default:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Unknown Notification %x.\n", 
									chan->No, buffer[pos] & 127);
					}
					pos += wlen;
				}
				break;
			case PI:
			case 0x600|PI:
				if (wlen > 1) {
					switch(buffer[pos+1] & 127) {
						case 1:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Call is not end-to-end ISDN.\n", chan->No);
							break;
						case 2:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Destination address is non ISDN.\n", chan->No);
							break;
						case 3:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Origination address is non ISDN.\n", chan->No);
							break;
						case 4:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Call has returned to the ISDN.\n", chan->No);
							break;
						case 5:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Interworking has occurred.\n", chan->No);
							break;
						case 8:
							eicon_log(ccard, 4, "idi_inf: Ch%d: In-band information available.\n", chan->No);
							break;
						default:
							eicon_log(ccard, 4, "idi_inf: Ch%d: Unknown Progress %x.\n", 
									chan->No, buffer[pos+1] & 127);
					}
				}
				pos += wlen;
				break;
			case CAU:
				if (wlen > sizeof(message->cau)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->cau[i] = buffer[pos++];
				memcpy(&chan->cause, &message->cau, 2);
				eicon_log(ccard, 4, "idi_inf: Ch%d: CAU=%d %d\n", chan->No,
					message->cau[0],message->cau[1]);
				break;
			case 0x800|CAU:
				if (wlen > sizeof(message->e_cau)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->e_cau[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: ECAU=%d %d\n", chan->No,
					message->e_cau[0],message->e_cau[1]);
				break;
			case 0x800|CHI:
				if (wlen > sizeof(message->e_chi)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->e_chi[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: ESC/CHI=%d\n", chan->No,
					message->e_cau[0]);
				break;
			case 0x800|0x7a:
				pos ++;
				message->e_mt=buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: EMT=0x%x\n", chan->No, message->e_mt);
				break;
			case DT:
				if (wlen > sizeof(message->dt)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->dt[i] = buffer[pos++];
				eicon_log(ccard, 4, "idi_inf: Ch%d: DT: %02d.%02d.%02d %02d:%02d:%02d\n", chan->No,
					message->dt[2], message->dt[1], message->dt[0],
					message->dt[3], message->dt[4], message->dt[5]);
				break;
			case 0x600|SIN:
				if (wlen > sizeof(message->sin)) {
					pos += wlen;
					break;
				}
				for(i=0; i < wlen; i++) 
					message->sin[i] = buffer[pos++];
				eicon_log(ccard, 2, "idi_inf: Ch%d: SIN=%d %d\n", chan->No,
					message->sin[0],message->sin[1]);
				break;
			case 0x600|CPS:
				eicon_log(ccard, 2, "idi_inf: Ch%d: Called Party Status in ind\n", chan->No);
				pos += wlen;
				break;
			case 0x600|CIF:
				for (i = 0; i < wlen; i++)
					if (buffer[pos + i] != '0') break;
				memcpy(&cmd.parm.num, &buffer[pos + i], wlen - i);
				cmd.parm.num[wlen - i] = 0;
				eicon_log(ccard, 2, "idi_inf: Ch%d: CIF=%s\n", chan->No, cmd.parm.num);
				pos += wlen;
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_CINF;
				cmd.arg = chan->No;
				ccard->interface.statcallb(&cmd);
				break;
			case 0x600|DATE:
				eicon_log(ccard, 2, "idi_inf: Ch%d: Date in ind\n", chan->No);
				pos += wlen;
				break;
			case 0xa1: 
				eicon_log(ccard, 2, "idi_inf: Ch%d: Sending Complete in ind.\n", chan->No);
				pos += wlen;
				break;
			case 0xe08: 
			case 0xe7a: 
			case 0xe04: 
			case 0xe00: 
				/* *** TODO *** */
			case CHA:
				/* Charge advice */
			case FTY:
			case 0x600|FTY:
			case CHI:
			case 0x800:
				/* Not yet interested in this */
				pos += wlen;
				break;
			case 0x880:
				/* Managment Information Element */
				if (!manbuf) {
					eicon_log(ccard, 1, "idi_err: manbuf not allocated\n");
				}
				else {
					memcpy(&manbuf->data[manbuf->pos], &buffer[pos], wlen);
					manbuf->length[manbuf->count] = wlen;
					manbuf->count++;
					manbuf->pos += wlen;
				}
				pos += wlen;
				break;
			default:
				pos += wlen;
				eicon_log(ccard, 6, "idi_inf: Ch%d: unknown information element 0x%x in ind, len:%x\n", 
					chan->No, code, wlen);
		}
	}
  }
}

void
idi_bc2si(unsigned char *bc, unsigned char *hlc, unsigned char *sin, unsigned char *si1, unsigned char *si2)
{
	si1[0] = 0;
	si2[0] = 0;

	switch (bc[0] & 0x7f) {
		case 0x00: /* Speech */
			si1[0] = 1;
#ifdef EICON_FULL_SERVICE_OKTETT
			si1[0] = sin[0];
			si2[0] = sin[1];
#endif
			break;
		case 0x10: /* 3.1 Khz audio */
			si1[0] = 1;
#ifdef EICON_FULL_SERVICE_OKTETT
			si1[0] = sin[0];
			si2[0] = sin[1];
#endif
			break;
		case 0x08: /* Unrestricted digital information */
			si1[0] = 7;
			si2[0] = sin[1];
			break;
		case 0x09: /* Restricted digital information */
			si1[0] = 2;
			break;
		case 0x11:
			/* Unrestr. digital information  with
			 * tones/announcements ( or 7 kHz audio
			 */
			si1[0] = 3;
			break;
		case 0x18: /* Video */
			si1[0] = 4;
			break;
	}
	switch (bc[1] & 0x7f) {
		case 0x40: /* packed mode */
			si1[0] = 8;
			break;
		case 0x10: /* 64 kbit */
		case 0x11: /* 2*64 kbit */
		case 0x13: /* 384 kbit */
		case 0x15: /* 1536 kbit */
		case 0x17: /* 1920 kbit */
			/* moderate = bc[1] & 0x7f; */
			break;
	}
}

/********************* FAX stuff ***************************/

#ifdef CONFIG_ISDN_TTY_FAX

int
idi_fill_in_T30(eicon_chan *chan, unsigned char *buffer)
{
	eicon_t30_s	*t30 = (eicon_t30_s *) buffer;

	if (!chan->fax) {
		eicon_log(NULL, 1,"idi_T30: fill_in with NULL fax struct, ERROR\n");
		return 0;
	}
	memset(t30, 0, sizeof(eicon_t30_s));
	t30->station_id_len = EICON_FAXID_LEN;
	memcpy(&t30->station_id[0], &chan->fax->id[0], EICON_FAXID_LEN);
	t30->resolution = chan->fax->resolution;
	t30->rate = chan->fax->rate + 1;	/* eicon rate starts with 1 */
	t30->format = T30_FORMAT_SFF;
	t30->pages_low = 0;
	t30->pages_high = 0;
	t30->atf = 1;				/* optimised for AT+F command set */
	t30->code = 0;
	t30->feature_bits_low = 0;
	t30->feature_bits_high = 0;
	t30->control_bits_low = 0;
	t30->control_bits_high = 0;

	if (chan->fax->nbc) {
		/* set compression by DCC value */
  	  switch(chan->fax->compression) {
		case (0):	/* 1-D modified */
			break;
		case (1):	/* 2-D modified Read */
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_2D_CODING;
			t30->feature_bits_low |= T30_FEATURE_BIT_2D_CODING;
			break;
		case (2):	/* 2-D uncompressed */
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_UNCOMPR;
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_2D_CODING;
			t30->feature_bits_low |= T30_FEATURE_BIT_UNCOMPR_ENABLED;
			t30->feature_bits_low |= T30_FEATURE_BIT_2D_CODING;
			break;
		case (3):	/* 2-D modified Read */
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_T6_CODING;
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_2D_CODING;
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_UNCOMPR;
			t30->feature_bits_low |= T30_FEATURE_BIT_UNCOMPR_ENABLED;
			t30->feature_bits_low |= T30_FEATURE_BIT_T6_CODING;
			t30->feature_bits_low |= T30_FEATURE_BIT_2D_CODING;
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_ECM;
			t30->feature_bits_low |= T30_FEATURE_BIT_ECM;
			break;
	  }
	} else {
		/* set compression to best */
		t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_T6_CODING;
		t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_2D_CODING;
		t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_UNCOMPR;
		t30->feature_bits_low |= T30_FEATURE_BIT_UNCOMPR_ENABLED;
		t30->feature_bits_low |= T30_FEATURE_BIT_T6_CODING;
		t30->feature_bits_low |= T30_FEATURE_BIT_2D_CODING;
		t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_ECM;
		t30->feature_bits_low |= T30_FEATURE_BIT_ECM;
	}
	switch(chan->fax->ecm) {
		case (0):	/* disable ECM */
			break;
		case (1):
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_ECM;
			t30->control_bits_low |= T30_CONTROL_BIT_ECM_64_BYTES;
			t30->feature_bits_low |= T30_FEATURE_BIT_ECM;
			t30->feature_bits_low |= T30_FEATURE_BIT_ECM_64_BYTES;
			break;
		case (2):
			t30->control_bits_low |= T30_CONTROL_BIT_ENABLE_ECM;
			t30->feature_bits_low |= T30_FEATURE_BIT_ECM;
			break;
	}

	if (DebugVar & 128) {
		char st[40];
		eicon_log(NULL, 128, "sT30:code = %x\n", t30->code);
		eicon_log(NULL, 128, "sT30:rate = %x\n", t30->rate);
		eicon_log(NULL, 128, "sT30:res  = %x\n", t30->resolution);
		eicon_log(NULL, 128, "sT30:format = %x\n", t30->format);
		eicon_log(NULL, 128, "sT30:pages_low = %x\n", t30->pages_low);
		eicon_log(NULL, 128, "sT30:pages_high = %x\n", t30->pages_high);
		eicon_log(NULL, 128, "sT30:atf  = %x\n", t30->atf);
		eicon_log(NULL, 128, "sT30:control_bits_low = %x\n", t30->control_bits_low);
		eicon_log(NULL, 128, "sT30:control_bits_high = %x\n", t30->control_bits_high);
		eicon_log(NULL, 128, "sT30:feature_bits_low = %x\n", t30->feature_bits_low);
		eicon_log(NULL, 128, "sT30:feature_bits_high = %x\n", t30->feature_bits_high);
		//eicon_log(NULL, 128, "sT30:universal_5 = %x\n", t30->universal_5);
		//eicon_log(NULL, 128, "sT30:universal_6 = %x\n", t30->universal_6);
		//eicon_log(NULL, 128, "sT30:universal_7 = %x\n", t30->universal_7);
		eicon_log(NULL, 128, "sT30:station_id_len = %x\n", t30->station_id_len);
		eicon_log(NULL, 128, "sT30:head_line_len = %x\n", t30->head_line_len);
		strncpy(st, t30->station_id, t30->station_id_len);
		st[t30->station_id_len] = 0;
		eicon_log(NULL, 128, "sT30:station_id = <%s>\n", st);
	}
	return(sizeof(eicon_t30_s));
}

/* send fax struct */
int
idi_send_edata(eicon_card *card, eicon_chan *chan)
{
	struct sk_buff *skb;
	struct sk_buff *skb2;
	eicon_REQ *reqbuf;
	eicon_chan_ptr *chan2;

	if ((chan->fsm_state == EICON_STATE_NULL) || (chan->fsm_state == EICON_STATE_LISTEN)) {
		eicon_log(card, 1, "idi_snd: Ch%d: send edata on state %d !\n", chan->No, chan->fsm_state);
		return -ENODEV;
	}
	eicon_log(card, 128, "idi_snd: Ch%d: edata (fax)\n", chan->No);

	skb = alloc_skb(sizeof(eicon_REQ) + sizeof(eicon_t30_s), GFP_ATOMIC);
	skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

	if ((!skb) || (!skb2)) {
		eicon_log(card, 1, "idi_err: Ch%d: alloc_skb failed in send_edata()\n", chan->No);
		if (skb) 
			dev_kfree_skb(skb);
		if (skb2) 
			dev_kfree_skb(skb2);
		return -ENOMEM;
	}

	chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
	chan2->ptr = chan;

	reqbuf = (eicon_REQ *)skb_put(skb, sizeof(eicon_t30_s) + sizeof(eicon_REQ));

	reqbuf->Req = N_EDATA;
	reqbuf->ReqCh = chan->e.IndCh;
	reqbuf->ReqId = 1;

	reqbuf->XBuffer.length = idi_fill_in_T30(chan, reqbuf->XBuffer.P);
	reqbuf->Reference = 1; /* Net Entity */

	skb_queue_tail(&chan->e.X, skb);
	skb_queue_tail(&card->sndq, skb2);
	eicon_schedule_tx(card);
	return (0);
}

void
idi_parse_edata(eicon_card *ccard, eicon_chan *chan, unsigned char *buffer, int len)
{
	eicon_t30_s *p = (eicon_t30_s *)buffer;
	int i;

	if (DebugVar & 128) {
		char st[40];
		eicon_log(ccard, 128, "rT30:len %d , size %d\n", len, sizeof(eicon_t30_s));
		eicon_log(ccard, 128, "rT30:code = %x\n", p->code);
		eicon_log(ccard, 128, "rT30:rate = %x\n", p->rate);
		eicon_log(ccard, 128, "rT30:res  = %x\n", p->resolution);
		eicon_log(ccard, 128, "rT30:format = %x\n", p->format);
		eicon_log(ccard, 128, "rT30:pages_low = %x\n", p->pages_low);
		eicon_log(ccard, 128, "rT30:pages_high = %x\n", p->pages_high);
		eicon_log(ccard, 128, "rT30:atf  = %x\n", p->atf);
		eicon_log(ccard, 128, "rT30:control_bits_low = %x\n", p->control_bits_low);
		eicon_log(ccard, 128, "rT30:control_bits_high = %x\n", p->control_bits_high);
		eicon_log(ccard, 128, "rT30:feature_bits_low = %x\n", p->feature_bits_low);
		eicon_log(ccard, 128, "rT30:feature_bits_high = %x\n", p->feature_bits_high);
		//eicon_log(ccard, 128, "rT30:universal_5 = %x\n", p->universal_5);
		//eicon_log(ccard, 128, "rT30:universal_6 = %x\n", p->universal_6);
		//eicon_log(ccard, 128, "rT30:universal_7 = %x\n", p->universal_7);
		eicon_log(ccard, 128, "rT30:station_id_len = %x\n", p->station_id_len);
		eicon_log(ccard, 128, "rT30:head_line_len = %x\n", p->head_line_len);
		strncpy(st, p->station_id, p->station_id_len);
		st[p->station_id_len] = 0;
		eicon_log(ccard, 128, "rT30:station_id = <%s>\n", st);
	}
	if (!chan->fax) {
		eicon_log(ccard, 1, "idi_edata: parse to NULL fax struct, ERROR\n");
		return;
	}
	chan->fax->code = p->code;
	i = (p->station_id_len < FAXIDLEN) ? p->station_id_len : (FAXIDLEN - 1);
	memcpy(chan->fax->r_id, p->station_id, i);
	chan->fax->r_id[i] = 0;
	chan->fax->r_resolution = p->resolution;
	chan->fax->r_rate = p->rate - 1;
	chan->fax->r_binary = 0; /* no binary support */
	chan->fax->r_width = 0;
	chan->fax->r_length = 2;
	chan->fax->r_scantime = 0;
	chan->fax->r_compression = 0;
	chan->fax->r_ecm = 0;
	if (p->feature_bits_low & T30_FEATURE_BIT_2D_CODING) {
		chan->fax->r_compression = 1;
		if (p->feature_bits_low & T30_FEATURE_BIT_UNCOMPR_ENABLED) {
			chan->fax->r_compression = 2;
		}
	}
	if (p->feature_bits_low & T30_FEATURE_BIT_T6_CODING) {
		chan->fax->r_compression = 3;
	}

	if (p->feature_bits_low & T30_FEATURE_BIT_ECM) {
		chan->fax->r_ecm = 2;
		if (p->feature_bits_low & T30_FEATURE_BIT_ECM_64_BYTES)
			chan->fax->r_ecm = 1;
	}
}

void
idi_fax_send_header(eicon_card *card, eicon_chan *chan, int header)
{
	static __u16 wd2sff[] = {
		1728, 2048, 2432, 1216, 864
	};
	static __u16 ln2sff[2][3] = {
		{ 1143, 1401, 0 } , { 2287, 2802, 0 }
	};
	struct sk_buff *skb;
	eicon_sff_dochead *doc;
	eicon_sff_pagehead *page;
	u_char *docp;

	if (!chan->fax) {
		eicon_log(card, 1, "idi_fax: send head with NULL fax struct, ERROR\n");
		return;
	}
	if (header == 2) { /* DocHeader + PageHeader */
		skb = alloc_skb(sizeof(eicon_sff_dochead) + sizeof(eicon_sff_pagehead), GFP_ATOMIC);
	} else {
		skb = alloc_skb(sizeof(eicon_sff_pagehead), GFP_ATOMIC);
	}
	if (!skb) {
		eicon_log(card, 1, "idi_err: Ch%d: alloc_skb failed in fax_send_header()\n", chan->No);
		return;
	}

	if (header == 2) { /* DocHeader + PageHeader */
		docp = skb_put(skb, sizeof(eicon_sff_dochead) + sizeof(eicon_sff_pagehead));
		doc = (eicon_sff_dochead *) docp;
		page = (eicon_sff_pagehead *) (docp + sizeof(eicon_sff_dochead));
		memset(docp, 0,sizeof(eicon_sff_dochead)  + sizeof(eicon_sff_pagehead));
		doc->id = 0x66666653;
		doc->version = 0x01;
		doc->off1pagehead = sizeof(eicon_sff_dochead);
	} else {
		page = (eicon_sff_pagehead *)skb_put(skb, sizeof(eicon_sff_pagehead));
		memset(page, 0, sizeof(eicon_sff_pagehead));
	}

	switch(header) {
		case 1:	/* PageHeaderEnd */
			page->pageheadid = 254;
			page->pageheadlen = 0; 
			break;
		case 0: /* PageHeader */
		case 2: /* DocHeader + PageHeader */
			page->pageheadid = 254;
			page->pageheadlen = sizeof(eicon_sff_pagehead) - 2;
			page->resvert = chan->fax->resolution;
			page->reshoriz = 0; /* always 203 dpi */
			page->coding = 0; /* always 1D */
			page->linelength = wd2sff[chan->fax->width];
			page->pagelength = ln2sff[chan->fax->resolution][chan->fax->length]; 
			eicon_log(card, 128, "sSFF-Head: linelength = %d\n", page->linelength);
			eicon_log(card, 128, "sSFF-Head: pagelength = %d\n", page->pagelength);
			break;
	}
	idi_send_data(card, chan, 0, skb, 0, 0);
}

void
idi_fax_cmd(eicon_card *card, eicon_chan *chan) 
{
	isdn_ctrl cmd;

	if ((!card) || (!chan))
		return;

	if (!chan->fax) {
		eicon_log(card, 1, "idi_fax: cmd with NULL fax struct, ERROR\n");
		return;
	}
	switch (chan->fax->code) {
		case ISDN_TTY_FAX_DT:
			if (chan->fax->phase == ISDN_FAX_PHASE_B) {
				idi_send_edata(card, chan);
				break;
			}
			if (chan->fax->phase == ISDN_FAX_PHASE_D) {
				idi_send_edata(card, chan);
				break;
			}
			break;

		case ISDN_TTY_FAX_DR:
			if (chan->fax->phase == ISDN_FAX_PHASE_B) {
				idi_send_edata(card, chan);

				cmd.driver = card->myid;
				cmd.command = ISDN_STAT_FAXIND;
				cmd.arg = chan->No;
				chan->fax->r_code = ISDN_TTY_FAX_CFR;
				card->interface.statcallb(&cmd);

				cmd.driver = card->myid;
				cmd.command = ISDN_STAT_FAXIND;
				cmd.arg = chan->No;
				chan->fax->r_code = ISDN_TTY_FAX_RID;
				card->interface.statcallb(&cmd);

				/* telling 1-D compression */
				chan->fax->r_compression = 0;
				cmd.driver = card->myid;
				cmd.command = ISDN_STAT_FAXIND;
				cmd.arg = chan->No;
				chan->fax->r_code = ISDN_TTY_FAX_DCS;
				card->interface.statcallb(&cmd);

				chan->fax2.NextObject = FAX_OBJECT_DOCU;
				chan->fax2.PrevObject = FAX_OBJECT_DOCU;

				break;
			}
			if (chan->fax->phase == ISDN_FAX_PHASE_D) {
				idi_send_edata(card, chan);
				break;
			}
			break;

		case ISDN_TTY_FAX_ET:
				switch(chan->fax->fet) {
					case 0:
					case 1:
						idi_fax_send_header(card, chan, 0);
						break;
					case 2:
						idi_fax_send_header(card, chan, 1);
						break;
				}
			break;
	}
}

void
idi_edata_rcveop(eicon_card *card, eicon_chan *chan)
{
	isdn_ctrl cmd;

	if (!chan->fax) {
		eicon_log(card, 1, "idi_edata: rcveop with NULL fax struct, ERROR\n");
		return;
	}
	cmd.driver = card->myid;
	cmd.command = ISDN_STAT_FAXIND;
	cmd.arg = chan->No;
	chan->fax->r_code = ISDN_TTY_FAX_ET;
	card->interface.statcallb(&cmd);
}

void
idi_reset_fax_stat(eicon_chan *chan)
{
	chan->fax2.LineLen = 0;
	chan->fax2.LineData = 0;
	chan->fax2.LineDataLen = 0;
	chan->fax2.NullByteExist = 0;
	chan->fax2.Dle = 0;
	chan->fax2.PageCount = 0;
	chan->fax2.Eop = 0;
}

void
idi_edata_action(eicon_card *ccard, eicon_chan *chan, char *buffer, int len)
{
	isdn_ctrl cmd;

	if (!chan->fax) {
		eicon_log(ccard, 1, "idi_edata: action with NULL fax struct, ERROR\n");
		return;
	}
	if (chan->fax->direction == ISDN_TTY_FAX_CONN_OUT) {
		idi_parse_edata(ccard, chan, buffer, len);

		if (chan->fax->phase == ISDN_FAX_PHASE_A) {
			idi_reset_fax_stat(chan);

			chan->fsm_state = EICON_STATE_ACTIVE;
			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_BCONN;
			cmd.arg = chan->No;
			strcpy(cmd.parm.num, "");
			ccard->interface.statcallb(&cmd);

			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_FCON;
			ccard->interface.statcallb(&cmd);

			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_RID;
			ccard->interface.statcallb(&cmd);

			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_DIS;
			ccard->interface.statcallb(&cmd);

			if (chan->fax->r_compression != 0) {
			/* telling fake compression in second DIS message */
				chan->fax->r_compression = 0;
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_FAXIND;
				cmd.arg = chan->No;
				chan->fax->r_code = ISDN_TTY_FAX_DIS;
				ccard->interface.statcallb(&cmd);
			}

			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_SENT; /* OK message */
			ccard->interface.statcallb(&cmd);
		} else
		if (chan->fax->phase == ISDN_FAX_PHASE_D) {

			if ((chan->fax->code == EDATA_T30_MCF) &&
			    (chan->fax->fet != 2)) {
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_FAXIND;
				cmd.arg = chan->No;
				chan->fax->r_code = ISDN_TTY_FAX_PTS;
				ccard->interface.statcallb(&cmd);
			}

			switch(chan->fax->fet) {
				case 0:	/* new page */
					/* stay in phase D , wait on cmd +FDT */
					break;
				case 1:	/* new document */
					/* link-level switch to phase B */
					break;
				case 2:	/* session end */
				default:
					/* send_edata produces error on some */
					/* fax-machines here, so we don't */
					/* idi_send_edata(ccard, chan); */
					break;
			}
		}
	}

	if (chan->fax->direction == ISDN_TTY_FAX_CONN_IN) {
		idi_parse_edata(ccard, chan, buffer, len);

		if ((chan->fax->code == EDATA_T30_DCS) &&
		    (chan->fax->phase == ISDN_FAX_PHASE_A)) {
			idi_reset_fax_stat(chan);

			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_BCONN;
			cmd.arg = chan->No;
			strcpy(cmd.parm.num, "");
			ccard->interface.statcallb(&cmd);

			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_FCON_I;
			ccard->interface.statcallb(&cmd);
		} else
		if ((chan->fax->code == EDATA_T30_TRAIN_OK) &&
		    (chan->fax->phase == ISDN_FAX_PHASE_A)) {
			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_RID;
			ccard->interface.statcallb(&cmd);

			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_TRAIN_OK;
			ccard->interface.statcallb(&cmd);
		} else
		if ((chan->fax->code == EDATA_T30_TRAIN_OK) &&
		    (chan->fax->phase == ISDN_FAX_PHASE_B)) {
			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_FAXIND;
			cmd.arg = chan->No;
			chan->fax->r_code = ISDN_TTY_FAX_TRAIN_OK;
			ccard->interface.statcallb(&cmd);
		} else
		if (chan->fax->phase == ISDN_FAX_PHASE_C) {
			switch(chan->fax->code) {
				case EDATA_T30_TRAIN_OK:
					idi_send_edata(ccard, chan);
					break;
				case EDATA_T30_MPS:
					chan->fax->fet = 0;
					idi_edata_rcveop(ccard, chan);
					break;
				case EDATA_T30_EOM:
					chan->fax->fet = 1;
					idi_edata_rcveop(ccard, chan);
					break;
				case EDATA_T30_EOP:
					chan->fax->fet = 2;
					idi_edata_rcveop(ccard, chan);
					break;
			}
		}
	}
}

void
fax_put_rcv(eicon_card *ccard, eicon_chan *chan, u_char *Data, int len)
{
	struct sk_buff *skb;
	
        skb = alloc_skb(len + MAX_HEADER_LEN, GFP_ATOMIC);
	if (!skb) {
		eicon_log(ccard, 1, "idi_err: Ch%d: alloc_skb failed in fax_put_rcv()\n", chan->No);
		return;
	}
	skb_reserve(skb, MAX_HEADER_LEN);
	memcpy(skb_put(skb, len), Data, len);
	ccard->interface.rcvcallb_skb(ccard->myid, chan->No, skb);
}

void
idi_faxdata_rcv(eicon_card *ccard, eicon_chan *chan, struct sk_buff *skb)
{
	eicon_OBJBUFFER InBuf;
	eicon_OBJBUFFER LineBuf;
	unsigned int Length = 0;
	unsigned int aLength = 0;
	unsigned int ObjectSize = 0;
	unsigned int ObjHeadLen = 0;
	unsigned int ObjDataLen = 0;
	__u8 Recordtype;
	__u8 PageHeaderLen;	
	__u8 Event;
	eicon_sff_pagehead *ob_page;

	__u16 Cl2Eol = 0x8000;

#	define EVENT_NONE	0
#	define EVENT_NEEDDATA	1

	if (!chan->fax) {
		eicon_log(ccard, 1, "idi_fax: rcvdata with NULL fax struct, ERROR\n");
		return;
	}


	
	if (chan->fax->direction == ISDN_TTY_FAX_CONN_IN) {
		InBuf.Data = skb->data;
		InBuf.Size = skb->len;
		InBuf.Len  = 0;
		InBuf.Next = InBuf.Data;
		LineBuf.Data = chan->fax2.abLine;
		LineBuf.Size = sizeof(chan->fax2.abLine);
		LineBuf.Len  = chan->fax2.LineLen;
		LineBuf.Next = LineBuf.Data + LineBuf.Len;

		Event = EVENT_NONE;
		while (Event == EVENT_NONE) {
			switch(chan->fax2.NextObject) {
				case FAX_OBJECT_DOCU:
						Length = LineBuf.Len + (InBuf.Size - InBuf.Len);
						if (Length < sizeof(eicon_sff_dochead)) {
							Event = EVENT_NEEDDATA;
							break;
						}
						ObjectSize = sizeof(eicon_sff_dochead);
						Length = ObjectSize;
						if (LineBuf.Len < Length) {
							Length -= LineBuf.Len;
							LineBuf.Len = 0;
							LineBuf.Next = LineBuf.Data;
							InBuf.Len += Length;
							InBuf.Next += Length;
						} else {
							LineBuf.Len -= Length;
							LineBuf.Next = LineBuf.Data + LineBuf.Len;
							memmove(LineBuf.Data, LineBuf.Data + Length, LineBuf.Len);
						}
						chan->fax2.PrevObject = FAX_OBJECT_DOCU;
						chan->fax2.NextObject = FAX_OBJECT_PAGE;
					break;

				case FAX_OBJECT_PAGE:
						Length = LineBuf.Len + (InBuf.Size - InBuf.Len);
						if (Length < 2) {
							Event = EVENT_NEEDDATA;
							break;
						}
						if (LineBuf.Len == 0) {
							*LineBuf.Next++ = *InBuf.Next++;
							LineBuf.Len++;
							InBuf.Len++;
						}
						if (LineBuf.Len == 1) {
							*LineBuf.Next++ = *InBuf.Next++;
							LineBuf.Len++;
							InBuf.Len++;
						}
						PageHeaderLen = *(LineBuf.Data + 1);
						ObjectSize = (PageHeaderLen == 0) ? 2 : sizeof(eicon_sff_pagehead);
						if (Length < ObjectSize) {
							Event = EVENT_NEEDDATA;
							break;
						}
						Length = ObjectSize;
						/* extract page dimensions */
						if (LineBuf.Len < Length) {
							aLength = Length - LineBuf.Len;
							memcpy(LineBuf.Next, InBuf.Next, aLength);
							LineBuf.Next += aLength;
							InBuf.Next += aLength;
							LineBuf.Len += aLength;
							InBuf.Len += aLength;
						}
						if (Length > 2) {
							ob_page = (eicon_sff_pagehead *)LineBuf.Data;
							switch(ob_page->linelength) {
								case 2048:
									chan->fax->r_width = 1;
									break;
								case 2432:
									chan->fax->r_width = 2;
									break;
								case 1216:
									chan->fax->r_width = 3;
									break;
								case 864:
									chan->fax->r_width = 4;
									break;
								case 1728:
								default:
									chan->fax->r_width = 0;
							}
							switch(ob_page->pagelength) {
								case 1143:
								case 2287:
									chan->fax->r_length = 0;
									break;
								case 1401:
								case 2802:
									chan->fax->r_length = 1;
									break;
								default:
									chan->fax->r_length = 2;
							}
							eicon_log(ccard, 128, "rSFF-Head: linelength = %d\n", ob_page->linelength);
							eicon_log(ccard, 128, "rSFF-Head: pagelength = %d\n", ob_page->pagelength);
						}
						LineBuf.Len -= Length;
						LineBuf.Next = LineBuf.Data + LineBuf.Len;
						memmove(LineBuf.Data, LineBuf.Data + Length, LineBuf.Len);

						chan->fax2.PrevObject = FAX_OBJECT_PAGE;
						chan->fax2.NextObject = FAX_OBJECT_LINE;
					break;

				case FAX_OBJECT_LINE:
						Length = LineBuf.Len + (InBuf.Size - InBuf.Len);
						if (Length < 1) {
							Event = EVENT_NEEDDATA;
							break;
						}
						if (LineBuf.Len == 0) {
							*LineBuf.Next++ = *InBuf.Next++;
							LineBuf.Len++;
							InBuf.Len++;
						}
						Recordtype = *LineBuf.Data;
						if (Recordtype == 0) {
							/* recordtype pixel row (2 byte length) */
							ObjHeadLen = 3;
							if (Length < ObjHeadLen) {
								Event = EVENT_NEEDDATA;
								break;
							}
							while (LineBuf.Len < ObjHeadLen) {
								*LineBuf.Next++ = *InBuf.Next++;
								LineBuf.Len++;
								InBuf.Len++;
							}
							ObjDataLen = *((__u16*) (LineBuf.Data + 1));
							ObjectSize = ObjHeadLen + ObjDataLen;
							if (Length < ObjectSize) {
								Event = EVENT_NEEDDATA;
								break;
							}
						} else
						if ((Recordtype >= 1) && (Recordtype <= 216)) {
							/* recordtype pixel row (1 byte length) */
							ObjHeadLen = 1;
							ObjDataLen = Recordtype;
							ObjectSize = ObjHeadLen + ObjDataLen;
							if (Length < ObjectSize) {
								Event = EVENT_NEEDDATA;
								break;
							}
						} else
						if ((Recordtype >= 217) && (Recordtype <= 253)) {
							/* recordtype empty lines */
							ObjHeadLen = 1;
							ObjDataLen = 0;
							ObjectSize = ObjHeadLen + ObjDataLen;
							LineBuf.Len--;
							LineBuf.Next = LineBuf.Data + LineBuf.Len;
							memmove(LineBuf.Data, LineBuf.Data + 1, LineBuf.Len);
							break;
						} else
						if (Recordtype == 254) {
							/* recordtype page header */
							chan->fax2.PrevObject = FAX_OBJECT_LINE;
							chan->fax2.NextObject = FAX_OBJECT_PAGE;
							break;
						} else {
							/* recordtype user information */
							ObjHeadLen = 2;
							if (Length < ObjHeadLen) {
								Event = EVENT_NEEDDATA;
								break;
							}
							while (LineBuf.Len < ObjHeadLen) {
								*LineBuf.Next++ = *InBuf.Next++;
								LineBuf.Len++;
								InBuf.Len++;
							}
							ObjDataLen = *(LineBuf.Data + 1);
							ObjectSize = ObjHeadLen + ObjDataLen;
							if (ObjDataLen == 0) {
								/* illegal line coding */
								LineBuf.Len -= ObjHeadLen;
								LineBuf.Next = LineBuf.Data + LineBuf.Len;
								memmove(LineBuf.Data, LineBuf.Data + ObjHeadLen, LineBuf.Len);
								break;
							} else {
								/* user information */
								if (Length < ObjectSize) {
									Event = EVENT_NEEDDATA;
									break;
								}
								Length = ObjectSize;
								if (LineBuf.Len < Length) {
									Length -= LineBuf.Len;
									LineBuf.Len = 0;
									LineBuf.Next = LineBuf.Data;
									InBuf.Len += Length;
									InBuf.Next += Length;
								} else {
									LineBuf.Len -= Length;
									LineBuf.Next = LineBuf.Data + LineBuf.Len;
									memmove(LineBuf.Data, LineBuf.Data + Length, LineBuf.Len);
								}
							}
							break;	
						}
						Length = ObjectSize;
						if (LineBuf.Len > ObjHeadLen) {
							fax_put_rcv(ccard, chan, LineBuf.Data + ObjHeadLen,
									(LineBuf.Len - ObjHeadLen));
						}
						Length -= LineBuf.Len;
						LineBuf.Len = 0;
						LineBuf.Next = LineBuf.Data;
						if (Length > 0) {
							fax_put_rcv(ccard, chan, InBuf.Next, Length);
							InBuf.Len += Length;
							InBuf.Next += Length;
						}
						fax_put_rcv(ccard, chan, (__u8 *)&Cl2Eol, sizeof(Cl2Eol));
					break;
			} /* end of switch (chan->fax2.NextObject) */
		} /* end of while (Event==EVENT_NONE) */
		if (InBuf.Len < InBuf.Size) {
			Length = InBuf.Size - InBuf.Len;
			if ((LineBuf.Len + Length) > LineBuf.Size) {
				eicon_log(ccard, 1, "idi_fax: Ch%d: %d bytes dropping, small buffer\n", chan->No,
					Length);
				} else {
					memcpy(LineBuf.Next, InBuf.Next, Length);
					LineBuf.Len += Length;
				}
		}
		chan->fax2.LineLen = LineBuf.Len;
	} else { /* CONN_OUT */
		/* On CONN_OUT we do not need incoming data, drop it */
		/* maybe later for polling */
	}

#	undef EVENT_NONE
#	undef EVENT_NEEDDATA

	return;
}

int
idi_fax_send_outbuf(eicon_card *ccard, eicon_chan *chan, eicon_OBJBUFFER *OutBuf)
{
	struct sk_buff *skb;

	skb = alloc_skb(OutBuf->Len, GFP_ATOMIC);
	if (!skb) {
		eicon_log(ccard, 1, "idi_err: Ch%d: alloc_skb failed in fax_send_outbuf()\n", chan->No);
		return(-1);
	}
	memcpy(skb_put(skb, OutBuf->Len), OutBuf->Data, OutBuf->Len);

	OutBuf->Len = 0;
	OutBuf->Next = OutBuf->Data;

	return(idi_send_data(ccard, chan, 0, skb, 1, 0));
}

int
idi_faxdata_send(eicon_card *ccard, eicon_chan *chan, struct sk_buff *skb)
{
	isdn_ctrl cmd;
	eicon_OBJBUFFER InBuf;
	__u8 InData;
	__u8 InMask;
	eicon_OBJBUFFER OutBuf;
	eicon_OBJBUFFER LineBuf;
	__u32 LineData;
	unsigned int LineDataLen;
	__u8 Byte;
	__u8 Event;
	int ret = 1;

#	define EVENT_NONE	0
#	define EVENT_EOD	1
#	define EVENT_EOL 	2
#	define EVENT_EOP 	3

	if ((!ccard) || (!chan))
		return -1;

	if (!chan->fax) {
		eicon_log(ccard, 1, "idi_fax: senddata with NULL fax struct, ERROR\n");
		return -1;
	}

	if (chan->fax->direction == ISDN_TTY_FAX_CONN_IN) {
		/* Simply ignore any data written in data mode when receiving a fax.    */
		/* This is not completely correct because only XON's should come here.  */
        	dev_kfree_skb(skb);
		return 1;
	}

	if (chan->fax->phase != ISDN_FAX_PHASE_C) {
        	dev_kfree_skb(skb);
		return 1;
	}

        if (chan->queued + skb->len > 1200)
                return 0;
	if (chan->pqueued > 1)
		return 0;

	InBuf.Data = skb->data;
	InBuf.Size = skb->len;
	InBuf.Len  = 0;
	InBuf.Next = InBuf.Data;
	InData = 0;
	InMask = 0;

	LineBuf.Data = chan->fax2.abLine;
	LineBuf.Size = sizeof(chan->fax2.abLine);
	LineBuf.Len  = chan->fax2.LineLen;
	LineBuf.Next = LineBuf.Data + LineBuf.Len;
	LineData = chan->fax2.LineData;
	LineDataLen = chan->fax2.LineDataLen;

	OutBuf.Data = chan->fax2.abFrame;
	OutBuf.Size = sizeof(chan->fax2.abFrame);
	OutBuf.Len = 0;
	OutBuf.Next = OutBuf.Data;

	Event = EVENT_NONE;

	chan->fax2.Eop = 0;

	for (;;) {
	  for (;;) {
		if (InMask == 0) {
			if (InBuf.Len >= InBuf.Size) {
				Event = EVENT_EOD;
				break;
			}
			if ((chan->fax2.Dle != _DLE_) && *InBuf.Next == _DLE_) {
				chan->fax2.Dle = _DLE_;
				InBuf.Next++;
				InBuf.Len++;
				if (InBuf.Len >= InBuf.Size) {
					Event = EVENT_EOD;
					break;
				}
			}
			if (chan->fax2.Dle == _DLE_) {
				chan->fax2.Dle = 0;
				if (*InBuf.Next == _ETX_) {
					Event = EVENT_EOP;
					break;
				} else
				if (*InBuf.Next == _DLE_) {
					/* do nothing */
				} else {
					eicon_log(ccard, 1,
						"idi_err: Ch%d: unknown DLE escape %02x found\n",
							chan->No, *InBuf.Next);
					InBuf.Next++;
					InBuf.Len++;
					if (InBuf.Len >= InBuf.Size) {
						Event = EVENT_EOD;
						break;
					}
				}
			}
			InBuf.Len++;
			InData = *InBuf.Next++;
			InMask = (chan->fax->bor) ? 0x80 : 0x01;
		}
		while (InMask) {
			LineData >>= 1;
			LineDataLen++;
			if (InData & InMask)
				LineData |= 0x80000000;
			if (chan->fax->bor)
				InMask >>= 1;
			else
				InMask <<= 1;

			if ((LineDataLen >= T4_EOL_BITSIZE) &&
			   ((LineData & T4_EOL_MASK_DWORD) == T4_EOL_DWORD)) {
				Event = EVENT_EOL;
				if (LineDataLen > T4_EOL_BITSIZE) {
					Byte = (__u8)
						((LineData & ~T4_EOL_MASK_DWORD) >>
						(32 - LineDataLen));
					if (Byte == 0) {
						if (! chan->fax2.NullByteExist) {
							chan->fax2.NullBytesPos = LineBuf.Len;
							chan->fax2.NullByteExist = 1;
						}
					} else {
						chan->fax2.NullByteExist = 0;
					}
					if (LineBuf.Len < LineBuf.Size) {
						*LineBuf.Next++  = Byte;
						LineBuf.Len++;
					}
				}
				LineDataLen = 0;
				break;
			}
			if (LineDataLen >= T4_EOL_BITSIZE + 8) {
				Byte = (__u8)
					((LineData & ~T4_EOL_MASK_DWORD) >>
					(32 - T4_EOL_BITSIZE - 8));
				LineData &= T4_EOL_MASK_DWORD;
				LineDataLen = T4_EOL_BITSIZE;
				if (Byte == 0) {
					if (! chan->fax2.NullByteExist) {
						chan->fax2.NullBytesPos = LineBuf.Len;
						chan->fax2.NullByteExist = 1;
					}
				} else {
					chan->fax2.NullByteExist = 0;
				}
				if (LineBuf.Len < LineBuf.Size) {
					*LineBuf.Next++  = Byte; 
					LineBuf.Len++;
				}
			}
		}
		if (Event != EVENT_NONE)
			break;
	  }

		if ((Event != EVENT_EOL) && (Event != EVENT_EOP))
			break;

		if ((Event == EVENT_EOP) && (LineDataLen > 0)) {
			LineData >>= 32 - LineDataLen;
			LineDataLen = 0;
			while (LineData != 0) {
				Byte = (__u8) LineData;
				LineData >>= 8;
				if (Byte == 0) {
					if (! chan->fax2.NullByteExist) {
						chan->fax2.NullBytesPos = LineBuf.Len;
						chan->fax2.NullByteExist = 1;
					}
				} else {
					chan->fax2.NullByteExist = 0;
				}
				if (LineBuf.Len < LineBuf.Size) {
					*LineBuf.Next++  = Byte;
					LineBuf.Len++;
				}
				
			}
		}
		if (chan->fax2.NullByteExist) {
			if (chan->fax2.NullBytesPos == 0) {
				LineBuf.Len = 0;
			} else {
				LineBuf.Len = chan->fax2.NullBytesPos + 1;
			}
		}
		if (LineBuf.Len > 0) {
			if (OutBuf.Len + LineBuf.Len + SFF_LEN_FLD_SIZE > OutBuf.Size) {
				ret = idi_fax_send_outbuf(ccard, chan, &OutBuf);
			}
			if (LineBuf.Len <= 216) {
				*OutBuf.Next++ = (__u8) LineBuf.Len;
				OutBuf.Len++;
			} else {
				*OutBuf.Next++ = 0;
				*((__u16 *) OutBuf.Next)++ = (__u16) LineBuf.Len;
				OutBuf.Len += 3;
			}
			memcpy(OutBuf.Next, LineBuf.Data, LineBuf.Len);
			OutBuf.Next += LineBuf.Len;
			OutBuf.Len  += LineBuf.Len;
		}
		LineBuf.Len = 0;
		LineBuf.Next = LineBuf.Data;
		chan->fax2.NullByteExist = 0;
		if (Event == EVENT_EOP)
			break;

		Event = EVENT_NONE;
	}

	if (Event == EVENT_EOP) {
		chan->fax2.Eop = 1;
		chan->fax2.PageCount++;
		cmd.driver = ccard->myid;
		cmd.command = ISDN_STAT_FAXIND;
		cmd.arg = chan->No;
		chan->fax->r_code = ISDN_TTY_FAX_EOP;
		ccard->interface.statcallb(&cmd);
	}
	if (OutBuf.Len > 0) {
		ret = idi_fax_send_outbuf(ccard, chan, &OutBuf);
	}

	chan->fax2.LineLen = LineBuf.Len;
	chan->fax2.LineData = LineData;
	chan->fax2.LineDataLen = LineDataLen;

#	undef EVENT_NONE
#	undef EVENT_EOD
#	undef EVENT_EOL
#	undef EVENT_EOP

	if (ret >= 0)
	        dev_kfree_skb(skb);
	if (ret == 0)
		ret = 1;
	return(ret);
}

void
idi_fax_hangup(eicon_card *ccard, eicon_chan *chan)
{
	isdn_ctrl cmd;

	if (!chan->fax) {
		eicon_log(ccard, 1, "idi_fax: hangup with NULL fax struct, ERROR\n");
		return;
	}
	if ((chan->fax->direction == ISDN_TTY_FAX_CONN_OUT) &&
	    (chan->fax->code == 0)) {
		cmd.driver = ccard->myid;
		cmd.command = ISDN_STAT_FAXIND;
		cmd.arg = chan->No;
		chan->fax->r_code = ISDN_TTY_FAX_PTS;
		ccard->interface.statcallb(&cmd);
	}
	if ((chan->fax->code > 1) && (chan->fax->code < 120))
		chan->fax->code += 120;
	eicon_log(ccard, 8, "idi_fax: Ch%d: Hangup (code=%d)\n", chan->No, chan->fax->code);
	chan->fax->r_code = ISDN_TTY_FAX_HNG;
	cmd.driver = ccard->myid;
	cmd.command = ISDN_STAT_FAXIND;
	cmd.arg = chan->No;
	ccard->interface.statcallb(&cmd);
}

#endif	/******** FAX ********/

int
idi_send_udata(eicon_card *card, eicon_chan *chan, int UReq, u_char *buffer, int len)
{
	struct sk_buff *skb;
	struct sk_buff *skb2;
	eicon_REQ *reqbuf;
	eicon_chan_ptr *chan2;

	if ((chan->fsm_state == EICON_STATE_NULL) || (chan->fsm_state == EICON_STATE_LISTEN)) {
		eicon_log(card, 1, "idi_snd: Ch%d: send udata on state %d !\n", chan->No, chan->fsm_state);
		return -ENODEV;
	}
	eicon_log(card, 8, "idi_snd: Ch%d: udata 0x%x: %d %d %d %d\n", chan->No,
			UReq, buffer[0], buffer[1], buffer[2], buffer[3]);

	skb = alloc_skb(sizeof(eicon_REQ) + len + 1, GFP_ATOMIC);
	skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

	if ((!skb) || (!skb2)) {
		eicon_log(card, 1, "idi_err: Ch%d: alloc_skb failed in send_udata()\n", chan->No);
		if (skb) 
			dev_kfree_skb(skb);
		if (skb2) 
			dev_kfree_skb(skb2);
		return -ENOMEM;
	}

	chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
	chan2->ptr = chan;

	reqbuf = (eicon_REQ *)skb_put(skb, 1 + len + sizeof(eicon_REQ));

	reqbuf->Req = N_UDATA;
	reqbuf->ReqCh = chan->e.IndCh;
	reqbuf->ReqId = 1;

	reqbuf->XBuffer.length = len + 1;
	reqbuf->XBuffer.P[0] = UReq;
	memcpy(&reqbuf->XBuffer.P[1], buffer, len);
	reqbuf->Reference = 1; /* Net Entity */

	skb_queue_tail(&chan->e.X, skb);
	skb_queue_tail(&card->sndq, skb2);
	eicon_schedule_tx(card);
	return (0);
}

void
idi_audio_cmd(eicon_card *ccard, eicon_chan *chan, int cmd, u_char *value)
{
	u_char buf[6];
	struct enable_dtmf_s *dtmf_buf = (struct enable_dtmf_s *)buf;

	if ((!ccard) || (!chan))
		return;

	memset(buf, 0, 6);
	switch(cmd) {
		case ISDN_AUDIO_SETDD:
			if (value[0]) {
				dtmf_buf->tone = (__u16) (value[1] * 5);
				dtmf_buf->gap = (__u16) (value[1] * 5);
				idi_send_udata(ccard, chan,
					DSP_UDATA_REQUEST_ENABLE_DTMF_RECEIVER,
					buf, 4);
			} else {
				idi_send_udata(ccard, chan,
					DSP_UDATA_REQUEST_DISABLE_DTMF_RECEIVER,
					buf, 0);
			}
			break;
	}
}

void
idi_parse_udata(eicon_card *ccard, eicon_chan *chan, unsigned char *buffer, int len)
{
	isdn_ctrl cmd;
	eicon_dsp_ind *p = (eicon_dsp_ind *) (&buffer[1]);
        static char *connmsg[] =
        {"", "V.21", "V.23", "V.22", "V.22bis", "V.32bis", "V.34",
         "V.8", "Bell 212A", "Bell 103", "V.29 Leased", "V.33 Leased", "V.90",
         "V.21 CH2", "V.27ter", "V.29", "V.33", "V.17", "V.32", "K56Flex",
         "X2", "V.18", "V.18LH", "V.18HL", "V.21LH", "V.21HL",
         "Bell 103LH", "Bell 103HL", "V.23", "V.23", "EDT 110",
         "Baudot45", "Baudot47", "Baudot50", "DTMF" };
	static u_char dtmf_code[] = {
	'1','4','7','*','2','5','8','0','3','6','9','#','A','B','C','D'
	};

	if ((!ccard) || (!chan))
		return;

	switch (buffer[0]) {
		case DSP_UDATA_INDICATION_SYNC:
			eicon_log(ccard, 16, "idi_ind: Ch%d: UDATA_SYNC time %d\n", chan->No, p->time);
			break;
		case DSP_UDATA_INDICATION_DCD_OFF:
			eicon_log(ccard, 8, "idi_ind: Ch%d: UDATA_DCD_OFF time %d\n", chan->No, p->time);
			break;
		case DSP_UDATA_INDICATION_DCD_ON:
			if ((chan->l2prot == ISDN_PROTO_L2_MODEM) &&
			    (chan->fsm_state == EICON_STATE_WMCONN)) {
				chan->fsm_state = EICON_STATE_ACTIVE;
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_BCONN;
				cmd.arg = chan->No;
                                if (p->norm > 34) {
                                  sprintf(cmd.parm.num, "%d/(%d)", p->speed, p->norm);
                                } else {
                                  sprintf(cmd.parm.num, "%d/%s", p->speed, connmsg[p->norm]);
                                }
				ccard->interface.statcallb(&cmd);
			}
			eicon_log(ccard, 8, "idi_ind: Ch%d: UDATA_DCD_ON time %d\n", chan->No, p->time);
			eicon_log(ccard, 8, "idi_ind: Ch%d: %d %d %d %d\n", chan->No,
				p->norm, p->options, p->speed, p->delay); 
			break;
		case DSP_UDATA_INDICATION_CTS_OFF:
			eicon_log(ccard, 8, "idi_ind: Ch%d: UDATA_CTS_OFF time %d\n", chan->No, p->time);
			break;
		case DSP_UDATA_INDICATION_CTS_ON:
			eicon_log(ccard, 8, "idi_ind: Ch%d: UDATA_CTS_ON time %d\n", chan->No, p->time);
			eicon_log(ccard, 8, "idi_ind: Ch%d: %d %d %d %d\n", chan->No,
				p->norm, p->options, p->speed, p->delay); 
			break;
		case DSP_UDATA_INDICATION_DISCONNECT:
			eicon_log(ccard, 8, "idi_ind: Ch%d: UDATA_DISCONNECT cause %d\n", chan->No, buffer[1]);
			break;
		case DSP_UDATA_INDICATION_DTMF_DIGITS_RECEIVED:
			eicon_log(ccard, 8, "idi_ind: Ch%d: UDATA_DTMF_REC '%c'\n", chan->No,
				dtmf_code[buffer[1]]);
			cmd.driver = ccard->myid;
			cmd.command = ISDN_STAT_AUDIO;
			cmd.parm.num[0] = ISDN_AUDIO_DTMF;
			cmd.parm.num[1] = dtmf_code[buffer[1]];
			cmd.arg = chan->No;
			ccard->interface.statcallb(&cmd);
			break;
		default:
			eicon_log(ccard, 8, "idi_ind: Ch%d: UNHANDLED UDATA Indication 0x%02x\n", chan->No, buffer[0]);
	}
}

void
eicon_parse_trace(eicon_card *ccard, unsigned char *buffer, int len)
{
	int i,j,n;
	int buflen = len * 3 + 30;
	char *p;
	struct trace_s {
		unsigned long time;
		unsigned short size;
		unsigned short code;
		unsigned char data[1];
	} *q;

	if (!(p = kmalloc(buflen, GFP_ATOMIC))) {
		eicon_log(ccard, 1, "idi_err: Ch??: could not allocate trace buffer\n");
		return;
	}
	memset(p, 0, buflen);
	q = (struct trace_s *)buffer;

	if (DebugVar & 512) {
		if ((q->code == 3) || (q->code == 4)) {
			n = (short) *(q->data);
			if (n) {
				j = sprintf(p, "DTRC:");
				for (i = 0; i < n; i++) {
					j += sprintf(p + j, "%02x ", q->data[i+2]);
				}
				j += sprintf(p + j, "\n");
			}
		}
	} else {
		j = sprintf(p, "XLOG: %lx %04x %04x ",
			q->time, q->size, q->code);

		for (i = 0; i < q->size; i++) {
			j += sprintf(p + j, "%02x ", q->data[i]);
		}
		j += sprintf(p + j, "\n");
	}
	if (strlen(p))
		eicon_putstatus(ccard, p);
	kfree(p);
}

void
idi_handle_ind(eicon_card *ccard, struct sk_buff *skb)
{
	int tmp;
	char tnum[64];
	int dlev;
	int free_buff;
	ulong flags;
	struct sk_buff *skb2;
        eicon_IND *ind = (eicon_IND *)skb->data;
	eicon_chan *chan;
	idi_ind_message message;
	isdn_ctrl cmd;

	if (!ccard) {
		eicon_log(ccard, 1, "idi_err: Ch??: null card in handle_ind\n");
  		dev_kfree_skb(skb);
		return;
	}

	if ((chan = ccard->IdTable[ind->IndId]) == NULL) {
		eicon_log(ccard, 1, "idi_err: Ch??: null chan in handle_ind\n");
  		dev_kfree_skb(skb);
		return;
	}
	
	if ((ind->Ind != 8) && (ind->Ind != 0xc))
		dlev = 144;
	else
		dlev = 128;

       	eicon_log(ccard, dlev, "idi_hdl: Ch%d: Ind=%x Id=%x Ch=%x MInd=%x MLen=%x Len=%x\n", chan->No,
	        ind->Ind,ind->IndId,ind->IndCh,ind->MInd,ind->MLength,ind->RBuffer.length);

	free_buff = 1;
	/* Signal Layer */
	if (chan->e.D3Id == ind->IndId) {
		idi_IndParse(ccard, chan, &message, ind->RBuffer.P, ind->RBuffer.length);
		switch(ind->Ind) {
			case HANGUP:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Hangup\n", chan->No);
		                while((skb2 = skb_dequeue(&chan->e.X))) {
					dev_kfree_skb(skb2);
				}
				spin_lock_irqsave(&eicon_lock, flags);
				chan->queued = 0;
				chan->pqueued = 0;
				chan->waitq = 0;
				chan->waitpq = 0;
				spin_unlock_irqrestore(&eicon_lock, flags);
				if (message.e_cau[0] & 0x7f) {
					cmd.driver = ccard->myid;
					cmd.arg = chan->No;
					sprintf(cmd.parm.num,"E%02x%02x", 
						chan->cause[0]&0x7f, message.e_cau[0]&0x7f); 
					cmd.command = ISDN_STAT_CAUSE;
					ccard->interface.statcallb(&cmd);
				}
				chan->cause[0] = 0; 
				if (((chan->fsm_state == EICON_STATE_ACTIVE) ||
				    (chan->fsm_state == EICON_STATE_WMCONN)) ||
				    ((chan->l2prot == ISDN_PROTO_L2_FAX) &&
				    (chan->fsm_state == EICON_STATE_OBWAIT))) {
					chan->fsm_state = EICON_STATE_NULL;
				} else {
					if (chan->e.B2Id)
						idi_do_req(ccard, chan, REMOVE, 1);
					chan->statectrl &= ~WAITING_FOR_HANGUP;
					chan->statectrl &= ~IN_HOLD;
					if (chan->statectrl & HAVE_CONN_REQ) {
						eicon_log(ccard, 32, "idi_req: Ch%d: queueing delayed conn_req\n", chan->No);
						chan->statectrl &= ~HAVE_CONN_REQ;
						if ((chan->tskb1) && (chan->tskb2)) {
							skb_queue_tail(&chan->e.X, chan->tskb1);
							skb_queue_tail(&ccard->sndq, chan->tskb2); 
							eicon_schedule_tx(ccard);
						}
						chan->tskb1 = NULL;
						chan->tskb2 = NULL;
					} else {
						chan->fsm_state = EICON_STATE_NULL;
						cmd.driver = ccard->myid;
						cmd.arg = chan->No;
						cmd.command = ISDN_STAT_DHUP;
						ccard->interface.statcallb(&cmd);
						eicon_idi_listen_req(ccard, chan);
#ifdef CONFIG_ISDN_TTY_FAX
						chan->fax = 0;
#endif
					}
				}
				break;
			case INDICATE_IND:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Indicate_Ind\n", chan->No);
				if (chan->fsm_state != EICON_STATE_LISTEN) {
					eicon_log(ccard, 1, "idi_err: Ch%d: Incoming call on wrong state (%d).\n",
						chan->No, chan->fsm_state);
					idi_do_req(ccard, chan, HANGUP, 0);
					break;
				}
				chan->fsm_state = EICON_STATE_ICALL;
				idi_bc2si(message.bc, message.hlc, message.sin, &chan->si1, &chan->si2);
				strcpy(chan->cpn, message.cpn + 1);
				strcpy(chan->oad, message.oad);
				strcpy(chan->dsa, message.dsa);
				strcpy(chan->osa, message.osa);
				chan->plan = message.plan;
				chan->screen = message.screen;
				try_stat_icall_again: 
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_ICALL;
				cmd.arg = chan->No;
				cmd.parm.setup.si1 = chan->si1;
				cmd.parm.setup.si2 = chan->si2;
				strcpy(tnum, chan->cpn);
				if (strlen(chan->dsa)) {
					strcat(tnum, ".");
					strcat(tnum, chan->dsa);
				}
				tnum[ISDN_MSNLEN - 1] = 0;
				strcpy(cmd.parm.setup.eazmsn, tnum);
				strcpy(tnum, chan->oad);
				if (strlen(chan->osa)) {
					strcat(tnum, ".");
					strcat(tnum, chan->osa);
				}
				tnum[ISDN_MSNLEN - 1] = 0;
				strcpy(cmd.parm.setup.phone, tnum);
				cmd.parm.setup.plan = chan->plan;
				cmd.parm.setup.screen = chan->screen;
				tmp = ccard->interface.statcallb(&cmd);
				switch(tmp) {
					case 0: /* no user responding */
						idi_do_req(ccard, chan, HANGUP, 0);
						chan->fsm_state = EICON_STATE_NULL;
						break;
					case 1: /* alert */
						eicon_log(ccard, 8, "idi_req: Ch%d: Call Alert\n", chan->No);
						if ((chan->fsm_state == EICON_STATE_ICALL) || (chan->fsm_state == EICON_STATE_ICALLW)) {
							chan->fsm_state = EICON_STATE_ICALL;
							idi_do_req(ccard, chan, CALL_ALERT, 0);
						}
						break;
					case 2: /* reject */
						eicon_log(ccard, 8, "idi_req: Ch%d: Call Reject\n", chan->No);
						idi_do_req(ccard, chan, REJECT, 0);
						break;
					case 3: /* incomplete number */
						eicon_log(ccard, 8, "idi_req: Ch%d: Incomplete Number\n", chan->No);
						chan->fsm_state = EICON_STATE_ICALLW;
						break;
				}
				break;
			case INFO_IND:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Info_Ind\n", chan->No);
				if ((chan->fsm_state == EICON_STATE_ICALLW) &&
				    (message.cpn[0])) {
					strcat(chan->cpn, message.cpn + 1);
					goto try_stat_icall_again;
				}
				break;
			case CALL_IND:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Call_Ind\n", chan->No);
				if ((chan->fsm_state == EICON_STATE_ICALL) || (chan->fsm_state == EICON_STATE_IWAIT)) {
					chan->fsm_state = EICON_STATE_IBWAIT;
					cmd.driver = ccard->myid;
					cmd.command = ISDN_STAT_DCONN;
					cmd.arg = chan->No;
					ccard->interface.statcallb(&cmd);
					switch(chan->l2prot) {
						case ISDN_PROTO_L2_FAX:
#ifdef CONFIG_ISDN_TTY_FAX
							if (chan->fax)
								chan->fax->phase = ISDN_FAX_PHASE_A;
#endif
							break;
						case ISDN_PROTO_L2_MODEM:
							/* do nothing, wait for connect */
							break;
						case ISDN_PROTO_L2_V11096:
						case ISDN_PROTO_L2_V11019:
						case ISDN_PROTO_L2_V11038:
						case ISDN_PROTO_L2_TRANS:
							idi_do_req(ccard, chan, N_CONNECT, 1);
							break;
						default:;
							/* On most incoming calls we use automatic connect */
							/* idi_do_req(ccard, chan, N_CONNECT, 1); */
					}
				} else {
					if (chan->fsm_state != EICON_STATE_ACTIVE)
						idi_hangup(ccard, chan);
				}
				break;
			case CALL_CON:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Call_Con\n", chan->No);
				if (chan->fsm_state == EICON_STATE_OCALL) {
					/* check if old NetID has been removed */
					if (chan->e.B2Id) {
						eicon_log(ccard, 1, "eicon: Ch%d: old net_id %x still exist, removing.\n",
							chan->No, chan->e.B2Id);
						idi_do_req(ccard, chan, REMOVE, 1);
					}
#ifdef CONFIG_ISDN_TTY_FAX
					if (chan->l2prot == ISDN_PROTO_L2_FAX) {
						if (chan->fax) {
							chan->fax->phase = ISDN_FAX_PHASE_A;
						} else {
							eicon_log(ccard, 1, "idi_ind: Call_Con with NULL fax struct, ERROR\n");
							idi_hangup(ccard, chan);
							break;
						}
					}
#endif
					chan->fsm_state = EICON_STATE_OBWAIT;
					cmd.driver = ccard->myid;
					cmd.command = ISDN_STAT_DCONN;
					cmd.arg = chan->No;
					ccard->interface.statcallb(&cmd);

					idi_do_req(ccard, chan, ASSIGN, 1); 
					idi_do_req(ccard, chan, N_CONNECT, 1);
				} else
					idi_hangup(ccard, chan);
				break;
			case AOC_IND:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Advice of Charge\n", chan->No);
				break;
			case CALL_HOLD_ACK:
				chan->statectrl |= IN_HOLD;
				eicon_log(ccard, 8, "idi_ind: Ch%d: Call Hold Ack\n", chan->No);
				break;
			case SUSPEND_REJ:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Suspend Rejected\n", chan->No);
				break;
			case SUSPEND:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Suspend Ack\n", chan->No);
				break;
			case RESUME:
				eicon_log(ccard, 8, "idi_ind: Ch%d: Resume Ack\n", chan->No);
				break;
			default:
				eicon_log(ccard, 8, "idi_ind: Ch%d: UNHANDLED SigIndication 0x%02x\n", chan->No, ind->Ind);
		}
	}
	/* Network Layer */
	else if (chan->e.B2Id == ind->IndId) {

		if (chan->No == ccard->nchannels) {
			/* Management Indication */
			if (ind->Ind == 0x04) { /* Trace_Ind */
				eicon_parse_trace(ccard, ind->RBuffer.P, ind->RBuffer.length);
			} else {
				idi_IndParse(ccard, chan, &message, ind->RBuffer.P, ind->RBuffer.length);
				chan->fsm_state = 1;
			}
		} 
		else
		switch(ind->Ind) {
			case N_CONNECT_ACK:
				eicon_log(ccard, 16, "idi_ind: Ch%d: N_Connect_Ack\n", chan->No);
				if (chan->l2prot == ISDN_PROTO_L2_MODEM) {
					chan->fsm_state = EICON_STATE_WMCONN;
					break;
				}
				if (chan->l2prot == ISDN_PROTO_L2_FAX) {
#ifdef CONFIG_ISDN_TTY_FAX
					chan->fsm_state = EICON_STATE_ACTIVE;
					idi_parse_edata(ccard, chan, ind->RBuffer.P, ind->RBuffer.length);
					if (chan->fax) {
						if (chan->fax->phase == ISDN_FAX_PHASE_B) {
							idi_fax_send_header(ccard, chan, 2);
							cmd.driver = ccard->myid;
							cmd.command = ISDN_STAT_FAXIND;
							cmd.arg = chan->No;
							chan->fax->r_code = ISDN_TTY_FAX_DCS;
							ccard->interface.statcallb(&cmd);
						}
					}
					else {
						eicon_log(ccard, 1, "idi_ind: N_Connect_Ack with NULL fax struct, ERROR\n");
					}
#endif
					break;
				}
				chan->fsm_state = EICON_STATE_ACTIVE;
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_BCONN;
				cmd.arg = chan->No;
				strcpy(cmd.parm.num, "64000");
				ccard->interface.statcallb(&cmd);
				break; 
			case N_CONNECT:
				eicon_log(ccard, 16,"idi_ind: Ch%d: N_Connect\n", chan->No);
				chan->e.IndCh = ind->IndCh;
				if (chan->e.B2Id) idi_do_req(ccard, chan, N_CONNECT_ACK, 1);
				if (chan->l2prot == ISDN_PROTO_L2_FAX) {
					break;
				}
				if (chan->l2prot == ISDN_PROTO_L2_MODEM) {
					chan->fsm_state = EICON_STATE_WMCONN;
					break;
				}
				chan->fsm_state = EICON_STATE_ACTIVE;
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_BCONN;
				cmd.arg = chan->No;
				strcpy(cmd.parm.num, "64000");
				ccard->interface.statcallb(&cmd);
				break; 
			case N_DISC:
				eicon_log(ccard, 16, "idi_ind: Ch%d: N_Disc\n", chan->No);
				if (chan->e.B2Id) {
		                	while((skb2 = skb_dequeue(&chan->e.X))) {
						dev_kfree_skb(skb2);
					}
					idi_do_req(ccard, chan, N_DISC_ACK, 1);
					idi_do_req(ccard, chan, REMOVE, 1);
				}
#ifdef CONFIG_ISDN_TTY_FAX
				if ((chan->l2prot == ISDN_PROTO_L2_FAX) && (chan->fax)){
					idi_parse_edata(ccard, chan, ind->RBuffer.P, ind->RBuffer.length);
					idi_fax_hangup(ccard, chan);
				}
#endif
				chan->e.IndCh = 0;
				spin_lock_irqsave(&eicon_lock, flags);
				chan->queued = 0;
				chan->pqueued = 0;
				chan->waitq = 0;
				chan->waitpq = 0;
				spin_unlock_irqrestore(&eicon_lock, flags);
				if (!(chan->statectrl & IN_HOLD)) {
					idi_do_req(ccard, chan, HANGUP, 0);
				}
				if (chan->fsm_state == EICON_STATE_ACTIVE) {
					cmd.driver = ccard->myid;
					cmd.command = ISDN_STAT_BHUP;
					cmd.arg = chan->No;
					ccard->interface.statcallb(&cmd);
					chan->fsm_state = EICON_STATE_NULL;
					if (!(chan->statectrl & IN_HOLD)) {
						chan->statectrl |= WAITING_FOR_HANGUP;
					}
				}
#ifdef CONFIG_ISDN_TTY_FAX
				chan->fax = 0;
#endif
				break; 
			case N_DISC_ACK:
				eicon_log(ccard, 16, "idi_ind: Ch%d: N_Disc_Ack\n", chan->No);
#ifdef CONFIG_ISDN_TTY_FAX
				if (chan->l2prot == ISDN_PROTO_L2_FAX) {
					idi_parse_edata(ccard, chan, ind->RBuffer.P, ind->RBuffer.length);
					idi_fax_hangup(ccard, chan);
				}
#endif
				break; 
			case N_DATA_ACK:
				eicon_log(ccard, 128, "idi_ind: Ch%d: N_Data_Ack\n", chan->No);
				break;
			case N_DATA:
				skb_pull(skb, sizeof(eicon_IND) - 1);
				eicon_log(ccard, 128, "idi_rcv: Ch%d: %d bytes\n", chan->No, skb->len);
				if (chan->l2prot == ISDN_PROTO_L2_FAX) {
#ifdef CONFIG_ISDN_TTY_FAX
					idi_faxdata_rcv(ccard, chan, skb);
#endif
				} else {
					ccard->interface.rcvcallb_skb(ccard->myid, chan->No, skb);
					free_buff = 0; 
				}
				break; 
			case N_UDATA:
				idi_parse_udata(ccard, chan, ind->RBuffer.P, ind->RBuffer.length);
				break; 
#ifdef CONFIG_ISDN_TTY_FAX
			case N_EDATA:
				idi_edata_action(ccard, chan, ind->RBuffer.P, ind->RBuffer.length);
				break; 
#endif
			default:
				eicon_log(ccard, 8, "idi_ind: Ch%d: UNHANDLED NetIndication 0x%02x\n", chan->No, ind->Ind);
		}
	}
	else {
		eicon_log(ccard, 1, "idi_ind: Ch%d: Ind is neither SIG nor NET !\n", chan->No);
	}
   if (free_buff)
	dev_kfree_skb(skb);
}

int
idi_handle_ack_ok(eicon_card *ccard, eicon_chan *chan, eicon_RC *ack)
{
	ulong flags;
	isdn_ctrl cmd;
	int tqueued = 0;
	int twaitpq = 0;

	if (ack->RcId != ((chan->e.ReqCh) ? chan->e.B2Id : chan->e.D3Id)) {
		/* I dont know why this happens, should not ! */
		/* just ignoring this RC */
		eicon_log(ccard, 16, "idi_ack: Ch%d: RcId %d not equal to last %d\n", chan->No, 
			ack->RcId, (chan->e.ReqCh) ? chan->e.B2Id : chan->e.D3Id);
		return 1;
	}

	/* Management Interface */	
	if (chan->No == ccard->nchannels) {
		/* Managementinterface: changing state */
		if (chan->e.Req != 0x02)
			chan->fsm_state = 1;
	}

	/* Remove an Id */
	if (chan->e.Req == REMOVE) {
		if (ack->Reference != chan->e.ref) {
			/* This should not happen anymore */
			eicon_log(ccard, 16, "idi_ack: Ch%d: Rc-Ref %d not equal to stored %d\n", chan->No,
				ack->Reference, chan->e.ref);
		}
		spin_lock_irqsave(&eicon_lock, flags);
		ccard->IdTable[ack->RcId] = NULL;
		if (!chan->e.ReqCh) 
			chan->e.D3Id = 0;
		else
			chan->e.B2Id = 0;
		spin_unlock_irqrestore(&eicon_lock, flags);
		eicon_log(ccard, 16, "idi_ack: Ch%d: Removed : Id=%x Ch=%d (%s)\n", chan->No,
			ack->RcId, ack->RcCh, (chan->e.ReqCh)? "Net":"Sig");
		return 1;
	}

	/* Signal layer */
	if (!chan->e.ReqCh) {
		eicon_log(ccard, 16, "idi_ack: Ch%d: RC OK Id=%x Ch=%d (ref:%d)\n", chan->No,
			ack->RcId, ack->RcCh, ack->Reference);
	} else {
	/* Network layer */
		switch(chan->e.Req & 0x0f) {
			case N_CONNECT:
				chan->e.IndCh = ack->RcCh;
				eicon_log(ccard, 16, "idi_ack: Ch%d: RC OK Id=%x Ch=%d (ref:%d)\n", chan->No,
					ack->RcId, ack->RcCh, ack->Reference);
				break;
			case N_MDATA:
			case N_DATA:
				tqueued = chan->queued;
				twaitpq = chan->waitpq;
				if ((chan->e.Req & 0x0f) == N_DATA) {
					spin_lock_irqsave(&eicon_lock, flags);
					chan->waitpq = 0;
					if(chan->pqueued)
						chan->pqueued--;
					spin_unlock_irqrestore(&eicon_lock, flags);
#ifdef CONFIG_ISDN_TTY_FAX
					if (chan->l2prot == ISDN_PROTO_L2_FAX) {
						if (((chan->queued - chan->waitq) < 1) &&
						    (chan->fax2.Eop)) {
							chan->fax2.Eop = 0;
							if (chan->fax) {
								cmd.driver = ccard->myid;
								cmd.command = ISDN_STAT_FAXIND;
								cmd.arg = chan->No;
								chan->fax->r_code = ISDN_TTY_FAX_SENT;
								ccard->interface.statcallb(&cmd);
							}
							else {
								eicon_log(ccard, 1, "idi_ack: Sent with NULL fax struct, ERROR\n");
							}
						}
					}
#endif
				}
				spin_lock_irqsave(&eicon_lock, flags);
				chan->queued -= chan->waitq;
				if (chan->queued < 0) chan->queued = 0;
				spin_unlock_irqrestore(&eicon_lock, flags);
				if (((chan->e.Req & 0x0f) == N_DATA) && (tqueued)) {
					cmd.driver = ccard->myid;
					cmd.command = ISDN_STAT_BSENT;
					cmd.arg = chan->No;
					cmd.parm.length = twaitpq;
					ccard->interface.statcallb(&cmd);
				}
				break;
			default:
				eicon_log(ccard, 16, "idi_ack: Ch%d: RC OK Id=%x Ch=%d (ref:%d)\n", chan->No,
					ack->RcId, ack->RcCh, ack->Reference);
		}
	}
	return 1;
}

void
idi_handle_ack(eicon_card *ccard, struct sk_buff *skb)
{
	int j;
	ulong flags;
        eicon_RC *ack = (eicon_RC *)skb->data;
	eicon_chan *chan;
	isdn_ctrl cmd;
	int dCh = -1;

	if (!ccard) {
		eicon_log(ccard, 1, "idi_err: Ch??: null card in handle_ack\n");
		dev_kfree_skb(skb);
		return;
	}

	spin_lock_irqsave(&eicon_lock, flags);
	if ((chan = ccard->IdTable[ack->RcId]) != NULL)
		dCh = chan->No;
	spin_unlock_irqrestore(&eicon_lock, flags);

	switch (ack->Rc) {
		case OK_FC:
		case N_FLOW_CONTROL:
		case ASSIGN_RC:
			eicon_log(ccard, 1, "idi_ack: Ch%d: unhandled RC 0x%x\n",
				dCh, ack->Rc);
			break;
		case READY_INT:
		case TIMER_INT:
			/* we do nothing here */
			break;

		case OK:
			if (!chan) {
				eicon_log(ccard, 1, "idi_ack: Ch%d: OK on chan without Id\n", dCh);
				break;
			}
			if (!idi_handle_ack_ok(ccard, chan, ack))
				chan = NULL;
			break;

		case ASSIGN_OK:
			if (chan) {
				eicon_log(ccard, 1, "idi_ack: Ch%d: ASSIGN-OK on chan already assigned (%x,%x)\n",
					chan->No, chan->e.D3Id, chan->e.B2Id);
			}
			spin_lock_irqsave(&eicon_lock, flags);
			for(j = 0; j < ccard->nchannels + 1; j++) {
				if ((ccard->bch[j].e.ref == ack->Reference) &&
					(ccard->bch[j].e.Req == ASSIGN)) {
					if (!ccard->bch[j].e.ReqCh) 
						ccard->bch[j].e.D3Id  = ack->RcId;
					else
						ccard->bch[j].e.B2Id  = ack->RcId;
					ccard->IdTable[ack->RcId] = &ccard->bch[j];
					chan = &ccard->bch[j];
					break;
				}
			}
			spin_unlock_irqrestore(&eicon_lock, flags);
			eicon_log(ccard, 16, "idi_ack: Ch%d: Id %x assigned (%s)\n", j, 
				ack->RcId, (ccard->bch[j].e.ReqCh)? "Net":"Sig");
			if (j > ccard->nchannels) {
				eicon_log(ccard, 24, "idi_ack: Ch??: ref %d not found for Id %d\n", 
					ack->Reference, ack->RcId);
			}
			break;

		case OUT_OF_RESOURCES:
		case UNKNOWN_COMMAND:
		case WRONG_COMMAND:
		case WRONG_ID:
		case ADAPTER_DEAD:
		case WRONG_CH:
		case UNKNOWN_IE:
		case WRONG_IE:
		default:
			if (!chan) {
				eicon_log(ccard, 1, "idi_ack: Ch%d: Not OK !! on chan without Id\n", dCh);
				break;
			} else
			switch (chan->e.Req) {
				case 12:	/* Alert */
					eicon_log(ccard, 2, "eicon_err: Ch%d: Alert Not OK : Rc=%d Id=%x Ch=%d\n",
						dCh, ack->Rc, ack->RcId, ack->RcCh);
					break;
				default:
					if (dCh != ccard->nchannels)
						eicon_log(ccard, 1, "eicon_err: Ch%d: Ack Not OK !!: Rc=%d Id=%x Ch=%d Req=%d\n",
							dCh, ack->Rc, ack->RcId, ack->RcCh, chan->e.Req);
			}
			if (dCh == ccard->nchannels) { /* Management */
				chan->fsm_state = 2;
				eicon_log(ccard, 8, "eicon_err: Ch%d: Ack Not OK !!: Rc=%d Id=%x Ch=%d Req=%d\n",
					dCh, ack->Rc, ack->RcId, ack->RcCh, chan->e.Req);
			} else if (dCh >= 0) {
					/* any other channel */
					/* card reports error: we hangup */
				idi_hangup(ccard, chan);
				cmd.driver = ccard->myid;
				cmd.command = ISDN_STAT_DHUP;
				cmd.arg = chan->No;
				ccard->interface.statcallb(&cmd);
			}
	}
	spin_lock_irqsave(&eicon_lock, flags);
	if (chan) {
		chan->e.ref = 0;
		chan->e.busy = 0;
	}
	spin_unlock_irqrestore(&eicon_lock, flags);
	dev_kfree_skb(skb);
	eicon_schedule_tx(ccard);
}

int
idi_send_data(eicon_card *card, eicon_chan *chan, int ack, struct sk_buff *skb, int que, int chk)
{
        struct sk_buff *xmit_skb;
        struct sk_buff *skb2;
        eicon_REQ *reqbuf;
        eicon_chan_ptr *chan2;
        int len, plen = 0, offset = 0;
	unsigned long flags;

	if ((!card) || (!chan)) {
		eicon_log(card, 1, "idi_err: Ch??: null card/chan in send_data\n");
		return -1;
	}

        if (chan->fsm_state != EICON_STATE_ACTIVE) {
		eicon_log(card, 1, "idi_snd: Ch%d: send bytes on state %d !\n", chan->No, chan->fsm_state);
                return -ENODEV;
	}

        len = skb->len;
	if (len > EICON_MAX_QUEUE)	/* too much for the shared memory */
		return -1;
        if (!len)
                return 0;

	if ((chk) && (chan->pqueued > 1))
		return 0;

	eicon_log(card, 128, "idi_snd: Ch%d: %d bytes (Pqueue=%d)\n",
		chan->No, len, chan->pqueued);

	spin_lock_irqsave(&eicon_lock, flags);
	while(offset < len) {

		plen = ((len - offset) > 270) ? 270 : len - offset;

	        xmit_skb = alloc_skb(plen + sizeof(eicon_REQ), GFP_ATOMIC);
        	skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

	        if ((!xmit_skb) || (!skb2)) {
			spin_unlock_irqrestore(&eicon_lock, flags);
        	        eicon_log(card, 1, "idi_err: Ch%d: alloc_skb failed in send_data()\n", chan->No);
			if (xmit_skb) 
				dev_kfree_skb(skb);
			if (skb2) 
				dev_kfree_skb(skb2);
                	return -ENOMEM;
	        }

	        chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
        	chan2->ptr = chan;

	        reqbuf = (eicon_REQ *)skb_put(xmit_skb, plen + sizeof(eicon_REQ));
		if ((len - offset) > 270) { 
		        reqbuf->Req = N_MDATA;
		} else {
		        reqbuf->Req = N_DATA;
			/* if (ack) reqbuf->Req |= N_D_BIT; */
		}	
        	reqbuf->ReqCh = chan->e.IndCh;
	        reqbuf->ReqId = 1;
		memcpy(&reqbuf->XBuffer.P, skb->data + offset, plen);
		reqbuf->XBuffer.length = plen;
		reqbuf->Reference = 1; /* Net Entity */

		skb_queue_tail(&chan->e.X, xmit_skb);
		skb_queue_tail(&card->sndq, skb2); 

		offset += plen;
	}
	if (que) {
		chan->queued += len;
		chan->pqueued++;
	}
	spin_unlock_irqrestore(&eicon_lock, flags);
	eicon_schedule_tx(card);
        dev_kfree_skb(skb);
        return len;
}


int
eicon_idi_manage_assign(eicon_card *card)
{
        struct sk_buff *skb;
        struct sk_buff *skb2;
        eicon_REQ  *reqbuf;
        eicon_chan     *chan;
        eicon_chan_ptr *chan2;

        chan = &(card->bch[card->nchannels]);

        skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC);
        skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

        if ((!skb) || (!skb2)) {
		eicon_log(card, 1, "idi_err: alloc_skb failed in manage_assign()\n");
		if (skb) 
			dev_kfree_skb(skb);
		if (skb2) 
			dev_kfree_skb(skb2);
                return -ENOMEM;
        }

        chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
        chan2->ptr = chan;

        reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ));

        reqbuf->XBuffer.P[0] = 0;
        reqbuf->Req = ASSIGN;
        reqbuf->ReqCh = 0;
        reqbuf->ReqId = MAN_ID;
        reqbuf->XBuffer.length = 1;
        reqbuf->Reference = 2; /* Man Entity */

        skb_queue_tail(&chan->e.X, skb);
        skb_queue_tail(&card->sndq, skb2);
        eicon_schedule_tx(card);
        return(0);
}


int
eicon_idi_manage_remove(eicon_card *card)
{
        struct sk_buff *skb;
        struct sk_buff *skb2;
        eicon_REQ  *reqbuf;
        eicon_chan     *chan;
        eicon_chan_ptr *chan2;

        chan = &(card->bch[card->nchannels]);

        skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC);
        skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

        if ((!skb) || (!skb2)) {
               	eicon_log(card, 1, "idi_err: alloc_skb failed in manage_remove()\n");
		if (skb) 
			dev_kfree_skb(skb);
		if (skb2) 
			dev_kfree_skb(skb2);
                return -ENOMEM;
        }

        chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
        chan2->ptr = chan;

        reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ));

        reqbuf->Req = REMOVE;
        reqbuf->ReqCh = 0;
        reqbuf->ReqId = 1;
        reqbuf->XBuffer.length = 0;
        reqbuf->Reference = 2; /* Man Entity */

        skb_queue_tail(&chan->e.X, skb);
        skb_queue_tail(&card->sndq, skb2);
        eicon_schedule_tx(card);
        return(0);
}


int
eicon_idi_manage(eicon_card *card, eicon_manifbuf *mb)
{
	int l = 0;
	int ret = 0;
	int timeout;
	int i;
        struct sk_buff *skb;
        struct sk_buff *skb2;
        eicon_REQ  *reqbuf;
        eicon_chan     *chan;
        eicon_chan_ptr *chan2;

        chan = &(card->bch[card->nchannels]);

	if (!(chan->e.D3Id)) {
		chan->e.D3Id = 1;
		while((skb2 = skb_dequeue(&chan->e.X)))
			dev_kfree_skb(skb2);
		chan->e.busy = 0;
 
		if ((ret = eicon_idi_manage_assign(card))) {
			chan->e.D3Id = 0;
			return(ret); 
		}

	        timeout = jiffies + 50;
        	while (timeout > jiffies) {
	                if (chan->e.B2Id) break;
        	        SLEEP(10);
	        }
	        if (!chan->e.B2Id) {
			chan->e.D3Id = 0;
			return -EIO;
		}
	}

	chan->fsm_state = 0;

	if (!(manbuf = kmalloc(sizeof(eicon_manifbuf), GFP_KERNEL))) {
               	eicon_log(card, 1, "idi_err: alloc_manifbuf failed\n");
		return -ENOMEM;
	}
	if (copy_from_user(manbuf, mb, sizeof(eicon_manifbuf))) {
		kfree(manbuf);
		return -EFAULT;
	}

        skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC);
        skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC);

        if ((!skb) || (!skb2)) {
               	eicon_log(card, 1, "idi_err_manif: alloc_skb failed in manage()\n");
		if (skb) 
			dev_kfree_skb(skb);
		if (skb2) 
			dev_kfree_skb(skb2);
		kfree(manbuf);
                return -ENOMEM;
        }

        chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr));
        chan2->ptr = chan;

        reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ));

        reqbuf->XBuffer.P[l++] = ESC;
        reqbuf->XBuffer.P[l++] = 6;
        reqbuf->XBuffer.P[l++] = 0x80;
	for (i = 0; i < manbuf->length[0]; i++)
	        reqbuf->XBuffer.P[l++] = manbuf->data[i];
        reqbuf->XBuffer.P[1] = manbuf->length[0] + 1;

        reqbuf->XBuffer.P[l++] = 0;
        reqbuf->Req = (manbuf->count) ? manbuf->count : MAN_READ;
        reqbuf->ReqCh = 0;
        reqbuf->ReqId = 1;
        reqbuf->XBuffer.length = l;
        reqbuf->Reference = 2; /* Man Entity */

        skb_queue_tail(&chan->e.X, skb);
        skb_queue_tail(&card->sndq, skb2);

	manbuf->count = 0;
	manbuf->pos = 0;

        eicon_schedule_tx(card);

        timeout = jiffies + 50;
        while (timeout > jiffies) {
                if (chan->fsm_state) break;
                SLEEP(10);
        }
        if ((!chan->fsm_state) || (chan->fsm_state == 2)) {
		kfree(manbuf);
		return -EIO;
	}
	if (copy_to_user(mb, manbuf, sizeof(eicon_manifbuf))) {
		kfree(manbuf);
		return -EFAULT;
	}

	kfree(manbuf);
  return(0);
}
