/*
 * Copyright 2000 Carsten Paeth (calle@calle.in-berlin.de)
 * Copyright 2000 AVM GmbH Berlin (info@avm.de)
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the Lesser GNU General Public License
 *  as published by the Free Software Foundation; either version
 *  2 of the License, or (at your option) any later version.
 *
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/capi.h>
#include <linux/kernelcapi.h>
#include <linux/isdn/capicmd.h>
#include <linux/isdn/capiutil.h>
#include <linux/isdn/capiconn.h>

static char *revision = "$Revision: 2.0 $";

static _cmsg cmdcmsg;
static _cmsg cmsg;

MODULE_DESCRIPTION("CAPI4Linux: CAPI connections inside kernel");
MODULE_AUTHOR("Carsten Paeth");
MODULE_LICENSE("LGPL");

/* -------- defines -------------------------------------------------- */

#ifndef CAPI_MAXDATAWINDOW
#define	CAPI_MAXDATAWINDOW	8
#endif

/* -------- util functions ------------------------------------------- */

static inline int capimsg_addu8(void *m, int off, __u8 val)
{
	((__u8 *)m)[off] = val;
	return off+1;
}

static inline int capimsg_addu16(void *m, int off, __u16 val)
{
	((__u8 *)m)[off] = val & 0xff;
	((__u8 *)m)[off+1] = (val >> 8) & 0xff;
	return off+2;
}

static inline int capimsg_addu32(void *m, int off, __u32 val)
{
	((__u8 *)m)[off] = val & 0xff;
	((__u8 *)m)[off+1] = (val >> 8) & 0xff;
	((__u8 *)m)[off+2] = (val >> 16) & 0xff;
	((__u8 *)m)[off+3] = (val >> 24) & 0xff;
	return off+4;
}

static inline int capimsg_addcstruct(void *m, int off, __u8 len, __u8 *val)
{
   if (len == 0 || val == 0) return capimsg_addu8(m, off, 0);
   off = capimsg_addu8(m, off, len);
   memcpy(m+off, val, len);
   return off+len;
}

/* -------- type definitions ----------------------------------------- */

struct capiconn_context {
	struct capiconn_context *next;

    struct capi20_appl *appl;
	unsigned appid;
	struct capiconn_callbacks *cb;

	int ncontr;
	struct capi_contr *contr_list;

        /* statistic */
	unsigned long nrecvctlpkt;
	unsigned long nrecvdatapkt;
	unsigned long nsentctlpkt;
	unsigned long nsentdatapkt;
};

struct capi_contr {

	struct capi_contr *next;
	struct capiconn_context *ctx;

	unsigned contrnr;
	struct capi_contrinfo cinfo;
	unsigned ddilen;

	/*
	 * LISTEN state
	 */
	int state;
	__u32 infomask;
	__u32 cipmask;
	__u32 cipmask2;

	/*
	 * ID of capi message sent
	 */
	__u16 msgid;

	/*
	 * B-Channels
	 */
	int nbchan;
	struct capi_connection {
		struct capi_connection *next;
		struct capi_contr *contr;
		struct capiconn_context *ctx;

		struct capi_conninfo conninfo;

		unsigned incoming:1,
			 disconnecting:1,
			 localdisconnect:1,
			 delay_connect_active_resp:1,
			 connect_active_resp_saved:1,
			 with_early_b3:1,
			 hold_or_unhold_in_progress:1,
             hold_state_wanted:1;

        unsigned long hold_tone_want;
        unsigned long hold_tone_sent;

		__u16 disconnectreason;
		__u16 disconnectreason_b3;

		__u32 plci;
		__u32 ncci;	/* ncci for CONNECT_ACTIVE_IND */
		__u16 msgid;	/* to identfy CONNECT_CONF */
		__u8 saved_connect_active_resp[180];
		int state;
		struct capi_ncci {
			struct capi_connection *plcip;
			struct capiconn_context *ctx;
			__u32 ncci;
			__u16 msgid;	/* to identfy CONNECT_B3_CONF */
			int state;
			int oldstate;
			/* */
			__u16 datahandle;
			struct ncci_datahandle_queue {
			    struct ncci_datahandle_queue *next;
			    __u16                        datahandle;
			    unsigned char                *data;
				unsigned                      len;
			} *ackqueue;
			int ackqueuelen;
		} *nccip;
	    void *userdata;
	} *connections;
};

typedef struct capi_ncci capi_ncci;
typedef struct capi_contr capi_contr;
typedef struct ncci_datahandle_queue ncci_datahandle_queue;

/* -------- forward delarations -------------------------------------- */

static int send_message(capi_contr * card, _cmsg * cmsg);
static int send_onhold(capi_connection *plcip, unsigned long tone);
static int send_offhold(capi_connection *plcip);

/* -------- data definitions ----------------------------------------- */

capiconn_context *context_list = 0;

/* -------- version -------------------------------------------------- */

char *capiconn_version(void)
{
	static char retbuf[256];
	char *p;

	if ((p = strchr(revision, ':'))) {
		strncpy(retbuf, p + 1, sizeof(retbuf));
		p = strchr(retbuf, '$');
		*p = 0;
	}
	return retbuf;
}

EXPORT_SYMBOL(capiconn_version);

/* -------- context handling ----------------------------------------- */

capiconn_context *
capiconn_getcontext(struct capi20_appl *appl, capiconn_callbacks *cb)
{
	capiconn_context *ctx;

	if ((ctx = kmalloc(sizeof(capiconn_context), GFP_ATOMIC)) == 0)
		return 0;
	memset(ctx, 0, sizeof(capiconn_context));

	ctx->appl = appl;
	ctx->appid = appl->applid;
	ctx->cb = cb;
	ctx->next = context_list;
	context_list = ctx;
	return ctx;
};

EXPORT_SYMBOL(capiconn_getcontext);

static void free_all_cards(capiconn_context *ctx)
{
}

int
capiconn_freecontext(capiconn_context *ctx)
{
	capiconn_context **pp;
	for (pp = &context_list; *pp; pp = &(*pp)->next) {
		if (*pp == ctx) {
			*pp = (*pp)->next;
			free_all_cards(ctx);
			kfree(ctx);
			return 0;
		}
	}
	return -1;
}

EXPORT_SYMBOL(capiconn_freecontext);

static inline capiconn_context *find_context(unsigned appid)
{
	capiconn_context *p;
	for (p = context_list; p; p = p->next)
		if (p->appid == appid)
			return p;
	return 0;
}

int
capiconn_addcontr(capiconn_context *ctx, unsigned contr, capi_contrinfo *cinfo)
{
	capi_contr *card;
	__u8 fparam[256];
	__u32 mask;
	int off;

	if (!(card = (capi_contr *)kmalloc(sizeof(capi_contr), GFP_ATOMIC)))
		return CAPICONN_NO_MEMORY;
	memset(card, 0, sizeof(capi_contr));
	card->contrnr = contr;
	card->cinfo = *cinfo;
	card->ctx = ctx;
	if (card->cinfo.ddi)
		card->ddilen = strlen(card->cinfo.ddi);
	card->next = ctx->contr_list;
	ctx->contr_list = card;
	ctx->ncontr++;

	mask = 0;
    mask |= 1<<0;    /* 0: Hold & Retrieve */
    mask |= 1<<2;    /* 2: ECT Notification */
	off = 1;
	off = capimsg_addu16(fparam, off, 0x0001); /* Listen */
    off = capimsg_addcstruct(fparam, off, sizeof(__u32), (__u8 *)&mask);
	fparam[0] = off-1;
    capi_fill_FACILITY_REQ(&cmdcmsg, ctx->appid, card->msgid++,
			               card->contrnr, 3,
						   fparam);
	return send_message(card, &cmdcmsg);
}

EXPORT_SYMBOL(capiconn_addcontr);

/* ------------------------------------------------------------------- */

static capi_contr *findcontrbynumber(capiconn_context *ctx, unsigned contr)
{
	capi_contr *p;

	for (p = ctx->contr_list; p; p = p->next)
		if (p->contrnr == contr)
			break;
	return p;
}

/* -------- plci management ------------------------------------------ */

static capi_connection *new_plci(capi_contr * card, int incoming)
{
	capiconn_context *ctx = card->ctx;
	capi_connection *plcip;

	plcip = (capi_connection *)kmalloc(sizeof(capi_connection), GFP_ATOMIC);

	if (plcip == 0)
		return 0;

	memset(plcip, 0, sizeof(capi_connection));
	plcip->contr = card;
	plcip->ctx = ctx;
	plcip->incoming = incoming;
	plcip->state = ST_PLCI_NONE;
	plcip->plci = 0;
	plcip->msgid = 0;
	plcip->next = card->connections;
	card->connections = plcip;

	return plcip;
}

static capi_connection *find_plci_by_plci(capi_contr * card, __u32 plci)
{
	capi_connection *p;
	for (p = card->connections; p; p = p->next)
		if (p->plci == plci)
			return p;
	return 0;
}

static capi_connection *find_plci_by_msgid(capi_contr * card, __u16 msgid)
{
	capi_connection *p;
	for (p = card->connections; p; p = p->next)
		if (p->msgid == msgid)
			return p;
	return 0;
}

static capi_connection *find_plci_by_ncci(capi_contr * card, __u32 ncci)
{
	capi_connection *p;
	for (p = card->connections; p; p = p->next)
		if (p->plci == (ncci & 0xffff))
			return p;
	return 0;
}

static void free_plci(capi_contr * card, capi_connection * plcip)
{
	capi_connection **pp;

	for (pp = &card->connections; *pp; pp = &(*pp)->next) {
		if (*pp == plcip) {
			*pp = (*pp)->next;
			kfree(plcip);
			return;
		}
	}
	printk(KERN_ERR "capiconn: free_plci %p (0x%x) not found, Huh?\n",
				plcip, plcip->plci);
}

/* -------- ncci management ------------------------------------------ */

static inline capi_ncci *new_ncci(capi_contr * card,
				     capi_connection * plcip,
				     __u32 ncci)
{
	capiconn_context *ctx = card->ctx;
	capi_ncci *nccip;

	nccip = (capi_ncci *)kmalloc(sizeof(capi_ncci), GFP_ATOMIC);

	if (nccip == 0)
		return 0;

	memset(nccip, 0, sizeof(capi_ncci));
	nccip->ctx = ctx;
	nccip->ncci = ncci;
	nccip->state = ST_NCCI_NONE;
	nccip->plcip = plcip;
	nccip->datahandle = 0;
	plcip->nccip = nccip;
	plcip->ncci = ncci;

	return nccip;
}

static inline capi_ncci *find_ncci(capi_contr * card, __u32 ncci)
{
	capi_connection *plcip;

	if ((plcip = find_plci_by_ncci(card, ncci)) == 0)
		return 0;

	return plcip->nccip;
}

static inline capi_ncci *find_ncci_by_msgid(capi_contr * card,
					       __u32 ncci, __u16 msgid)
{
	capi_connection *plcip;

	if ((plcip = find_plci_by_ncci(card, ncci)) == 0)
		return 0;

	return plcip->nccip;
}

static void free_ncci(capi_contr * card, capi_ncci *nccip)
{
	nccip->plcip->nccip = 0;
	kfree(nccip);
}

/* ------------------------------------------------------------------- */

static void clr_conninfo0(capiconn_context *ctx, capi_conninfo *p)
{
	if (p->llc) {
		kfree(p->llc);
		p->llc = 0;
	}	
}

