mISDN/drivers/isdn/hardware/mISDN/layer3.c

592 lines
12 KiB
C

/* $Id$
*
* Author Karsten Keil (keil@isdn4linux.de)
*
* This file is (c) under GNU PUBLIC LICENSE
* For changes and modifications please read
* ../../../Documentation/isdn/mISDN.cert
*
* Thanks to Jan den Ouden
* Fritz Elfert
*
*/
#define __NO_VERSION__
#include "layer3.h"
#include "helper.h"
const char *l3_revision = "$Revision$";
static
struct Fsm l3fsm = {NULL, 0, 0, NULL, NULL};
enum {
ST_L3_LC_REL,
ST_L3_LC_ESTAB_WAIT,
ST_L3_LC_REL_DELAY,
ST_L3_LC_REL_WAIT,
ST_L3_LC_ESTAB,
};
#define L3_STATE_COUNT (ST_L3_LC_ESTAB+1)
static char *strL3State[] =
{
"ST_L3_LC_REL",
"ST_L3_LC_ESTAB_WAIT",
"ST_L3_LC_REL_DELAY",
"ST_L3_LC_REL_WAIT",
"ST_L3_LC_ESTAB",
};
enum {
EV_ESTABLISH_REQ,
EV_ESTABLISH_IND,
EV_ESTABLISH_CNF,
EV_RELEASE_REQ,
EV_RELEASE_CNF,
EV_RELEASE_IND,
EV_TIMEOUT,
};
#define L3_EVENT_COUNT (EV_TIMEOUT+1)
static char *strL3Event[] =
{
"EV_ESTABLISH_REQ",
"EV_ESTABLISH_IND",
"EV_ESTABLISH_CNF",
"EV_RELEASE_REQ",
"EV_RELEASE_CNF",
"EV_RELEASE_IND",
"EV_TIMEOUT",
};
static void
l3m_debug(struct FsmInst *fi, char *fmt, ...)
{
layer3_t *l3 = fi->userdata;
logdata_t log;
va_start(log.args, fmt);
log.fmt = fmt;
log.head = l3->inst.name;
l3->inst.obj->ctrl(&l3->inst, MGR_DEBUGDATA | REQUEST, &log);
va_end(log.args);
}
void
l3_debug(layer3_t *l3, char *fmt, ...)
{
logdata_t log;
va_start(log.args, fmt);
log.fmt = fmt;
log.head = l3->inst.name;
l3->inst.obj->ctrl(&l3->inst, MGR_DEBUGDATA | REQUEST, &log);
va_end(log.args);
}
u_char *
findie(u_char * p, int size, u_char ie, int wanted_set)
{
int l, codeset, maincodeset;
u_char *pend = p + size;
/* skip protocol discriminator, callref and message type */
p++;
l = (*p++) & 0xf;
p += l;
p++;
codeset = 0;
maincodeset = 0;
/* while there are bytes left... */
while (p < pend) {
if ((*p & 0xf0) == 0x90) {
codeset = *p & 0x07;
if (!(*p & 0x08))
maincodeset = codeset;
}
if (codeset == wanted_set) {
if (*p == ie) {
/* improved length check (Werner Cornelius) */
if (!(*p & 0x80)) {
if ((pend - p) < 2)
return(NULL);
if (*(p+1) > (pend - (p+2)))
return(NULL);
p++; /* points to len */
}
return (p);
} else if ((*p > ie) && !(*p & 0x80))
return (NULL);
}
if (!(*p & 0x80)) {
p++;
l = *p;
p += l;
codeset = maincodeset;
}
p++;
}
return (NULL);
}
int
getcallref(u_char * p)
{
int l, cr = 0;
p++; /* prot discr */
if (*p & 0xfe) /* wrong callref BRI only 1 octet*/
return(-2);
l = 0xf & *p++; /* callref length */
if (!l) /* dummy CallRef */
return(-1);
cr = *p++;
return (cr);
}
static int OrigCallRef = 0;
int
newcallref(void)
{
if (OrigCallRef == 127)
OrigCallRef = 1;
else
OrigCallRef++;
return (OrigCallRef);
}
void
newl3state(l3_process_t *pc, int state)
{
if (pc->l3->debug & L3_DEB_STATE)
l3m_debug(&pc->l3->l3m, "newstate cr %d %d --> %d",
pc->callref & 0x7F,
pc->state, state);
pc->state = state;
}
static void
L3ExpireTimer(L3Timer_t *t)
{
t->pc->l3->p_mgr(t->pc, t->event, NULL);
}
void
L3InitTimer(l3_process_t *pc, L3Timer_t *t)
{
t->pc = pc;
t->tl.function = (void *) L3ExpireTimer;
t->tl.data = (long) t;
init_timer(&t->tl);
}
void
L3DelTimer(L3Timer_t *t)
{
del_timer(&t->tl);
}
int
L3AddTimer(L3Timer_t *t,
int millisec, int event)
{
if (timer_pending(&t->tl)) {
printk(KERN_WARNING "L3AddTimer: timer already active!\n");
return -1;
}
init_timer(&t->tl);
t->event = event;
t->tl.expires = jiffies + (millisec * HZ) / 1000;
add_timer(&t->tl);
return 0;
}
void
StopAllL3Timer(l3_process_t *pc)
{
L3DelTimer(&pc->timer);
if (pc->t303skb) {
dev_kfree_skb(pc->t303skb);
pc->t303skb = NULL;
}
}
/*
static void
no_l3_proto(struct PStack *st, int pr, void *arg)
{
struct sk_buff *skb = arg;
mISDN_putstatus(st->l1.hardware, "L3", "no D protocol");
if (skb) {
dev_kfree_skb(skb);
}
}
static int
no_l3_proto_spec(struct PStack *st, isdn_ctrl *ic)
{
printk(KERN_WARNING "mISDN: no specific protocol handler for proto %lu\n",ic->arg & 0xFF);
return(-1);
}
*/
l3_process_t
*getl3proc(layer3_t *l3, int cr)
{
l3_process_t *p = l3->proc;
while (p)
if (p->callref == cr)
return (p);
else
p = p->next;
return (NULL);
}
l3_process_t
*getl3proc4id(layer3_t *l3, u_int id)
{
l3_process_t *p = l3->proc;
while (p)
if (p->id == id)
return (p);
else
p = p->next;
return (NULL);
}
l3_process_t
*new_l3_process(layer3_t *l3, int cr, int n303, u_int id)
{
l3_process_t *p = NULL;
if (id == MISDN_ID_ANY) {
if (l3->entity == MISDN_ENTITY_NONE) {
printk(KERN_WARNING "%s: no entity allocated for l3(%x)\n",
__FUNCTION__, l3->id);
return (NULL);
}
if (l3->id_cnt == 0xFFFF)
l3->id_cnt = 0;
while(l3->id_cnt <= 0xFFFF) {
l3->id_cnt++;
id = l3->id_cnt | (l3->entity << 16);
p = getl3proc4id(l3, id);
if (!p)
break;
}
if (p) {
printk(KERN_WARNING "%s: no free process_id for l3(%x) entity(%x)\n",
__FUNCTION__, l3->id, l3->entity);
return (NULL);
}
} else {
/* id from other entity */
p = getl3proc4id(l3, id);
if (p) {
printk(KERN_WARNING "%s: process_id(%x) allready in use in l3(%x)\n",
__FUNCTION__, id, l3->id);
return (NULL);
}
}
if (!(p = kmalloc(sizeof(l3_process_t), GFP_ATOMIC))) {
printk(KERN_ERR "mISDN can't get memory for cr %d\n", cr);
return (NULL);
}
memset(p, 0, sizeof(l3_process_t));
p->l3 = l3;
p->id = id;
p->callref = cr;
p->n303 = n303;
L3InitTimer(p, &p->timer);
APPEND_TO_LIST(p, l3->proc);
return (p);
};
void
release_l3_process(l3_process_t *p)
{
layer3_t *l3;
if (!p)
return;
l3 = p->l3;
mISDN_l3up(p, CC_RELEASE_CR | INDICATION, NULL);
REMOVE_FROM_LISTBASE(p, l3->proc);
StopAllL3Timer(p);
kfree(p);
if (!l3->proc && !test_bit(FLG_PTP, &l3->Flag)) {
if (l3->debug)
l3_debug(l3, "release_l3_process: last process");
if (!skb_queue_len(&l3->squeue)) {
if (l3->debug)
l3_debug(l3, "release_l3_process: release link");
FsmEvent(&l3->l3m, EV_RELEASE_REQ, NULL);
} else {
if (l3->debug)
l3_debug(l3, "release_l3_process: not release link");
}
}
};
static void
l3ml3p(layer3_t *l3, int pr)
{
l3_process_t *p = l3->proc;
l3_process_t *np;
while (p) {
/* p might be kfreed under us, so we need to save where we want to go on */
np = p->next;
l3->p_mgr(p, pr, NULL);
p = np;
}
}
int
mISDN_l3up(l3_process_t *l3p, u_int prim, struct sk_buff *skb)
{
layer3_t *l3;
int err = -EINVAL;
if (!l3p)
return(-EINVAL);
l3 = l3p->l3;
if (!skb)
err = if_link(&l3->inst.up, prim, l3p->id, 0, NULL, 0);
else
err = if_newhead(&l3->inst.up, prim, l3p->id, skb);
return(err);
}
static int
l3down(layer3_t *l3, u_int prim, int dinfo, struct sk_buff *skb) {
int err = -EINVAL;
if (!skb)
err = if_link(&l3->inst.down, prim, dinfo, 0, NULL, 0);
else
err = if_newhead(&l3->inst.down, prim, dinfo, skb);
return(err);
}
#define DREL_TIMER_VALUE 40000
static void
lc_activate(struct FsmInst *fi, int event, void *arg)
{
layer3_t *l3 = fi->userdata;
FsmChangeState(fi, ST_L3_LC_ESTAB_WAIT);
l3down(l3, DL_ESTABLISH | REQUEST, 0, NULL);
}
static void
lc_connect(struct FsmInst *fi, int event, void *arg)
{
layer3_t *l3 = fi->userdata;
struct sk_buff *skb;
int dequeued = 0;
FsmChangeState(fi, ST_L3_LC_ESTAB);
while ((skb = skb_dequeue(&l3->squeue))) {
if (l3down(l3, DL_DATA | REQUEST, DINFO_SKB, skb))
dev_kfree_skb(skb);
dequeued++;
}
if ((!l3->proc) && dequeued) {
if (l3->debug)
l3m_debug(fi, "lc_connect: release link");
FsmEvent(&l3->l3m, EV_RELEASE_REQ, NULL);
} else
l3ml3p(l3, DL_ESTABLISH | INDICATION);
}
static void
lc_connected(struct FsmInst *fi, int event, void *arg)
{
layer3_t *l3 = fi->userdata;
struct sk_buff *skb;
int dequeued = 0;
FsmDelTimer(&l3->l3m_timer, 51);
FsmChangeState(fi, ST_L3_LC_ESTAB);
while ((skb = skb_dequeue(&l3->squeue))) {
if (l3down(l3, DL_DATA | REQUEST, DINFO_SKB, skb))
dev_kfree_skb(skb);
dequeued++;
}
if ((!l3->proc) && dequeued) {
if (l3->debug)
l3m_debug(fi, "lc_connected: release link");
FsmEvent(&l3->l3m, EV_RELEASE_REQ, NULL);
} else
l3ml3p(l3, DL_ESTABLISH | CONFIRM);
}
static void
lc_start_delay(struct FsmInst *fi, int event, void *arg)
{
layer3_t *l3 = fi->userdata;
FsmChangeState(fi, ST_L3_LC_REL_DELAY);
FsmAddTimer(&l3->l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50);
}
static void
lc_release_req(struct FsmInst *fi, int event, void *arg)
{
layer3_t *l3 = fi->userdata;
if (test_bit(FLG_L2BLOCK, &l3->Flag)) {
if (l3->debug)
l3m_debug(fi, "lc_release_req: l2 blocked");
/* restart release timer */
FsmAddTimer(&l3->l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 51);
} else {
FsmChangeState(fi, ST_L3_LC_REL_WAIT);
l3down(l3, DL_RELEASE | REQUEST, 0, NULL);
}
}
static void
lc_release_ind(struct FsmInst *fi, int event, void *arg)
{
layer3_t *l3 = fi->userdata;
FsmDelTimer(&l3->l3m_timer, 52);
FsmChangeState(fi, ST_L3_LC_REL);
discard_queue(&l3->squeue);
l3ml3p(l3, DL_RELEASE | INDICATION);
}
static void
lc_release_cnf(struct FsmInst *fi, int event, void *arg)
{
layer3_t *l3 = fi->userdata;
FsmChangeState(fi, ST_L3_LC_REL);
discard_queue(&l3->squeue);
l3ml3p(l3, DL_RELEASE | CONFIRM);
}
/* *INDENT-OFF* */
static struct FsmNode L3FnList[] =
{
{ST_L3_LC_REL, EV_ESTABLISH_REQ, lc_activate},
{ST_L3_LC_REL, EV_ESTABLISH_IND, lc_connect},
{ST_L3_LC_REL, EV_ESTABLISH_CNF, lc_connect},
{ST_L3_LC_ESTAB_WAIT, EV_ESTABLISH_CNF, lc_connected},
{ST_L3_LC_ESTAB_WAIT, EV_RELEASE_REQ, lc_start_delay},
{ST_L3_LC_ESTAB_WAIT, EV_RELEASE_IND, lc_release_ind},
{ST_L3_LC_ESTAB, EV_RELEASE_IND, lc_release_ind},
{ST_L3_LC_ESTAB, EV_RELEASE_REQ, lc_start_delay},
{ST_L3_LC_REL_DELAY, EV_RELEASE_IND, lc_release_ind},
{ST_L3_LC_REL_DELAY, EV_ESTABLISH_REQ, lc_connected},
{ST_L3_LC_REL_DELAY, EV_TIMEOUT, lc_release_req},
{ST_L3_LC_REL_WAIT, EV_RELEASE_CNF, lc_release_cnf},
{ST_L3_LC_REL_WAIT, EV_ESTABLISH_REQ, lc_activate},
};
/* *INDENT-ON* */
#define L3_FN_COUNT (sizeof(L3FnList)/sizeof(struct FsmNode))
int
l3_msg(layer3_t *l3, u_int pr, int dinfo, int len, void *arg)
{
switch (pr) {
case (DL_DATA | REQUEST):
if (l3->l3m.state == ST_L3_LC_ESTAB) {
return(l3down(l3, pr, dinfo, arg));
} else {
struct sk_buff *skb = arg;
// printk(KERN_DEBUG "%s: queue skb %p len(%d)\n",
// __FUNCTION__, skb, skb->len);
skb_queue_tail(&l3->squeue, skb);
FsmEvent(&l3->l3m, EV_ESTABLISH_REQ, NULL);
}
break;
case (DL_ESTABLISH | REQUEST):
FsmEvent(&l3->l3m, EV_ESTABLISH_REQ, NULL);
break;
case (DL_ESTABLISH | CONFIRM):
FsmEvent(&l3->l3m, EV_ESTABLISH_CNF, NULL);
break;
case (DL_ESTABLISH | INDICATION):
FsmEvent(&l3->l3m, EV_ESTABLISH_IND, NULL);
break;
case (DL_RELEASE | INDICATION):
FsmEvent(&l3->l3m, EV_RELEASE_IND, NULL);
break;
case (DL_RELEASE | CONFIRM):
FsmEvent(&l3->l3m, EV_RELEASE_CNF, NULL);
break;
case (DL_RELEASE | REQUEST):
FsmEvent(&l3->l3m, EV_RELEASE_REQ, NULL);
break;
}
return(0);
}
void
init_l3(layer3_t *l3)
{
l3->proc = NULL;
l3->global = NULL;
l3->dummy = NULL;
l3->entity = MISDN_ENTITY_NONE;
skb_queue_head_init(&l3->squeue);
l3->l3m.fsm = &l3fsm;
l3->l3m.state = ST_L3_LC_REL;
l3->l3m.debug = 1;
l3->l3m.userdata = l3;
l3->l3m.userint = 0;
l3->l3m.printdebug = l3m_debug;
FsmInitTimer(&l3->l3m, &l3->l3m_timer);
}
void
release_l3(layer3_t *l3)
{
printk(KERN_DEBUG "release_l3(%p) proc(%p) global(%p) dummy(%p)\n",
l3, l3->proc, l3->global, l3->dummy);
while (l3->proc)
release_l3_process(l3->proc);
if (l3->global) {
StopAllL3Timer(l3->global);
kfree(l3->global);
l3->global = NULL;
}
if (l3->dummy) {
StopAllL3Timer(l3->dummy);
kfree(l3->dummy);
l3->dummy = NULL;
}
FsmDelTimer(&l3->l3m_timer, 54);
discard_queue(&l3->squeue);
}
void
mISDNl3New(void)
{
l3fsm.state_count = L3_STATE_COUNT;
l3fsm.event_count = L3_EVENT_COUNT;
l3fsm.strEvent = strL3Event;
l3fsm.strState = strL3State;
FsmNew(&l3fsm, L3FnList, L3_FN_COUNT);
}
void
mISDNl3Free(void)
{
FsmFree(&l3fsm);
}