static void clr_conninfo1(capiconn_context *ctx, capi_conninfo *p)
{
	if (p->callednumber) {
		kfree(p->callednumber);
		p->callednumber = 0;
	}
	if (p->calledsub) {
		kfree(p->calledsub);
		p->calledsub = 0;
	}
	if (p->callingnumber) {
		kfree(p->callingnumber);
		p->callingnumber = 0;
	}
	if (p->callingsub) {
		kfree(p->callingsub);
		p->callingsub = 0;
	}
}

static void clr_conninfo2(capiconn_context *ctx, capi_conninfo *p)
{
	if (p->b1config) {
		kfree(p->b1config);
		p->b1config = 0;
	}
	if (p->b2config) {
		kfree(p->b2config);
		p->b2config = 0;
	}
	if (p->b3config) {
		kfree(p->b3config);
		p->b3config = 0;
	}
	if (p->bchaninfo) {
		kfree(p->bchaninfo);
		p->bchaninfo = 0;
	}
	if (p->ncpi) {
		kfree(p->ncpi);
		p->ncpi = 0;
	}
}

static void clr_conninfo(capiconn_context *ctx, capi_conninfo *p)
{
    clr_conninfo0(ctx, p);
	clr_conninfo1(ctx, p);
	clr_conninfo2(ctx, p);
}

static int set_conninfo1a(capiconn_context *ctx,
			capi_conninfo *p,
			__u16 cipvalue,
			char *callednumber,
			char *callingnumber)
{
	__u8 len;

	p->cipvalue = cipvalue;
	if ((p->callednumber = kmalloc(256, GFP_ATOMIC)) == 0)
		goto fail;
	if (callednumber) {
		len = (__u8)strlen(callednumber);
		if (callednumber[0] & 0x80) {
			memcpy(p->callednumber+1, callednumber, len);
			p->callednumber[0] = len;
	        p->callednumber[len+1] = 0;
		} else {
			memcpy(p->callednumber+2, callednumber, len);
			p->callednumber[0] = len+1;
			p->callednumber[1] = 0x81;
	        p->callednumber[len+2] = 0;
		}
	} else {
	    p->callednumber[0] = 0;
		p->callednumber[1] = 0;
	}
	if ((p->callingnumber = kmalloc(256, GFP_ATOMIC)) == 0)
		goto fail;
	if (callingnumber) {
		int is_int = 0;
		if (callingnumber[0] == '+') {
		   callingnumber++;
		   is_int = 1;
		} 
		len = (__u8)strlen(callingnumber);
		memcpy(p->callingnumber+3, callingnumber, len);
		p->callingnumber[0] = len+2;
		p->callingnumber[1] = is_int ? 0x11 : 0x00;
		p->callingnumber[2] = 0x80;
		p->callingnumber[len+3] = 0;
	} else {
		p->callingnumber[0] = 2;
		p->callingnumber[1] = 0x00;
	    p->callingnumber[2] = 0x80;
		p->callingnumber[3] = 0;
	}
	return 0;
fail:
	clr_conninfo1(ctx, p);
	return -1;
}

static _cstruct find_fac(_cstruct facarray, __u8 f)
{
   __u8 *end, *s;

   if (facarray == 0)
	  return 0;

   end = facarray + facarray[0];
   s = facarray + 1;

   while (s < end) {
	  if (s[0] == f) 
		 return s+1;
	  s += s[1];
   }
   return 0;
}


static int set_conninfo0(capiconn_context *ctx,
			capi_conninfo *p,
			_cstruct llc)
{
	__u8 len;
	
    if ((p->llc = kmalloc(256, GFP_ATOMIC)) == 0)
       goto fail;
    if (llc && llc[0] > 0) {
	   len = llc[0];
	   memcpy(p->llc, llc, len+1);
	   p->llc[len+1] = 0;
    } else {
       p->llc = 0;
    }
    return 0;
fail:
   clr_conninfo0(ctx, p);
   return -1;
}


static int set_conninfo1b(capiconn_context *ctx,
			capi_conninfo *p,
			__u16 cipvalue,
			_cstruct callednumber,
			_cstruct callingnumber,
			_cstruct facarray,
			_cstruct calledsub,
			_cstruct callingsub)
{
	_cstruct facp;
	__u8 len;

   p->cipvalue = cipvalue;

   if ((p->callednumber = kmalloc(256, GFP_ATOMIC)) == 0)
		goto fail;
   if (callednumber && callednumber[0] >= 2) {
	  len = callednumber[0];
	  memcpy(p->callednumber, callednumber, len+1);
	  p->callednumber[len+1] = 0;
	  if (calledsub) {
	     if ((p->calledsub = kmalloc(128, GFP_ATOMIC)) == 0)
		     goto fail;
	     len = calledsub[0];
	     memcpy(p->calledsub, calledsub, len+1);
	     p->calledsub[len+1] = 0;
      }
   } else if ((facp = find_fac(facarray, 0x70)) != 0) { /* CalledPartyNumber */
	  len = facp[0];
	  memcpy(p->callednumber, facp, len+1);
	  p->callednumber[len+1] = 0;
   } else if ((facp = find_fac(facarray, 0x2c)) != 0) { /* KeyPad */
	  len = facp[0];
	  p->callednumber[0] = len + 1;
      p->callednumber[1] = 0x81;
	  memcpy(p->callednumber+2, facp+1, len);
	  p->callednumber[len+2] = 0;
   } else {
	  p->callednumber[0] = 1;
      p->callednumber[1] = 0x81;
      p->callednumber[2] = 0;
   }
   if ((p->callingnumber = kmalloc(256, GFP_ATOMIC)) == 0)
      goto fail;
   if (callingnumber && callingnumber[0] >= 3) {
	  len = callingnumber[0];
	  memcpy(p->callingnumber, callingnumber, len+1);
	  p->callingnumber[len+1] = 0;
	  if (callingsub) {
		 if ((p->callingsub = kmalloc(128, GFP_ATOMIC)) == 0)
			 goto fail;
		 len = callingsub[0];
		 memcpy(p->callingsub, callingsub, len+1);
		 p->callingsub[len+1] = 0;
	  }
   } else {
	  p->callingnumber[0] = 2;
	  p->callingnumber[1] = 0x00;
	  p->callingnumber[2] = 0x80;
	  p->callingnumber[3] = 0;
   }
   return 0;
fail:
   clr_conninfo1(ctx, p);
   return -1;
}

static void extend_callednumber(capiconn_context *ctx, capi_conninfo *p,
				char *number, __u8 len)
{
	__u8 *curnumber = p->callednumber+2;
	__u8 clen = p->callednumber[0]-2;

	printk(KERN_DEBUG "capiconn: extend number %*.*s (len=%d)\n",
			(int)len, (int)len, number, len);

	if (len >= clen && memcmp(curnumber, number, clen) == 0) {
		memcpy(p->callednumber + 2, number, len);
		p->callednumber[0] = 2 + len;
	} else {
		memcpy(p->callednumber + p->callednumber[0], number, len);
		p->callednumber[0] += len;
	}
	p->callednumber[p->callednumber[0]+1] = 0;
	printk(KERN_DEBUG "capiconn: capiconn: extended to %s\n", p->callednumber+2);
}

static int set_conninfo2(capiconn_context *ctx,
			 capi_conninfo *p,
			 __u16 b1proto, __u16 b2proto, __u16 b3proto,
			_cstruct b1config, _cstruct b2config, _cstruct b3config,
			_cstruct bchaninfo, _cstruct ncpi)
{
	p->b1proto = b1proto;
	p->b2proto = b2proto;
	p->b3proto = b3proto;
	if (b1config) {
		if ((p->b1config = kmalloc(b1config[0]+1, GFP_ATOMIC)) == 0)
			goto fail;
		memcpy(p->b1config, b1config, b1config[0]+1);
	}
	if (b2config) {
		if ((p->b2config = kmalloc(b2config[0]+1, GFP_ATOMIC)) == 0)
			goto fail;
		memcpy(p->b2config, b2config, b2config[0]+1);
	}
	if (b3config) {
		if ((p->b3config = kmalloc(b3config[0]+1, GFP_ATOMIC)) == 0)
			goto fail;
		memcpy(p->b3config, b3config, b3config[0]+1);
	}
	if (bchaninfo) {
		if ((p->bchaninfo = kmalloc(bchaninfo[0]+1, GFP_ATOMIC)) == 0)
			goto fail;
		memcpy(p->bchaninfo, bchaninfo, bchaninfo[0]+1);
	}
	if (ncpi) {
		if ((p->ncpi = kmalloc(ncpi[0]+1, GFP_ATOMIC)) == 0)
			goto fail;
		memcpy(p->ncpi, ncpi, ncpi[0]+1);
	}
        return 0;
fail:
	clr_conninfo2(ctx, p);
	return -1;
}

capi_conninfo *capiconn_getinfo(capi_connection *p)
{
	p->conninfo.appid = p->ctx->appid;
	p->conninfo.plci = p->plci;
	p->conninfo.plci_state = p->state;
	p->conninfo.ncci = p->ncci;
	p->conninfo.ncci_state = p->nccip ? p->nccip->state : ST_NCCI_NONE;
	p->conninfo.ncci_qlen = p->nccip ? p->nccip->ackqueuelen : 0;
	p->conninfo.isincoming = p->incoming ? 1 : 0;
	p->conninfo.disconnect_was_local = p->localdisconnect ? 1 : 0;
	p->conninfo.disconnectreason = p->disconnectreason;
	p->conninfo.disconnectreason_b3 = p->disconnectreason_b3;
	return &p->conninfo;
}
EXPORT_SYMBOL(capiconn_getinfo);

/* ------------------------------------------------------------------- */

static int capi_add_ack(capi_ncci *nccip,
			__u16 datahandle,
			unsigned char *data,
			unsigned len)
{
	ncci_datahandle_queue *n, **pp;

	if (nccip->ackqueuelen >= CAPI_MAXDATAWINDOW)
		return 1;
	n = (ncci_datahandle_queue *)
		kmalloc(sizeof(ncci_datahandle_queue), GFP_ATOMIC);
	if (!n) {
	   printk(KERN_ERR "capiconn: kmalloc ncci_datahandle failed\n");
	   return -1;
	}
	n->next = 0;
	n->datahandle = datahandle;
	n->data = data;
	n->len = len;
	for (pp = &nccip->ackqueue; *pp; pp = &(*pp)->next) ;
	*pp = n;
	nccip->ackqueuelen++;
	return 0;
}

static unsigned char *capi_del_ack(capi_ncci *nccip, __u16 datahandle, unsigned *lenp)
{
	ncci_datahandle_queue **pp, *p;
	unsigned char *data;

	for (pp = &nccip->ackqueue; *pp; pp = &(*pp)->next) {
 		if ((*pp)->datahandle == datahandle) {
			p = *pp;
			data = p->data;
			if (lenp) *lenp = p->len;
			*pp = (*pp)->next;
		        kfree(p);
			nccip->ackqueuelen--;
			return data;
		}
	}
    printk(KERN_ERR "capiconn: datahandle %u not found\n", datahandle);
	return 0;
}

/* -------- convert and send capi message ---------------------------- */

static int send_message(capi_contr * card, _cmsg * cmsg)
{
	capiconn_context *ctx = card->ctx;
	capiconn_callbacks *cb = ctx->cb;
    struct sk_buff *skb;
	__u16 mlen;

    capi_cmsg2message(cmsg, cmsg->buf);
	mlen = CAPIMSG_LEN(cmsg->buf);
    if ((skb = alloc_skb(mlen, GFP_ATOMIC)) == 0)
       return CAPICONN_NO_MEMORY;
    memcpy(skb_put(skb, mlen), cmsg->buf, mlen);
	(*cb->capi_put_message) (ctx->appl, skb);
	ctx->nsentctlpkt++;
	return CAPICONN_OK;
}

/* -------- state machine -------------------------------------------- */

struct listenstatechange {
	int actstate;
	int nextstate;
	int event;
};

static struct listenstatechange listentable[] =
{
  {ST_LISTEN_NONE, ST_LISTEN_WAIT_CONF, EV_LISTEN_REQ},
  {ST_LISTEN_ACTIVE, ST_LISTEN_ACTIVE_WAIT_CONF, EV_LISTEN_REQ},
  {ST_LISTEN_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_ERROR},
  {ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_ERROR},
  {ST_LISTEN_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_EMPTY},
  {ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_NONE, EV_LISTEN_CONF_EMPTY},
  {ST_LISTEN_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_OK},
  {ST_LISTEN_ACTIVE_WAIT_CONF, ST_LISTEN_ACTIVE, EV_LISTEN_CONF_OK},
  { 0, 0, 0 },
};

static void listen_change_state(capi_contr * card, int event)
{
	struct listenstatechange *p = listentable;

	while (p->event) {
		if (card->state == p->actstate && p->event == event) {
			printk(KERN_DEBUG "capiconn: controller %d: listen_change_state %d -> %d\n",
			       card->contrnr, card->state, p->nextstate);
			card->state = p->nextstate;
			return;
		}
		p++;
	}
	printk(KERN_ERR "capiconn: controller %d: listen_change_state state=%d event=%d ????\n",
	       card->contrnr, card->state, event);
}

/* ------------------------------------------------------------------ */

static void p0(capi_contr * card, capi_connection * plcip)
{
	capiconn_context *ctx = card->ctx;
	capiconn_callbacks *cb = ctx->cb;
	if (*cb->disconnected)
	   (*cb->disconnected)(plcip,
			       plcip->localdisconnect,
			       plcip->disconnectreason,
			       plcip->disconnectreason_b3);
	clr_conninfo(ctx, &plcip->conninfo);
	free_plci(card, plcip);
}

/* ------------------------------------------------------------------ */

struct plcistatechange {
	int actstate;
	int nextstate;
	int event;
	void (*changefunc) (capi_contr * card, capi_connection * plci);
};

static struct plcistatechange plcitable[] =
{
  /* P-0 */
  {ST_PLCI_NONE, ST_PLCI_OUTGOING, EV_PLCI_CONNECT_REQ, 0},
  {ST_PLCI_NONE, ST_PLCI_ALLOCATED, EV_PLCI_FACILITY_IND_UP, 0},
  {ST_PLCI_NONE, ST_PLCI_INCOMING, EV_PLCI_CONNECT_IND, 0},
  {ST_PLCI_NONE, ST_PLCI_RESUMEING, EV_PLCI_RESUME_REQ, 0},
  /* P-0.1 */
  {ST_PLCI_OUTGOING, ST_PLCI_NONE, EV_PLCI_CONNECT_CONF_ERROR, p0},
  {ST_PLCI_OUTGOING, ST_PLCI_ALLOCATED, EV_PLCI_CONNECT_CONF_OK, 0},
  /* P-1 */
  {ST_PLCI_ALLOCATED, ST_PLCI_ACTIVE, EV_PLCI_CONNECT_ACTIVE_IND, 0},
  {ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, 0},
  {ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, 0},
  {ST_PLCI_ALLOCATED, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, 0},
  /* P-ACT */
  {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, 0},
  {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, 0},
  {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, 0},
  {ST_PLCI_ACTIVE, ST_PLCI_HELD, EV_PLCI_HOLD_IND, 0},
  {ST_PLCI_ACTIVE, ST_PLCI_DISCONNECTING, EV_PLCI_SUSPEND_IND, 0},
  {ST_PLCI_ACTIVE, ST_PLCI_ACTIVE, EV_PLCI_CONNECT_RESP, 0},
  /* P-2 */
  {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_CONNECT_REJECT, 0},
  {ST_PLCI_INCOMING, ST_PLCI_FACILITY_IND, EV_PLCI_FACILITY_IND_UP, 0},
  {ST_PLCI_INCOMING, ST_PLCI_ACCEPTING, EV_PLCI_CONNECT_RESP, 0},
  {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, 0},
  {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, 0},
  {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, 0},
  {ST_PLCI_INCOMING, ST_PLCI_DISCONNECTING, EV_PLCI_CD_IND, 0},
  /* P-3 */
  {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_CONNECT_REJECT, 0},
  {ST_PLCI_FACILITY_IND, ST_PLCI_ACCEPTING, EV_PLCI_CONNECT_ACTIVE_IND, 0},
  {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, 0},
  {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, 0},
  {ST_PLCI_FACILITY_IND, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, 0},
  {ST_PLCI_ACCEPTING, ST_PLCI_ACCEPTING, EV_PLCI_CONNECT_RESP, 0},
  {ST_PLCI_ACCEPTING, ST_PLCI_ACTIVE, EV_PLCI_CONNECT_ACTIVE_IND, 0},
  {ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, 0},
  {ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, 0},
  {ST_PLCI_ACCEPTING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, 0},
  /* P-5 */
  {ST_PLCI_DISCONNECTING, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, 0},
  /* P-6 */
  {ST_PLCI_DISCONNECTED, ST_PLCI_NONE, EV_PLCI_DISCONNECT_RESP, p0},
  /* P-0.Res */
  {ST_PLCI_RESUMEING, ST_PLCI_NONE, EV_PLCI_RESUME_CONF_ERROR, p0},
  {ST_PLCI_RESUMEING, ST_PLCI_RESUME, EV_PLCI_RESUME_CONF_OK, 0},
  /* P-RES */
  {ST_PLCI_RESUME, ST_PLCI_ACTIVE, EV_PLCI_RESUME_IND, 0},
  /* P-HELD */
  {ST_PLCI_HELD, ST_PLCI_DISCONNECTING, EV_PLCI_DISCONNECT_REQ, 0},
  {ST_PLCI_HELD, ST_PLCI_DISCONNECTING, EV_PLCI_FACILITY_IND_DOWN, 0},
  {ST_PLCI_HELD, ST_PLCI_DISCONNECTED, EV_PLCI_DISCONNECT_IND, 0},
  {ST_PLCI_HELD, ST_PLCI_ACTIVE, EV_PLCI_RETRIEVE_IND, 0},
  { 0, 0, 0, 0 },
};

static void plci_change_state(capi_contr * card, capi_connection * plci, int event)
{
	struct plcistatechange *p = plcitable;
	while (p->event) {
		if (plci->state == p->actstate && p->event == event) {
			printk(KERN_DEBUG "capiconn: plci_change_state:0x%x %d -> %d event=%d\n",
				  plci->plci, plci->state, p->nextstate, event);
			plci->state = p->nextstate;
			if (p->changefunc)
				p->changefunc(card, plci);
			return;
		}
		p++;
	}
	printk(KERN_ERR "capiconn: plci_change_state:0x%x state=%d event=%d ????\n",
	              plci->plci, plci->state, event);
}

/* ------------------------------------------------------------------ */


static void n0(capi_contr * card, capi_ncci * ncci)
{
	capiconn_context *ctx = card->ctx;
	capi_connection *plcip = ncci->plcip;

	if (plcip->state == ST_PLCI_HELD) {
	   printk(KERN_DEBUG "capiconn: n0:0x%x plci in status HELD\n", ncci->ncci);
	} else if (plcip->hold_or_unhold_in_progress)  {
	   printk(KERN_DEBUG "capiconn: n0:0x%x hold in progress\n", ncci->ncci);
	} else {
	   capi_fill_DISCONNECT_REQ(&cmsg,
					ctx->appid,
					card->msgid++,
					plcip->plci,
					0,	/* BChannelinformation */
					0,	/* Keypadfacility */
					0,	/* Useruserdata */   /* $$$$ */
					0	/* Facilitydataarray */
	   );
	   send_message(card, &cmsg);
	   plci_change_state(card, plcip, EV_PLCI_DISCONNECT_REQ);
	}
	free_ncci(card, ncci);
}

/* ------------------------------------------------------------------ */

struct nccistatechange {
	int actstate;
	int nextstate;
	int event;
	void (*changefunc) (capi_contr * card, capi_ncci * ncci);
};

static struct nccistatechange nccitable[] =
{
  /* N-0 */
  {ST_NCCI_NONE, ST_NCCI_OUTGOING, EV_NCCI_CONNECT_B3_REQ, 0},
  {ST_NCCI_NONE, ST_NCCI_INCOMING, EV_NCCI_CONNECT_B3_IND, 0},
  /* N-0.1 */
  {ST_NCCI_OUTGOING, ST_NCCI_ALLOCATED, EV_NCCI_CONNECT_B3_CONF_OK, 0},
  {ST_NCCI_OUTGOING, ST_NCCI_NONE, EV_NCCI_CONNECT_B3_CONF_ERROR, n0},
  /* N-1 */
  {ST_NCCI_INCOMING, ST_NCCI_DISCONNECTING, EV_NCCI_CONNECT_B3_REJECT, 0},
  {ST_NCCI_INCOMING, ST_NCCI_ALLOCATED, EV_NCCI_CONNECT_B3_RESP, 0},
  {ST_NCCI_INCOMING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, 0},
  {ST_NCCI_INCOMING, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, 0},
  /* N-2 */
  {ST_NCCI_ALLOCATED, ST_NCCI_ACTIVE, EV_NCCI_CONNECT_B3_ACTIVE_IND, 0},
  {ST_NCCI_ALLOCATED, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, 0},
  {ST_NCCI_ALLOCATED, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, 0},
  /* N-ACT */
  {ST_NCCI_ACTIVE, ST_NCCI_ACTIVE, EV_NCCI_RESET_B3_IND, 0},
  {ST_NCCI_ACTIVE, ST_NCCI_RESETING, EV_NCCI_RESET_B3_REQ, 0},
  {ST_NCCI_ACTIVE, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, 0},
  {ST_NCCI_ACTIVE, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, 0},
  /* N-3 */
  {ST_NCCI_RESETING, ST_NCCI_ACTIVE, EV_NCCI_RESET_B3_IND, 0},
  {ST_NCCI_RESETING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, 0},
  {ST_NCCI_RESETING, ST_NCCI_DISCONNECTING, EV_NCCI_DISCONNECT_B3_REQ, 0},
  /* N-4 */
  {ST_NCCI_DISCONNECTING, ST_NCCI_DISCONNECTED, EV_NCCI_DISCONNECT_B3_IND, 0},
  {ST_NCCI_DISCONNECTING, ST_NCCI_PREVIOUS, EV_NCCI_DISCONNECT_B3_CONF_ERROR,0},
  /* N-5 */
  {ST_NCCI_DISCONNECTED, ST_NCCI_NONE, EV_NCCI_DISCONNECT_B3_RESP, n0},
  { 0, 0, 0, 0 },
};

static void ncci_change_state(capi_contr * card, capi_ncci * ncci, int event)
{
	struct nccistatechange *p = nccitable;
	while (p->event) {
		if (ncci->state == p->actstate && p->event == event) {
			printk(KERN_DEBUG "capiconn: ncci_change_state:0x%x %d -> %d event=%d\n",
				  ncci->ncci, ncci->state, p->nextstate, event);
			if (p->nextstate == ST_NCCI_PREVIOUS) {
				ncci->state = ncci->oldstate;
				ncci->oldstate = p->actstate;
			} else {
				ncci->oldstate = p->actstate;
				ncci->state = p->nextstate;
			}
			if (p->changefunc)
				p->changefunc(card, ncci);
			return;
		}
		p++;
	}
	printk(KERN_ERR "capiconn: ncci_change_state:0x%x state=%d event=%d ????\n",
	       ncci->ncci, ncci->state, event);
}

/* ------------------------------------------------------------------- */

static void handle_controller(capiconn_context *ctx, _cmsg * cmsg)
{
	capi_contr *card = findcontrbynumber(ctx, cmsg->adr.adrController&0x7f);

	if (!card) {
		printk(KERN_ERR " capiconn: %s from unknown controller 0x%x\n",
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrController & 0x7f);
		return;
	}
	switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) {

	case CAPI_LISTEN_CONF:	/* Controller */
		printk(KERN_DEBUG "capiconn: contr %d: listenconf Info=0x%04x (%s) infomask=0x%x cipmask=0x%x capimask2=0x%x\n",
			       card->contrnr, cmsg->Info,
			       capi_info2str(cmsg->Info),
			       card->infomask,
			       card->cipmask,
			       card->cipmask2);
		if (cmsg->Info) {
			listen_change_state(card, EV_LISTEN_CONF_ERROR);
		} else if (card->cipmask == 0) {
			listen_change_state(card, EV_LISTEN_CONF_EMPTY);
		} else {
			listen_change_state(card, EV_LISTEN_CONF_OK);
		}
		break;

	case CAPI_MANUFACTURER_IND:	/* Controller */
#if 0
		if (   cmsg->ManuID == 0x214D5641
		    && cmsg->Class == 0
		    && cmsg->Function == 1) {
		   __u8 *data = cmsg->ManuData+3;
		   __u16 len = cmsg->ManuData[0];
		   __u16 layer;
		   int direction;
		   if (len == 255) {
		      len = (cmsg->ManuData[1] | (cmsg->ManuData[2] << 8));
		      data += 2;
		   }
		   len -= 2;
		   layer = ((*(data-1)) << 8) | *(data-2);
		   if (layer & 0x300)
			direction = (layer & 0x200) ? 0 : 1;
		   else direction = (layer & 0x800) ? 0 : 1;
		   if (layer & 0x0C00) {
		   	if ((layer & 0xff) == 0x80) {
		           handle_dtrace_data(card, direction, 1, data, len);
		           break;
		   	}
		   } else if ((layer & 0xff) < 0x80) {
		      handle_dtrace_data(card, direction, 0, data, len);
		      break;
		   }
		   printk(KERN_INFO "capiconn: %s from controller %d layer 0x%x, ignored\n",
		   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		                cmsg->adr.adrController, layer);
                   break;
		}
#endif
		goto ignored;
	case CAPI_MANUFACTURER_CONF:	/* Controller */
		if (cmsg->ManuID == 0x214D5641) {
		   char *s = 0;
		   switch (cmsg->Class) {
		      case 0: break;
		      case 1: s = "unknown class"; break;
		      case 2: s = "unknown function"; break;
		      default: s = "unkown error"; break;
		   }
		   if (s)
	           printk(KERN_INFO "capiconn: %s from controller 0x%x function %d: %s\n",
			capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			cmsg->adr.adrController,
			cmsg->Function, s);
		   break;
		}
		goto ignored;
	case CAPI_FACILITY_IND:	/* Controller/plci/ncci */
		goto ignored;
	case CAPI_FACILITY_CONF:	/* Controller/plci/ncci */
		switch (cmsg->FacilitySelector) {
		   case 3: /* Supplementary Services */
			 if (cmsg->Info)
			    printk(KERN_INFO "capiconn: %s info 0x%x (%s) for contr 0x%x\n",
			           capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			           cmsg->Info, capi_info2str(cmsg->Info), 
			           cmsg->adr.adrController);
			 break;
		   case 0x13: /* MWI */
			 if (cmsg->Info)
			    printk(KERN_INFO "capiconn: %s info 0x%x (%s) for contr 0x%x\n",
			           capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			           cmsg->Info, capi_info2str(cmsg->Info), 
			           cmsg->adr.adrController);
			 break;
		   default:
			  goto ignored;
		}
		break;
	case CAPI_INFO_IND:	/* Controller/plci */
		goto ignored;
	case CAPI_INFO_CONF:	/* Controller/plci */
		goto ignored;

	default:
		printk(KERN_ERR "capiconn: got %s from controller 0x%x ???\n",
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrController);
	}
	return;

      ignored:
	printk(KERN_INFO "capiconn: %s from controller 0x%x ignored\n",
	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
	       cmsg->adr.adrController);
}

static unsigned char SendingComplete[5] = { 4, 1, 0, 0, 0 };

static void check_incoming_complete(capi_connection *plcip)
{
	capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;
	capiconn_callbacks *cb = ctx->cb;
	capi_contrinfo *cip = &card->cinfo;
	unsigned ddilen = plcip->contr->ddilen;

	if (ddilen) {
		unsigned len = plcip->conninfo.callednumber[0]-2;
		char *num = plcip->conninfo.callednumber+2;
		char *start;
		int ndigits;

		if ((start = strstr(num, cip->ddi)) != 0)
			len = strlen(start);
		ndigits = len - ddilen;
		if (ndigits < cip->ndigits) {
			printk(KERN_DEBUG "capiconn: %d digits missing (%s)\n",
				cip->ndigits-ndigits, num);
			return;
		}
	}
	if (*cb->incoming)
		(*cb->incoming)(plcip,
				plcip->contr->contrnr,
				plcip->conninfo.cipvalue,
				plcip->conninfo.callednumber+2,
				plcip->conninfo.callingnumber+3);

	if (plcip->state == ST_PLCI_INCOMING) {
		/* call not accepted, rejected or ignored */
		capi_fill_ALERT_REQ(&cmsg,
			    	ctx->appid,
			    	card->msgid++,
			    	plcip->plci,	/* adr */
			    	0,	/* BChannelinformation */
			    	0,	/* Keypadfacility */
			    	0,	/* Useruserdata */
			    	0,	/* Facilitydataarray */
				SendingComplete
				);
		plcip->msgid = cmsg.Messagenumber;
		send_message(card, &cmsg);
	}
}

static void handle_incoming_call(capi_contr * card, _cmsg * cmsg)
{
	capiconn_context *ctx = card->ctx;
	capiconn_callbacks *cb = ctx->cb;
	capi_connection *plcip;

	if ((plcip = new_plci(card, 1)) == 0) {
		printk(KERN_ERR "capiconn: incoming call on contr %d: no memory, sorry.\n", card->contrnr);
		goto ignore;
	}
	plcip->plci = cmsg->adr.adrPLCI;
    if (set_conninfo0(ctx, &plcip->conninfo, cmsg->LLC) < 0) {
		free_plci(card, plcip);
		goto ignore;
	}
	if (set_conninfo1b(ctx, &plcip->conninfo,
				cmsg->CIPValue,
				cmsg->CalledPartyNumber,
				cmsg->CallingPartyNumber,
                cmsg->Facilitydataarray,
				cmsg->CalledPartySubaddress,
				cmsg->CallingPartySubaddress) < 0) {
		free_plci(card, plcip);
		goto ignore;
	}
	plci_change_state(card, plcip, EV_PLCI_CONNECT_IND);

	printk(KERN_DEBUG "capiconn: incoming call contr=%d cip=%d %s -> %s\n", 
			card->contrnr,
			cmsg->CIPValue,
			plcip->conninfo.callingnumber+2,
			plcip->conninfo.callednumber+3);

	if (cb->incoming == 0)
		goto ignore;

	check_incoming_complete(plcip);

	return;

ignore:
	capi_fill_CONNECT_RESP(&cmdcmsg,
			       ctx->appid,
			       card->msgid++,
			       cmsg->adr.adrPLCI,
			       1,	/* ignore call */
			       0,   /* b1 protocol */
			       0,   /* b2 protocol */
			       0,   /* b3 protocol */
			       0,   /* b1 configuration */
			       0,   /* b2 configuration */
			       0,   /* b3 configuration */
				   0,   /* global configuration */
			       0,	/* ConnectedNumber */
			       0,	/* ConnectedSubaddress */
			       0,	/* LLC */
			       0,	/* BChannelinformation */
			       0,	/* Keypadfacility */
			       0,	/* Useruserdata */
			       0	/* Facilitydataarray */
				);
	capi_cmsg2message(&cmdcmsg, cmdcmsg.buf);
	send_message(card, &cmdcmsg);
}

static int handle_charge_info(capi_connection *plcip, _cmsg *cmsg)
{
	capiconn_context *ctx = plcip->ctx;
	capiconn_callbacks *cb = ctx->cb;
	unsigned char *p = cmsg->InfoElement;
	unsigned long charge = 0;

	if ((cmsg->InfoNumber & 0x4000) && p[0] == 4) {
		unsigned char *p = &cmsg->InfoElement[1];
		charge |= ((unsigned)p[0]);
		charge |= ((unsigned)p[1]) << 8;
		charge |= ((unsigned)p[2]) << 16;
		charge |= ((unsigned)p[3]) << 24;
		if (cb->chargeinfo) {
	        	if (cmsg->InfoNumber & 0x1)
				(*cb->chargeinfo)(plcip, charge, 0);
			else (*cb->chargeinfo)(plcip, charge, 1);
		}
		return 1;
	} else if (cmsg->InfoNumber == 0x28) {
		if (p[0] > 10 && memcmp("*AOC2*12*", p+1, 9) == 0) {
			int i, len = p[0]-10;
			if (len > 8) len = 8;
			for (i=0; i < len; i++)
				charge = charge * 10 + (p[10+i] - '0');
			if (cb->chargeinfo)
				(*cb->chargeinfo)(plcip, charge, 0);
			return 1;
		} else if (p[0] > 7 && memcmp("FR.", p+1, 3) == 0) {
			int i, len = p[0]-3;
			for (i=0; p[3+i] != '.' && i < len; i++)
				charge = charge * 10 + (p[3+i] - '0');
			charge = charge * 10;
			if (p[3+i] == '.' && i+1 < len)
				charge += (p[3+i+1] - '0');
			if (cb->chargeinfo)
				(*cb->chargeinfo)(plcip, charge, 0);
			return 1;
		}
	} else if (cmsg->InfoNumber == 0x602) {
		if (p[0] > 1 && p[1] == 0x01) {
			int i, len = p[0]-1;
			for (i=0; i < len; i++)
				charge = charge * 10 + (p[1+i] - '0');
			if (cb->chargeinfo)
				(*cb->chargeinfo)(plcip, charge, 0);
			return 1;
		}
	}
	return 0;
}

static int handle_callednumber_info(capi_connection *plcip, _cmsg *cmsg)
{
	unsigned char *p = cmsg->InfoElement;
	if (cmsg->InfoNumber == 0x0070) {
		extend_callednumber(plcip->ctx, &plcip->conninfo, p+2, p[0]-1);
		check_incoming_complete(plcip);
		return 1;
	}
	return 0;
}

static int handle_dtmf_info(capi_connection *plcip, _cmsg *cmsg)
{
	capiconn_context *ctx = plcip->ctx;
	capiconn_callbacks *cb = ctx->cb;

	if (cmsg->InfoNumber == 0x002c) {
		(*cb->dtmf_received)(plcip,
				     cmsg->InfoElement+1,
				     cmsg->InfoElement[0]);
		return 1;
	}
	return 0;
}

static void handle_plci(capiconn_context *ctx, _cmsg * cmsg)
{
	capi_contr *card = findcontrbynumber(ctx, cmsg->adr.adrController&0x7f);
	capiconn_callbacks *cb = ctx->cb;
	capi_connection *plcip;

	if (!card) {
		printk(KERN_ERR "capiconn: %s from unknown controller 0x%x\n",
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrController & 0x7f);
		return;
	}
	switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) {

	case CAPI_DISCONNECT_IND:	/* plci */
		if (cmsg->Reason) {
			printk(KERN_DEBUG "capiconn: %s reason 0x%x (%s) for plci 0x%x\n",
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->Reason, capi_info2str(cmsg->Reason), cmsg->adr.adrPLCI);
		}
		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI))) {
			capi_cmsg_answer(cmsg);
			send_message(card, cmsg);
			goto notfound;
		}
		plcip->disconnectreason = cmsg->Reason;
		plcip->disconnecting = 1;
		plci_change_state(card, plcip, EV_PLCI_DISCONNECT_IND);
		capi_cmsg_answer(cmsg);
		send_message(card, cmsg);
		plci_change_state(card, plcip, EV_PLCI_DISCONNECT_RESP);
		break;

	case CAPI_DISCONNECT_CONF:	/* plci */
		if (cmsg->Info) {
			printk(KERN_INFO "capiconn: %s info 0x%x (%s) for plci 0x%x\n",
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->Info, capi_info2str(cmsg->Info), 
			       cmsg->adr.adrPLCI);
		}
		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
			goto notfound;

		plcip->disconnecting = 1;
		break;

	case CAPI_ALERT_CONF:	/* plci */
		if (cmsg->Info) {
			printk(KERN_INFO "capiconn: %s info 0x%x (%s) for plci 0x%x\n",
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->Info, capi_info2str(cmsg->Info), 
			       cmsg->adr.adrPLCI);
		}
		break;

	case CAPI_CONNECT_IND:	/* plci */
		handle_incoming_call(card, cmsg);
		break;

	case CAPI_CONNECT_CONF:	/* plci */
		if (cmsg->Info) {
			printk(KERN_INFO "capiconn: %s info 0x%x (%s) for plci 0x%x\n",
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->Info, capi_info2str(cmsg->Info), 
			       cmsg->adr.adrPLCI);
		}
		if (!(plcip = find_plci_by_msgid(card, cmsg->Messagenumber)))
			goto notfound;

		plcip->plci = cmsg->adr.adrPLCI;
		if (cmsg->Info) {
			plci_change_state(card, plcip, EV_PLCI_CONNECT_CONF_ERROR);
		} else {
			plci_change_state(card, plcip, EV_PLCI_CONNECT_CONF_OK);
		}
		break;

	case CAPI_CONNECT_ACTIVE_IND:	/* plci */

		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
			goto notfound;

		if (cb->plci_connected)
		   (*cb->plci_connected)(plcip);

		if (plcip->incoming) {
			capi_cmsg_answer(cmsg);
		        if (plcip->delay_connect_active_resp) {
	                   capi_cmsg2message(cmsg, plcip->saved_connect_active_resp);
		           plcip->connect_active_resp_saved = 1;
		        } else {
		           send_message(card, cmsg);
		        }
			plci_change_state(card, plcip, EV_PLCI_CONNECT_ACTIVE_IND);
		} else {
			capi_ncci *nccip;

			capi_cmsg_answer(cmsg);
			send_message(card, cmsg);
			plci_change_state(card, plcip, EV_PLCI_CONNECT_ACTIVE_IND);

			if (plcip->nccip) /* Early B3 Connect */
			   break;

send_connect_b3_req:

			nccip = new_ncci(card, plcip, cmsg->adr.adrPLCI);

			if (!nccip) {
				printk(KERN_ERR "capiconn: no mem for ncci on contr %d, sorry\n", card->contrnr);
				break;
			}
			capi_fill_CONNECT_B3_REQ(cmsg,
						 ctx->appid,
						 card->msgid++,
						 plcip->plci,	/* adr */
						 plcip->conninfo.ncpi);
			nccip->msgid = cmsg->Messagenumber;
			send_message(card, cmsg);
			ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_REQ);
		}
		break;

	case CAPI_INFO_IND:	/* Controller/plci */

		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
			goto notfound;

		if (handle_charge_info(plcip, cmsg)) {
			capi_cmsg_answer(cmsg);
			send_message(card, cmsg);
		} else if (handle_callednumber_info(plcip, cmsg)) {
			capi_cmsg_answer(cmsg);
			send_message(card, cmsg);
		} else if (handle_dtmf_info(plcip, cmsg)) {
			capi_cmsg_answer(cmsg);
			send_message(card, cmsg);
	    } else if (cmsg->InfoNumber == 0x1e) { /* Progress Indication */
			capi_cmsg_answer(cmsg);
			send_message(card, cmsg);
			if (plcip->with_early_b3 && plcip->nccip == 0)
               goto send_connect_b3_req;
	    } else if (cmsg->InfoNumber == 0x8001) { /* ALERTING */
		    if (cb->alerting)
	           (*cb->alerting)(plcip);
        } else if (cmsg->InfoNumber == 0x8045) { /* DISCONNECT */
            if (cb->call_progress_disconnect) {
                (*cb->call_progress_disconnect)(plcip);
            }
		} else {
			capi_cmsg_answer(cmsg);
			send_message(card, cmsg);
		}
		/* 0x800d SETUP ACKNOWLEDGE */
		/* 0x8002 CALL PROCEEDING */
		/* 0x8001 ALERTING */
		/* 0x8003 PROGRESS */
		break;

	case CAPI_CONNECT_ACTIVE_CONF:		/* plci */
		goto ignored;
	case CAPI_SELECT_B_PROTOCOL_CONF:	/* plci */
		goto ignored;
	case CAPI_FACILITY_IND:	/* Controller/plci/ncci */
		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
			goto notfound;
		if (cmsg->FacilitySelector == 1) { /* DTMF */
		   (*cb->dtmf_received)(plcip,
								cmsg->FacilityIndicationParameter+1,
								cmsg->FacilityIndicationParameter[0]);
		   capi_cmsg_answer(cmsg);
		   send_message(card, cmsg);
		} else if (cmsg->FacilitySelector == 3) { /* Supplementary Services */
		   capi_ncci *nccip;

		   switch (CAPIMSG_U16(cmsg->FacilityIndicationParameter,1)) {

			  case 0x0002: /* HOLD */
		         printk(KERN_DEBUG "capiconn: plci 0x%x hold indication\n", plcip->plci);
				 plci_change_state(card, plcip, EV_PLCI_HOLD_IND);
                 plcip->hold_or_unhold_in_progress = 0;
				 if (cb->onhold_indication)
				    (*cb->onhold_indication)(plcip);
				 capi_cmsg_answer(cmsg);
				 send_message(card, cmsg);
                 
				 if (plcip->hold_state_wanted == 0) {
		            printk(KERN_DEBUG "capiconn: plci 0x%x retrieve again\n", plcip->plci);
                    (void)send_offhold(plcip);
				 } else if (plcip->hold_tone_want != plcip->hold_tone_sent) {
		            printk(KERN_DEBUG "capiconn: plci 0x%x retrieve again, (want tone %ld)\n",
									 plcip->plci, plcip->hold_tone_want);
                    (void)send_offhold(plcip);
				 }
				 break;

			  case 0x0003: /* RETRIEVE */
		         printk(KERN_DEBUG "capiconn: plci 0x%x retrieve indication\n", plcip->plci);
				 plci_change_state(card, plcip, EV_PLCI_RETRIEVE_IND);
                 plcip->hold_or_unhold_in_progress = 0;
				 if (cb->offhold_indication)
				    (*cb->offhold_indication)(plcip);
				 capi_cmsg_answer(cmsg);
				 send_message(card, cmsg);

				 if (plcip->hold_state_wanted) {
		            printk(KERN_DEBUG "capiconn: plci 0x%x hold again, (want tone %ld)\n",
									 plcip->plci, plcip->hold_tone_want);
                    (void)send_onhold(plcip, plcip->hold_tone_want);
					break;
				 }

		         if (plcip->nccip)
					break;

				 nccip = new_ncci(card, plcip, cmsg->adr.adrPLCI);

				 if (!nccip) {
					printk(KERN_ERR "capiconn: no mem for ncci on contr %d, sorry\n", card->contrnr);
					break;
				 }
				 capi_fill_CONNECT_B3_REQ(cmsg,
							 ctx->appid,
							 card->msgid++,
							 plcip->plci,	/* adr */
							 plcip->conninfo.ncpi);
				 nccip->msgid = cmsg->Messagenumber;
				 send_message(card, cmsg);
				 ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_REQ);

				 break;

			  case 0x0006: /* ECT Explicit Call Transfer */
				 {
	                capi_connection *plcip2;
		            __u16 plci;

					plci = CAPIMSG_U16(cmsg->FacilityIndicationParameter, 4);
		            printk(KERN_DEBUG "capiconn: plci 0x%x ECT 0x%x\n", plcip->plci, plci);
					if ((plcip2 = find_plci_by_plci(card, plci)) != 0) {
				       if (cb->ect_request)
				          (*cb->ect_request)(plcip, plcip2);
					} else {
		               printk(KERN_ERR "capiconn: ECT plci 0x%x not found\n", plci);
					}
				 }
				 capi_cmsg_answer(cmsg);
				 send_message(card, cmsg);
				 break;

			  case 0x0007: /* 3PTY Begin */
				 {
	                capi_connection *plcip2;
		            __u16 plci;

					plci = CAPIMSG_U16(cmsg->FacilityIndicationParameter, 4);
		            printk(KERN_DEBUG "capiconn: plci 0x%x 3PTY 0x%x\n", plcip->plci, plci);
					if ((plcip2 = find_plci_by_plci(card, plci)) != 0) {
				       if (cb->pty3_request)
				          (*cb->pty3_request)(plcip, plcip2);
					} else {
		               printk(KERN_ERR "capiconn: 3PTY plci 0x%x not found\n", plci);
					}
				 }
				 capi_cmsg_answer(cmsg);
				 send_message(card, cmsg);
				 break;

			  case 0x8000: /* HOLD notification  */

		         printk(KERN_DEBUG "capiconn: plci 0x%x hold notification\n", plcip->plci);
				 (*cb->onhold_notification)(plcip);
				 capi_cmsg_answer(cmsg);
				 send_message(card, cmsg);
				 break;

			  case 0x8001: /* RETRIEVE notification  */

		         printk(KERN_DEBUG "capiconn: plci 0x%x retrieve notification\n", plcip->plci);
				 (*cb->offhold_notification)(plcip);
				 capi_cmsg_answer(cmsg);
				 send_message(card, cmsg);
				 break;

			  default:
				 goto ignored;
		   }
		} else {
		   goto ignored;
		}
		break;
	case CAPI_FACILITY_CONF:	/* Controller/plci/ncci */
		switch (cmsg->FacilitySelector) {
		   case 1: /* DTMF */
		   case 3: /* Supplementary Services */
			  if (cmsg->Info) {
				  printk(KERN_INFO "capiconn: %s info 0x%x (%s) for plci 0x%x\n",
					 capi_cmd2str(cmsg->Command, cmsg->Subcommand),
						 cmsg->Info, capi_info2str(cmsg->Info), 
						 cmsg->adr.adrPLCI);
			  }
			  break;
		   default:
			  goto ignored;
		}
		break;
	case CAPI_INFO_CONF:	/* Controller/plci */
		goto ignored;

	case CAPI_CONNECT_B3_CONF:	/* ncci */
		if (!(plcip = find_plci_by_plci(card, cmsg->adr.adrPLCI)))
		   goto notfound;
		printk(KERN_INFO "capiconn: %s info 0x%x (%s) for ncci 0x%x\n",
					    capi_cmd2str(cmsg->Command, cmsg->Subcommand),
						cmsg->Info, capi_info2str(cmsg->Info), 
						cmsg->adr.adrNCCI);
		if (plcip->with_early_b3)
		   free_ncci(card, plcip->nccip);
		else
		   ncci_change_state(card, plcip->nccip, EV_NCCI_CONNECT_B3_CONF_ERROR);
		break;

	default:
		printk(KERN_ERR "capiconn: got %s for plci 0x%x ???\n",
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrPLCI);
	}
	return;

ignored:
	printk(KERN_INFO "capiconn: %s for plci 0x%x ignored\n",
	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
	       cmsg->adr.adrPLCI);
	return;

notfound:
	printk(KERN_ERR "capiconn: %s: plci 0x%x not found\n",
	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
	       cmsg->adr.adrPLCI);
	return;
}

static void handle_ncci(capiconn_context *ctx, _cmsg * cmsg)
{
	capi_contr *card = findcontrbynumber(ctx, cmsg->adr.adrController&0x7f);
	capiconn_callbacks *cb = ctx->cb;
	capi_connection *plcip;
	capi_ncci *nccip;
	unsigned char *data;
	unsigned len;

	if (!card) {
		printk(KERN_ERR "capiconn: %s from unknown controller 0x%x\n",
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrController & 0x7f);
		return;
	}
	switch (CAPICMD(cmsg->Command, cmsg->Subcommand)) {

	case CAPI_CONNECT_B3_ACTIVE_IND:	/* ncci */
		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
			goto notfound;
	    plcip = nccip->plcip;

		capi_cmsg_answer(cmsg);
		send_message(card, cmsg);
		ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_ACTIVE_IND);

		printk(KERN_DEBUG "capiconn: ncci 0x%x up\n", nccip->ncci);

		(*cb->connected)(plcip, cmsg->NCPI);
		break;

	case CAPI_CONNECT_B3_ACTIVE_CONF:	/* ncci */
		goto ignored;

	case CAPI_CONNECT_B3_IND:	/* ncci */

		plcip = find_plci_by_ncci(card, cmsg->adr.adrNCCI);
		if (plcip) {
			nccip = new_ncci(card, plcip, cmsg->adr.adrNCCI);
			if (nccip) {
				ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_IND);
				capi_fill_CONNECT_B3_RESP(cmsg,
							  ctx->appid,
							  card->msgid++,
							  nccip->ncci,	/* adr */
							  0,	/* Reject */
							  0	/* NCPI */
				);
				send_message(card, cmsg);
				ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_RESP);
				break;
			}
			printk(KERN_ERR "capiconn-%d: no mem for ncci, sorry\n",	card->contrnr);
		} else {
			printk(KERN_ERR "capiconn-%d: %s: plci for ncci 0x%x not found\n",
			   card->contrnr,
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->adr.adrNCCI);
		}
		capi_fill_CONNECT_B3_RESP(cmsg,
					  ctx->appid,
					  card->msgid++,
					  cmsg->adr.adrNCCI,
					  2,	/* Reject */
					  plcip->conninfo.ncpi);
		send_message(card, cmsg);
		break;

	case CAPI_CONNECT_B3_CONF:	/* ncci */

		if (!(nccip = find_ncci_by_msgid(card,
						 cmsg->adr.adrNCCI,
						 cmsg->Messagenumber)))
			goto notfound;

		plcip = nccip->plcip;
		nccip->ncci = cmsg->adr.adrNCCI;
		plcip->ncci = cmsg->adr.adrNCCI;
		if (cmsg->Info) {
			printk(KERN_INFO "capiconn: %s info 0x%x (%s) for ncci 0x%x\n",
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->Info, capi_info2str(cmsg->Info), 
			       cmsg->adr.adrNCCI);
		}

		if (cmsg->Info)
			ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_CONF_ERROR);
		else
			ncci_change_state(card, nccip, EV_NCCI_CONNECT_B3_CONF_OK);
		break;

	case CAPI_CONNECT_B3_T90_ACTIVE_IND:	/* ncci */
		capi_cmsg_answer(cmsg);
		send_message(card, cmsg);
		break;

	case CAPI_DATA_B3_IND:	/* ncci */
		/* handled in handle_data() */
		goto ignored;

	case CAPI_DATA_B3_CONF:	/* ncci */
		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
			goto notfound;

		plcip = nccip->plcip;
		data = capi_del_ack(nccip, cmsg->DataHandle, &len);
		if (data == 0)
			break;
		if (cb->datasent)
			(*cb->datasent)(plcip, data, len);
		break;

	case CAPI_DISCONNECT_B3_IND:	/* ncci */
		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
			goto notfound;

		plcip = nccip->plcip;
		plcip->disconnectreason_b3 = cmsg->Reason_B3;
	    if (!(plcip->state == ST_PLCI_HELD || plcip->hold_or_unhold_in_progress))
		   nccip->plcip->disconnecting = 1;
		ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_IND);
		capi_cmsg_answer(cmsg);
		send_message(card, cmsg);
		ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_RESP);
		break;

	case CAPI_DISCONNECT_B3_CONF:	/* ncci */
		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
			goto notfound;
		if (cmsg->Info) {
			printk(KERN_INFO "capiconn: %s info 0x%x (%s) for ncci 0x%x\n",
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->Info, capi_info2str(cmsg->Info), 
			       cmsg->adr.adrNCCI);
			ncci_change_state(card, nccip, EV_NCCI_DISCONNECT_B3_CONF_ERROR);
		}
		break;

	case CAPI_RESET_B3_IND:	/* ncci */
		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
			goto notfound;
		ncci_change_state(card, nccip, EV_NCCI_RESET_B3_IND);
		capi_cmsg_answer(cmsg);
		send_message(card, cmsg);
		break;

	case CAPI_RESET_B3_CONF:	/* ncci */
		goto ignored;	/* $$$$ */

	case CAPI_FACILITY_IND:	/* Controller/plci/ncci */
		goto ignored;

	case CAPI_FACILITY_CONF:	/* Controller/plci/ncci */
		if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI)))
			goto notfound;
		if (cmsg->Info) {
		   printk(KERN_INFO "capiconn: %s info 0x%x (%s) for ncci 0x%x\n",
			   capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->Info, capi_info2str(cmsg->Info), 
			       cmsg->adr.adrNCCI);
		} else if (   cmsg->FacilitySelector == 1
		           && cmsg->FacilityConfirmationParameter[0] > 0
			   && cmsg->FacilityConfirmationParameter[1]) {
		   switch (cmsg->FacilityConfirmationParameter[1]) {
		      case 0:
			 break;
		      case 1: 
		         printk(KERN_INFO "capiconn: %s incorrect DTMF digit for ncci 0x%x\n",
			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->adr.adrNCCI);
			 break;
		      case 2: 
		         printk(KERN_INFO "capiconn: %s Unknown DTMF request for ncci 0x%x\n",
			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->adr.adrNCCI);
			 break;
		      default:
		         printk(KERN_INFO "capiconn: %s DTMF errcode %d for ncci 0x%x\n",
			       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
			       cmsg->FacilityConfirmationParameter[1],
			       cmsg->adr.adrNCCI);
			 break;
		   }
		}
		break;

	default:
		printk(KERN_ERR "capiconn-%d: got %s for ncci 0x%x ???\n",
		       card->contrnr,
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrNCCI);
	}
	return;
ignored:
	printk(KERN_INFO "capiconn: %s for ncci 0x%x ignored\n",
	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
	       cmsg->adr.adrNCCI);
	return;
notfound:
	printk(KERN_ERR "capiconn: %s: ncci 0x%x not found\n",
	       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
	       cmsg->adr.adrNCCI);
}


static void handle_data(capiconn_context *ctx, _cmsg * cmsg)
{
	capi_contr *card = findcontrbynumber(ctx,cmsg->adr.adrController&0x7f);
	capiconn_callbacks *cb = ctx->cb;
	capi_ncci *nccip;
        unsigned char *data;

	if (!card) {
		printk(KERN_ERR "capiconn: %s from unknown controller 0x%x\n",
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrController & 0x7f);
		return;
	}
	if (!(nccip = find_ncci(card, cmsg->adr.adrNCCI))) {
		printk(KERN_ERR "capiconn: %s: ncci 0x%x not found\n",
		       capi_cmd2str(cmsg->Command, cmsg->Subcommand),
		       cmsg->adr.adrNCCI);
		return;
	}
	data = (unsigned char *)cmsg->Data;
	if (cb->received)
		(*cb->received)(nccip->plcip, data, cmsg->DataLength);
	capi_cmsg_answer(cmsg);
	send_message(card, cmsg);
}

static _cmsg s_cmsg;

void capiconn_inject(struct capi20_appl *appl, struct sk_buff *skb)
{
	capiconn_context *ctx = find_context(appl->applid);

	if (!ctx)
		return;

	capi_message2cmsg(&s_cmsg, skb->data);
	if (s_cmsg.Command == CAPI_DATA_B3 && s_cmsg.Subcommand == CAPI_IND) {
		handle_data(ctx, &s_cmsg);
		ctx->nrecvdatapkt++;
		kfree_skb(skb);
		return;
	} 
	if ((s_cmsg.adr.adrController & 0xffffff00) == 0)
		handle_controller(ctx, &s_cmsg);
	else if ((s_cmsg.adr.adrPLCI & 0xffff0000) == 0)
		handle_plci(ctx, &s_cmsg);
	else
		handle_ncci(ctx, &s_cmsg);
	ctx->nrecvctlpkt++;
	kfree_skb(skb);
}
EXPORT_SYMBOL(capiconn_inject);

/* ------------------------------------------------------------------- */

static capi_connection *_capiconn_connect(
	capiconn_context *ctx,
	unsigned contr,
	__u16 cipvalue,
	char *callednumber,	/* remote number */
	char *callingnumber,	/* own number */
	__u16 b1proto,
	__u16 b2proto,
	__u16 b3proto,
	_cstruct b1config,
	_cstruct b2config,
	_cstruct b3config,
	_cstruct bchaninfo,
	_cstruct ncpi,
	int early_b3)
{
    capi_contr *card = findcontrbynumber(ctx, contr);
	capi_connection *plcip;

	if (!card) {
		printk(KERN_ERR "capiconn: controller %d not found\n", contr);
		return 0;
	}

	if ((plcip = new_plci(card, 0)) == 0) {
		printk(KERN_ERR "capiconn: no mem for plci\n");
		return 0;
	}
	plcip->with_early_b3 = early_b3 ? 1 : 0;

	if (set_conninfo1a(ctx, &plcip->conninfo,
				cipvalue,
				callednumber,
				callingnumber) < 0) {
		clr_conninfo1(ctx, &plcip->conninfo);
		free_plci(card, plcip);
		printk(KERN_ERR "capiconn: no mem for connection info (1a)\n");
		return 0;
	}

	if (set_conninfo2(ctx, &plcip->conninfo,
				b1proto, b2proto, b3proto,
				b1config, b2config, b3config,
				bchaninfo, ncpi) < 0) {
		clr_conninfo1(ctx, &plcip->conninfo);
		clr_conninfo2(ctx, &plcip->conninfo);
		free_plci(card, plcip);
		printk(KERN_ERR "capiconn: no mem for connection info (2)\n");
		return 0;
	}

	capi_fill_CONNECT_REQ(&cmdcmsg,
			      ctx->appid,
			      card->msgid++,
			      card->contrnr,	/* adr */
			      plcip->conninfo.cipvalue,
			      plcip->conninfo.callednumber,
			      plcip->conninfo.callingnumber[0] > 2 ? plcip->conninfo.callingnumber : 0,
			      0,	/* CalledPartySubaddress */
			      0,	/* CallingPartySubaddress */
			      plcip->conninfo.b1proto,
			      plcip->conninfo.b2proto,
			      plcip->conninfo.b3proto,
			      plcip->conninfo.b1config,
			      plcip->conninfo.b2config,
			      plcip->conninfo.b3config,
				  0,    /* GlobalConfiguration */
			      0,	/* BC */
			      0,	/* LLC */
			      0,	/* HLC */
			      plcip->conninfo.bchaninfo, /* BChannelinformation */
			      0,	/* Keypadfacility */
			      0,	/* Useruserdata */
			      0,	/* Facilitydataarray */
		          0     /* SendingComplete */
			    );

	plcip->msgid = cmdcmsg.Messagenumber;
	plci_change_state(card, plcip, EV_PLCI_CONNECT_REQ);
	send_message(card, &cmdcmsg);
	return plcip;
}

capi_connection *capiconn_connect(
	capiconn_context *ctx,
	unsigned contr,
	__u16 cipvalue,
	char *callednumber,	/* remote number */
	char *callingnumber,	/* own number */
	__u16 b1proto,
	__u16 b2proto,
	__u16 b3proto,
	_cstruct b1config,
	_cstruct b2config,
	_cstruct b3config,
	_cstruct bchaninfo,
	_cstruct ncpi)
{
   return _capiconn_connect(ctx, contr, cipvalue, callednumber, callingnumber,
							b1proto, b2proto, b3proto,
							b1config, b2config, b3config,
							bchaninfo, ncpi, 0);
}
EXPORT_SYMBOL(capiconn_connect);

capi_connection *capiconn_connect_early_b3(
	capiconn_context *ctx,
	unsigned contr,
	__u16 cipvalue,
	char *callednumber,	/* remote number */
	char *callingnumber,	/* own number */
	__u16 b1proto,
	__u16 b2proto,
	__u16 b3proto,
	_cstruct b1config,
	_cstruct b2config,
	_cstruct b3config,
	_cstruct bchaninfo,
	_cstruct ncpi)
{
   return _capiconn_connect(ctx, contr, cipvalue, callednumber, callingnumber,
							b1proto, b2proto, b3proto,
							b1config, b2config, b3config,
							bchaninfo, ncpi, 1);
}
EXPORT_SYMBOL(capiconn_connect_early_b3);

int capiconn_delay_connect_active_resp(capi_connection *plcip)
{
	if (plcip->state != ST_PLCI_INCOMING)
		return CAPICONN_WRONG_STATE;
	plcip->delay_connect_active_resp = 1;
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_delay_connect_active_resp);

int capiconn_send_delayed_connect_active_resp(capi_connection *plcip)
{
    capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;
	capiconn_callbacks *cb = ctx->cb;

	if (plcip->connect_active_resp_saved == 0) {
		plcip->delay_connect_active_resp = 0;
	} else {
		char *msg = plcip->saved_connect_active_resp;
	    __u16 mlen = CAPIMSG_LEN(msg);
		struct sk_buff *skb;
        if ((skb = alloc_skb(mlen, GFP_ATOMIC)) == 0)
           return CAPICONN_NO_MEMORY;
        memcpy(skb_put(skb, mlen), msg, mlen);
		(*cb->capi_put_message) (ctx->appl, skb);
		plcip->connect_active_resp_saved = 0;
	}
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_send_delayed_connect_active_resp);

int capiconn_accept(
	capi_connection *plcip,
	__u16 b1proto,
	__u16 b2proto,
	__u16 b3proto,
	_cstruct b1config,
	_cstruct b2config,
	_cstruct b3config,
	_cstruct ncpi,
	_cstruct sendingcomplete)
{
        capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;

	if (sendingcomplete) {
	   if (plcip->state != ST_PLCI_ACTIVE)
		  return CAPICONN_WRONG_STATE;
	} else {
	   if (plcip->state != ST_PLCI_INCOMING)
		  return CAPICONN_WRONG_STATE;
	}

	if (set_conninfo2(ctx, &plcip->conninfo,
				b1proto, b2proto, b3proto,
				b1config, b2config, b3config,
				0, ncpi) < 0) {
		clr_conninfo2(ctx, &plcip->conninfo);
		printk(KERN_ERR "capiconn: no mem for connection info (2)\n");
		return CAPICONN_NO_MEMORY;
	}

	printk(KERN_DEBUG "capiconn: accept plci 0x%04x %d,%d,%d\n",
			plcip->plci,
			(int)plcip->conninfo.b1proto,
			(int)plcip->conninfo.b2proto,
			(int)plcip->conninfo.b3proto);

	capi_fill_CONNECT_RESP(&cmdcmsg,
			       ctx->appid,
			       card->msgid++,
			       plcip->plci,	/* adr */
			       0,	/* Reject */
			       plcip->conninfo.b1proto,
			       plcip->conninfo.b2proto,
			       plcip->conninfo.b3proto,
			       plcip->conninfo.b1config,
			       plcip->conninfo.b2config,
			       plcip->conninfo.b3config,
				   0,   /* GlobalConfiguration */
			       0,	/* ConnectedNumber */
			       0,	/* ConnectedSubaddress */
			       0,	/* LLC */
			       0,	/* BChannelinformation */
			       0,	/* Keypadfacility */
			       0,	/* Useruserdata */
			       0	/* Facilitydataarray */
				);
	cmdcmsg.SendingComplete = sendingcomplete;
	plci_change_state(card, plcip, EV_PLCI_CONNECT_RESP);
	send_message(card, &cmdcmsg);
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_accept);

int capiconn_ignore(capi_connection *plcip)
{
        capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;

	if (plcip->state != ST_PLCI_INCOMING)
		return CAPICONN_WRONG_STATE;

	capi_fill_CONNECT_RESP(&cmdcmsg,
			       ctx->appid,
			       card->msgid++,
			       plcip->plci,	/* adr */
			       1,	/* ignore call */
			       0,   /* b1 protocol */
			       0,   /* b2 protocol */
			       0,   /* b3 protocol */
			       0,   /* b1 configuration */
			       0,   /* b2 configuration */
			       0,   /* b3 configuration */
				   0,   /* global configuration */
			       0,	/* ConnectedNumber */
			       0,	/* ConnectedSubaddress */
			       0,	/* LLC */
			       0,	/* BChannelinformation */
			       0,	/* Keypadfacility */
			       0,	/* Useruserdata */
			       0	/* Facilitydataarray */
				);
	plci_change_state(card, plcip, EV_PLCI_CONNECT_RESP);
	send_message(card, &cmdcmsg);
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_ignore);

int capiconn_reject(capi_connection *plcip)
{
        capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;

	if (plcip->state != ST_PLCI_INCOMING)
		return CAPICONN_WRONG_STATE;

	capi_fill_CONNECT_RESP(&cmdcmsg,
			       ctx->appid,
			       card->msgid++,
			       plcip->plci,	/* adr */
			       2,	/* Reject, normal call clearing */
			       0,   /* b1 protocol */
			       0,   /* b2 protocol */
			       0,   /* b3 protocol */
			       0,   /* b1 configuration */
			       0,   /* b2 configuration */
			       0,   /* b3 configuration */
				   0,   /* global configuration */
			       0,	/* ConnectedNumber */
			       0,	/* ConnectedSubaddress */
			       0,	/* LLC */
			       0,	/* BChannelinformation */
			       0,	/* Keypadfacility */
			       0,	/* Useruserdata */
			       0	/* Facilitydataarray */
				);
	plci_change_state(card, plcip, EV_PLCI_CONNECT_RESP);
	send_message(card, &cmdcmsg);
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_reject);

int capiconn_reject_with_value(capi_connection *plcip, unsigned short value)
{
    capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;

	if (plcip->state != ST_PLCI_INCOMING)
		return CAPICONN_WRONG_STATE;

    printk(KERN_INFO "capiconn: CONNECT_RESP with cause value 0x%hx\n", value);

	capi_fill_CONNECT_RESP(&cmdcmsg,
			       ctx->appid,
			       card->msgid++,
			       plcip->plci,	/* adr */
			       value,	/* Reject value */
			       0,   /* b1 protocol */
			       0,   /* b2 protocol */
			       0,   /* b3 protocol */
			       0,   /* b1 configuration */
			       0,   /* b2 configuration */
			       0,   /* b3 configuration */
				   0,   /* global configuration */
			       0,	/* ConnectedNumber */
			       0,	/* ConnectedSubaddress */
			       0,	/* LLC */
			       0,	/* BChannelinformation */
			       0,	/* Keypadfacility */
			       0,	/* Useruserdata */
			       0	/* Facilitydataarray */
				);
	plci_change_state(card, plcip, EV_PLCI_CONNECT_RESP);
	send_message(card, &cmdcmsg);
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_reject_with_value);

int capiconn_ringing(capi_connection *plcip)
{
        capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;

	if (plcip->state != ST_PLCI_INCOMING)
		return CAPICONN_WRONG_STATE;

	/* call not accepted, rejected or ignored */
	capi_fill_ALERT_REQ(&cmsg,
			    ctx->appid,
			    card->msgid++,
			    plcip->plci,	/* adr */
			    0,	/* BChannelinformation */
			    0,	/* Keypadfacility */
			    0,	/* Useruserdata */
			    0,	/* Facilitydataarray */
		            0   /* SendingComplete */
			   );
	plcip->msgid = cmsg.Messagenumber;
	send_message(card, &cmsg);
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_ringing);

int capiconn_disconnect(capi_connection *plcip, _cstruct ncpi)
{
        capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;

	if (plcip->disconnecting)
		return CAPICONN_ALREADY_IN_PROGRESS;

	if (plcip->nccip) {
		plcip->disconnecting = 1;
		plcip->localdisconnect = 1;
		capi_fill_DISCONNECT_B3_REQ(&cmdcmsg,
					    ctx->appid,
					    card->msgid++,
					    plcip->ncci,
					    ncpi);
		ncci_change_state(card, plcip->nccip, EV_NCCI_DISCONNECT_B3_REQ);
		send_message(card, &cmdcmsg);
		return CAPICONN_OK;
	} 
	if (plcip->state == ST_PLCI_INCOMING) {
		plcip->disconnecting = 1;
		plcip->localdisconnect = 1;
		return capiconn_reject(plcip);
	}
	if (plcip->plci) {
		plcip->disconnecting = 1;
		plcip->localdisconnect = 1;
		capi_fill_DISCONNECT_REQ(&cmdcmsg,
					 ctx->appid,
					 card->msgid++,
					 plcip->plci,
					 0,	/* BChannelinformation */
					 0,	/* Keypadfacility */
					 0,	/* Useruserdata */
					 0	/* Facilitydataarray */
					);
		plci_change_state(card, plcip, EV_PLCI_DISCONNECT_REQ);
		return send_message(card, &cmdcmsg);
	}
	return CAPICONN_WRONG_STATE;
}
EXPORT_SYMBOL(capiconn_disconnect);

static _cmsg sendcmsg;

int capiconn_send(capi_connection *plcip,
		  unsigned char *data,
		  unsigned len)
{
	capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;
	capiconn_callbacks *cb = ctx->cb;
	capi_ncci *nccip;
	struct sk_buff *skb;
	__u16 datahandle;
	__u16 mlen;

	nccip = plcip->nccip;
	if (!nccip || nccip->state != ST_NCCI_ACTIVE)
		return CAPICONN_WRONG_STATE;

	datahandle = nccip->datahandle;
	capi_fill_DATA_B3_REQ(&sendcmsg, ctx->appid, card->msgid++,
			      nccip->ncci,	/* adr */
			      (__u32)data,	/* Data */
			      len,		/* DataLength */
			      datahandle,	/* DataHandle */
			      0	/* Flags */
	    		);

	if (capi_add_ack(nccip, datahandle, data, len) != 0)
	   return CAPICONN_NOT_SENT;

	capi_cmsg2message(&sendcmsg, sendcmsg.buf);
	mlen = CAPIMSG_LEN(sendcmsg.buf);
    if ((skb = alloc_skb(mlen+len, GFP_ATOMIC)) == 0)
       return CAPICONN_NO_MEMORY;
    memcpy(skb_put(skb, mlen), sendcmsg.buf, mlen);
	memcpy(skb_put(skb, len), data, len);

    if ((*cb->capi_put_message) (ctx->appl, skb) != 0) {
	   capi_del_ack(nccip, datahandle, 0);
	   return CAPICONN_NOT_SENT;
	}
	nccip->datahandle++;
	ctx->nsentdatapkt++;
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_send);

int capiconn_dtmf_setstate(capi_connection *plcip, int on)
{
	__u8 fparam[32];
	int off;
	capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;
	capiconn_callbacks *cb = ctx->cb;

	capi_ncci *nccip;

	nccip = plcip->nccip;
	if (!nccip || nccip->state != ST_NCCI_ACTIVE)
		return CAPICONN_WRONG_STATE;

        if (cb->dtmf_received == 0)
		return CAPICONN_NOT_SUPPORTED;

	capi_cmsg_header(&sendcmsg, ctx->appid,
			 CAPI_FACILITY, CAPI_REQ, card->msgid++, nccip->ncci);
	sendcmsg.FacilitySelector = 1;
	off = 1;
	off = capimsg_addu16(fparam, off, on ? 1 : 2); /* Function */
    off = capimsg_addu16(fparam, off, 40);    /* Tone-duration */
    off = capimsg_addu16(fparam, off, 40); 	  /* Gap-dureation */ 
    off = capimsg_addcstruct(fparam, off, 0, 0); /* DTMF-digits */
    off = capimsg_addcstruct(fparam, off, 0, 0); /* DTMF-characteristics */
	fparam[0] = off;
	sendcmsg.FacilityRequestParameter = fparam;
    return send_message(card, &sendcmsg);
}
EXPORT_SYMBOL(capiconn_dtmf_setstate);

int capiconn_dtmf_send(capi_connection *plcip, char *digits)
{
	__u8 fparam[256];
	int off;
	capi_contr *card = plcip->contr;
	capiconn_context *ctx = card->ctx;

	capi_ncci *nccip;

	nccip = plcip->nccip;
	if (!nccip || nccip->state != ST_NCCI_ACTIVE)
		return CAPICONN_WRONG_STATE;

	capi_cmsg_header(&sendcmsg, ctx->appid,
			 CAPI_FACILITY, CAPI_REQ, card->msgid++, nccip->ncci);
	sendcmsg.FacilitySelector = 1;
	off = 1;
    off = capimsg_addu16(fparam, off, 3); /* Function */
    off = capimsg_addu16(fparam, off, 40);    /* Tone-duration */
    off = capimsg_addu16(fparam, off, 40); 	  /* Gap-dureation */ 
    off = capimsg_addcstruct(fparam, off, strlen(digits), digits);
    off = capimsg_addcstruct(fparam, off, 0, 0); /* DTMF-characteristics */
	fparam[0] = off-1;
	sendcmsg.FacilityRequestParameter = fparam;
    return send_message(card, &sendcmsg);
}
EXPORT_SYMBOL(capiconn_dtmf_send);

void capiconn_set_userdata(capi_connection *plcip, void *userdata)
{
   plcip->userdata = userdata;
}
EXPORT_SYMBOL(capiconn_set_userdata);

void *capiconn_get_userdata(capi_connection *plcip)
{
   return plcip->userdata;
}
EXPORT_SYMBOL(capiconn_get_userdata);

int capiconn_disconnecting(capi_connection *plcip)
{
   return plcip->disconnecting;
}
EXPORT_SYMBOL(capiconn_disconnecting);

static int send_onhold(capi_connection *plcip, unsigned long tone)
{
   capi_contr *card = plcip->contr;
   capiconn_context *ctx = card->ctx;
   __u8 fparam[256];
   int off;

   if (plcip->disconnecting)
	  return CAPICONN_WRONG_STATE;

   plcip->hold_or_unhold_in_progress = 1;
   plcip->hold_tone_sent = tone;

   printk(KERN_DEBUG "capiconn: plci 0x%x send hold request (tone=%lu)\n",
							  plcip->plci, tone);

   off = 1;
   off = capimsg_addu16(fparam, off, 0x0002); /* HOLD */
   if (tone == 0) {
      off = capimsg_addcstruct(fparam, off, 0, 0);
   } else {
      __u8 tparam[4];
      capimsg_addu32(tparam, 0, tone);
      off = capimsg_addcstruct(fparam, off, sizeof(tparam), tparam);
   }
   fparam[0] = off-1;
   
   capi_fill_FACILITY_REQ(&cmdcmsg, ctx->appid, card->msgid++,
				          plcip->plci, 3,
						  fparam);
   return send_message(card, &cmdcmsg);
}

static int send_offhold(capi_connection *plcip)
{
   capi_contr *card = plcip->contr;
   capiconn_context *ctx = card->ctx;
   __u8 fparam[256];
   int off;

   if (plcip->state != ST_PLCI_HELD)
	  return CAPICONN_WRONG_STATE;

   printk(KERN_DEBUG "capiconn: plci 0x%x send retrieve request\n", plcip->plci);
   
   off = 1;
   off = capimsg_addu16(fparam, off, 0x0003); /* RETRIEVE */
   off = capimsg_addcstruct(fparam, off, 0, 0);
   fparam[0] = off-1;
   capi_fill_FACILITY_REQ(&cmdcmsg, ctx->appid, card->msgid++,
				          plcip->plci, 3,
						  fparam);
   return send_message(card, &cmdcmsg);
}

int capiconn_onhold(capi_connection *plcip, unsigned long tone)
{
   if (plcip->hold_or_unhold_in_progress) {
      plcip->hold_state_wanted = 1;
      plcip->hold_tone_want = tone;
      printk(KERN_DEBUG "capiconn: plci 0x%x onhold tone=%ld queued\n", plcip->plci, tone);
      return CAPICONN_OK;
   }

   if (plcip->state == ST_PLCI_ACTIVE) {
      plcip->hold_state_wanted = 1;
      plcip->hold_tone_want = tone;
      return send_onhold(plcip, tone);
   } else if (plcip->state == ST_PLCI_HELD) {
	  if (plcip->hold_tone_want == tone) {
         printk(KERN_DEBUG "capiconn: plci 0x%x onhold keep tone=%ld\n", plcip->plci, tone);
		 return CAPICONN_OK;
	  }
	  plcip->hold_state_wanted = 1;
	  plcip->hold_tone_want = tone;
	  return send_offhold(plcip);
   } else {
      printk(KERN_DEBUG "capiconn: plci 0x%x onhold tone=%ld: wrong state\n",
					plcip->plci, tone);
	  return CAPICONN_WRONG_STATE;
   }

}
EXPORT_SYMBOL(capiconn_onhold);

int capiconn_offhold(capi_connection *plcip)
{
   if (plcip->hold_or_unhold_in_progress) {
      plcip->hold_state_wanted = 0;
      printk(KERN_DEBUG "capiconn: plci 0x%x offhold queued\n", plcip->plci);
      return CAPICONN_OK;
   }

   if (plcip->state != ST_PLCI_HELD) {
      printk(KERN_DEBUG "capiconn: plci 0x%x offhold: wrong state\n", plcip->plci);
	  return CAPICONN_WRONG_STATE;
   }

   plcip->hold_state_wanted = 0;
   return send_offhold(plcip);
}
EXPORT_SYMBOL(capiconn_offhold);

/* -------- listen handling ------------------------------------------ */

static void send_listen(capi_contr *card)
{
	capiconn_context *ctx = card->ctx;

	card->infomask = 0;
	card->infomask |= (1<<2); /* Display */
	card->infomask |= (1<<4); /* Call progress */
	card->infomask |= (1<<6); /* Charge Info */
	if (card->ddilen) card->infomask |= (1<<7); /* Called Party Number */
	card->infomask |= (1<<8); /* Channel Info */
	card->infomask |= (1<<9); /* Early B3 Connect */

	capi_fill_LISTEN_REQ(&cmdcmsg, ctx->appid,
			     card->msgid++,
			     card->contrnr,
			     card->infomask,
			     card->cipmask,
			     card->cipmask2,
			     0, 0);
	send_message(card, &cmdcmsg);
	listen_change_state(card, EV_LISTEN_REQ);
}

int
capiconn_listen(capiconn_context *ctx,
		unsigned contr, unsigned cipmask, unsigned cipmask2)
{
	capi_contr *card = findcontrbynumber(ctx, contr & 0x7f);

	if (card == 0)
		return CAPICONN_NO_CONTROLLER;

	card->cipmask = cipmask;	/* 0x1FFF03FF */
	card->cipmask2 = cipmask2;	/* 0 */

	send_listen(card);
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_listen);

int
capiconn_listenstate(capiconn_context *ctx, unsigned contr)
{
	capi_contr *card = findcontrbynumber(ctx, contr & 0x7f);

	if (card == 0)
		return CAPICONN_NO_CONTROLLER;
	if (card->state != ST_LISTEN_NONE && card->state != ST_LISTEN_ACTIVE)
		return CAPICONN_WRONG_STATE;
	return CAPICONN_OK;
}
EXPORT_SYMBOL(capiconn_listenstate);
