1052 lines
26 KiB
C
1052 lines
26 KiB
C
/* layer3.c
|
|
*
|
|
* Basic Layer3 functions
|
|
*
|
|
* Author Karsten Keil <kkeil@linux-pingi.de>
|
|
*
|
|
* Copyright 2007 by Karsten Keil <kkeil@novell.com>
|
|
* Copyright 2010 by Karsten Keil <kkeil@linux-pingi.de>
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
|
|
* version 2.1 as published by the Free Software Foundation.
|
|
*
|
|
* This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <errno.h>
|
|
#include <mISDN/q931.h>
|
|
#include <mISDN/mlayer3.h>
|
|
#include "layer3.h"
|
|
#include "debug.h"
|
|
#include "../../libmisdn/socket.h"
|
|
|
|
void
|
|
mIl3_debug(layer3_t *l3, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char buf[256], *p;
|
|
|
|
va_start(args, fmt);
|
|
p = buf;
|
|
p += sprintf(p, "l3 ");
|
|
p += vsprintf(p, fmt, args);
|
|
va_end(args);
|
|
dprint(DBGM_L3, "port%d %s\n", l3->l2master.l2addr.dev, buf);
|
|
}
|
|
|
|
void
|
|
mIpc_debug(u_int dmask, l3_process_t *pc, const char *fmt, ...)
|
|
{
|
|
struct _layer3 *l3;
|
|
va_list args;
|
|
char buf[256], *p;
|
|
|
|
if (!(mI_debug_mask & dmask))
|
|
return;
|
|
if (!pc)
|
|
return;
|
|
l3 = pc->L3;
|
|
if (!l3)
|
|
return;
|
|
va_start(args, fmt);
|
|
p = buf;
|
|
p += sprintf(p, "pc-%08x ", pc->pid);
|
|
p += vsprintf(p, fmt, args);
|
|
va_end(args);
|
|
dprint(DBGM_L3, "port%d %s\n", l3->l2master.l2addr.dev, buf);
|
|
}
|
|
|
|
static void
|
|
l3fi_debug(struct FsmInst *fi, const char *fmt, ...)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
layer3_t *l3 = l2i->l3;
|
|
|
|
va_list args;
|
|
char buf[256], *p;
|
|
|
|
va_start(args, fmt);
|
|
p = buf;
|
|
p += sprintf(p, "l3 ");
|
|
p += vsprintf(p, fmt, args);
|
|
va_end(args);
|
|
dprint(DBGM_L3, "port%d %s\n", l3->l2master.l2addr.dev, buf);
|
|
}
|
|
|
|
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 const 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 const char *strL3Event[] =
|
|
{
|
|
"EV_ESTABLISH_REQ",
|
|
"EV_ESTABLISH_IND",
|
|
"EV_ESTABLISH_CNF",
|
|
"EV_RELEASE_REQ",
|
|
"EV_RELEASE_CNF",
|
|
"EV_RELEASE_IND",
|
|
"EV_TIMEOUT",
|
|
};
|
|
|
|
l3_process_t *
|
|
get_l3process4pid(layer3_t *l3, u_int pid)
|
|
{
|
|
l3_process_t *p, *cp;
|
|
|
|
if ((pid & 0xffff7fff) == 0) /* global CR */
|
|
return &l3->global;
|
|
if (pid == MISDN_PID_GLOBAL)
|
|
return &l3->global;
|
|
if (pid == MISDN_PID_DUMMY)
|
|
return &l3->dummy;
|
|
list_for_each_entry(p, &l3->plist, list) {
|
|
if (p->pid == pid)
|
|
return p;
|
|
if (!list_empty(&p->child)) {
|
|
list_for_each_entry(cp, &p->child, list) {
|
|
if (cp->pid == pid)
|
|
return cp;
|
|
}
|
|
}
|
|
if ((p->pid & MISDN_PID_CRVAL_MASK) == (pid & MISDN_PID_CRVAL_MASK)) {
|
|
if ((p->pid & MISDN_PID_CRTYPE_MASK) == MISDN_PID_MASTER)
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
l3_process_t *
|
|
get_l3process4cref(layer3_t *l3, u_int cr)
|
|
{
|
|
l3_process_t *p;
|
|
|
|
if ((cr & 0x00007fff) == 0) /* global CR */
|
|
return &l3->global;
|
|
list_for_each_entry(p, &l3->plist, list)
|
|
if ((p->pid & MISDN_PID_CRVAL_MASK) == (cr & MISDN_PID_CRVAL_MASK))
|
|
return p;
|
|
return NULL;
|
|
}
|
|
|
|
l3_process_t *
|
|
get_first_l3process4ces(layer3_t *l3, unsigned int ces)
|
|
{
|
|
l3_process_t *p;
|
|
|
|
if (ces == MISDN_CES_MASTER)
|
|
return NULL;
|
|
list_for_each_entry(p, &l3->plist, list)
|
|
if (((unsigned int)p->pid >> 16) == ces)
|
|
return p;
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
L3TimerFunction(void *arg)
|
|
{
|
|
struct L3Timer *l3t = arg;
|
|
l3_process_t *pc;
|
|
unsigned int nr = l3t->nr;
|
|
|
|
l3t->nr = 0;
|
|
pc = get_l3process4pid(l3t->l3, l3t->pid);
|
|
if (pc)
|
|
l3t->l3->p_mgr(pc, nr, NULL);
|
|
}
|
|
|
|
static void
|
|
L3TimerInit(layer3_t *l3, int pid, struct L3Timer *t)
|
|
{
|
|
init_timer(&t->tl, &l3->tbase, t, L3TimerFunction);
|
|
t->pid = pid;
|
|
t->nr = 0;
|
|
t->l3 = l3;
|
|
}
|
|
|
|
void
|
|
L3AddTimer(struct L3Timer *t, int delay, unsigned int nr)
|
|
{
|
|
if (t->nr) {
|
|
eprint("%s: timer %x reused as %x\n", __FUNCTION__, t->nr, nr);
|
|
del_timer(&t->tl);
|
|
}
|
|
t->nr = nr;
|
|
add_timer(&t->tl, delay);
|
|
}
|
|
|
|
void
|
|
L3DelTimer(struct L3Timer *t)
|
|
{
|
|
if (t->nr) {
|
|
del_timer(&t->tl);
|
|
t->nr = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
StopAllL3Timer(struct _l3_process *pc)
|
|
{
|
|
L3DelTimer(&pc->timer1);
|
|
L3DelTimer(&pc->timer2);
|
|
free_l3_msg(pc->t303msg);
|
|
pc->t303msg = NULL;
|
|
test_and_clear_bit(FLG_L3P_TIMER303_1, &pc->flags);
|
|
test_and_clear_bit(FLG_L3P_TIMER308_1, &pc->flags);
|
|
test_and_clear_bit(FLG_L3P_TIMER312, &pc->flags);
|
|
}
|
|
|
|
static struct l2l3if *
|
|
get_l2if(layer3_t *l3, unsigned int ces)
|
|
{
|
|
struct l2l3if *l2i;
|
|
|
|
if (ces == MISDN_CES_MASTER)
|
|
return &l3->l2master;
|
|
if (ces == l3->l2master.l2addr.channel)
|
|
return &l3->l2master;
|
|
list_for_each_entry(l2i, &l3->l2master.list, list) {
|
|
if (ces == l2i->l2addr.channel)
|
|
return l2i;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
l3_process_t *
|
|
create_new_process(layer3_t *l3, unsigned int ces, unsigned int cr, l3_process_t *master)
|
|
{
|
|
l3_process_t *pc;
|
|
unsigned int max_cr = 0x7fff;
|
|
int try;
|
|
|
|
|
|
if ((cr & 0xffff) > 0) { /* remote owned callref */
|
|
pc = get_l3process4pid(l3, ((ces & 0xff) << 16) | cr);
|
|
if (pc && (pc != master)) /* already here */
|
|
return NULL;
|
|
} else {
|
|
if (test_bit(FLG_BASICRATE, &l3->ml3.options))
|
|
max_cr = 0x7f;
|
|
for (try = 0; try <= l3->ml3.nr_bchannel; try++) { /* more tries for more channels */
|
|
cr = l3->next_cr++;
|
|
if (l3->next_cr > max_cr)
|
|
l3->next_cr = 1;
|
|
if (!get_l3process4cref(l3, cr))
|
|
break;
|
|
}
|
|
if (get_l3process4cref(l3, cr))
|
|
return NULL;
|
|
cr |= MISDN_PID_CR_FLAG; /* we own it */
|
|
}
|
|
pc = calloc(1, sizeof(l3_process_t));
|
|
if (!pc) {
|
|
eprint("%s: no memory for layer3 process\n", __FUNCTION__);
|
|
return NULL;
|
|
}
|
|
pc->l2if = get_l2if(l3, ces);
|
|
if (ces == MISDN_CES_MASTER) {
|
|
if (test_bit(FLG_USER, &l3->ml3.options) && !test_bit(MISDN_FLG_PTP, &l3->ml3.options)) {
|
|
if (list_empty(&l3->l2master.list)) {
|
|
eprint("%s: no layer2 assigned\n", __FUNCTION__);
|
|
pc->l2if = NULL;
|
|
} else
|
|
pc->l2if = (struct l2l3if *)l3->l2master.list.next;
|
|
}
|
|
}
|
|
if (!pc->l2if) {
|
|
eprint("%s: no layer2 if found for ces %x\n", __FUNCTION__, ces);
|
|
free(pc);
|
|
return NULL;
|
|
}
|
|
pc->L3 = l3;
|
|
pc->pid = ((ces & 0xffff) << 16) | cr;
|
|
L3TimerInit(l3, pc->pid, &pc->timer1);
|
|
L3TimerInit(l3, pc->pid, &pc->timer2);
|
|
INIT_LIST_HEAD(&pc->child);
|
|
pc->master = master;
|
|
if (master)
|
|
list_add_tail(&pc->list, &master->child);
|
|
else
|
|
list_add_tail(&pc->list, &l3->plist);
|
|
return pc;
|
|
}
|
|
|
|
void
|
|
SendMsg(l3_process_t *pc, struct l3_msg *l3m, int state) {
|
|
int ret;
|
|
struct mbuffer *mb = container_of(l3m, struct mbuffer, l3);
|
|
|
|
|
|
ret = assembleQ931(pc, l3m);
|
|
if (ret) {
|
|
eprint("%s assembleQ931 error %x\n", __FUNCTION__, ret);
|
|
free_l3_msg(l3m);
|
|
return;
|
|
}
|
|
if (state != -1)
|
|
newl3state(pc, state);
|
|
mb->h->id = l3m->pid;
|
|
msg_push(mb, MISDN_HEADER_LEN);
|
|
if ((l3m->type == MT_SETUP) && test_bit(FLG_NETWORK, &pc->l2if->l3->ml3.options) &&
|
|
!test_bit(MISDN_FLG_PTP, &pc->l2if->l3->ml3.options))
|
|
mb->h->prim = DL_UNITDATA_REQ;
|
|
else
|
|
mb->h->prim = DL_DATA_REQ;
|
|
mb->addr = pc->l2if->l2addr;
|
|
mqueue_tail(&pc->l2if->squeue, mb);
|
|
if (pc->l2if->l3m.state != ST_L3_LC_ESTAB)
|
|
l3_manager(pc->l2if, DL_ESTABLISH_REQ);
|
|
}
|
|
|
|
void
|
|
release_l3_process(l3_process_t *pc)
|
|
{
|
|
layer3_t *l3;
|
|
struct l2l3if *l2i;
|
|
unsigned int ces;
|
|
|
|
if (!pc)
|
|
return;
|
|
l2i = pc->l2if;
|
|
l3 = l2i->l3;
|
|
ces = (pc->pid >> 16);
|
|
mISDN_l3up(pc, MT_FREE, NULL);
|
|
list_del(&pc->list);
|
|
StopAllL3Timer(pc);
|
|
free(pc);
|
|
pc = get_first_l3process4ces(l3, ces);
|
|
if ((!pc) && !test_bit(MISDN_FLG_L2_HOLD, &l3->ml3.options)) {
|
|
dprint(DBGM_L3, "port%d: tei %d idle\n", l3->l2master.l2addr.dev, l2i->l2addr.tei);
|
|
l2i->l3->ml3.from_layer3(&l2i->l3->ml3, MT_L2IDLE, l2i->l2addr.tei, NULL);
|
|
// if (!mqueue_len(&l2i->squeue)) {
|
|
// FsmEvent(&l2i->l3m, EV_RELEASE_REQ, NULL);
|
|
// }
|
|
}
|
|
}
|
|
|
|
static void
|
|
l3ml3p(layer3_t *l3, int pr, unsigned int ces)
|
|
{
|
|
l3_process_t *p, *np;
|
|
unsigned int p_ces;
|
|
|
|
list_for_each_entry_safe(p, np, &l3->plist, list) {
|
|
p_ces = (p->pid >> 16) & 0xffff;
|
|
dprint(DBGM_L2, "port%d: pr %s tei:%d pid %x ces %x/%x\n",
|
|
l3->l2master.l2addr.dev, _mi_msg_type2str(pr),
|
|
l3->l2master.l2addr.tei, p->pid, ces, p_ces);
|
|
if ((p_ces == ces) || (p_ces == MISDN_CES_MASTER && test_bit(FLG_USER, &l3->ml3.options))) {
|
|
dprint(L3_DEB_PROC, "port%d: send to l3proc pid=%x\n",
|
|
l3->l2master.l2addr.dev, p->pid);
|
|
l3->p_mgr(p, pr, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
mISDN_l3up(l3_process_t *l3p, u_int prim, struct l3_msg *l3m)
|
|
{
|
|
int ret;
|
|
|
|
if (!l3p->L3) {
|
|
eprint("%s no L3 for l3p(%p) pid(%x)\n", __FUNCTION__, l3p, l3p->pid);
|
|
return;
|
|
}
|
|
ret = l3p->L3->ml3.from_layer3(&l3p->L3->ml3, prim, l3p->pid, l3m);
|
|
if (ret) {
|
|
eprint("%s cannot deliver mesage %x process %x to application\n", __FUNCTION__, prim, l3p->pid);
|
|
if (l3m)
|
|
free_l3_msg(l3m);
|
|
}
|
|
}
|
|
|
|
static void
|
|
l3down(struct l2l3if *l2i, u_int prim, struct mbuffer *mb)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!mb) {
|
|
mb = alloc_mbuffer();
|
|
if (!mb) {
|
|
eprint("%s cannot alloc mbuffer for %x\n", __FUNCTION__, prim);
|
|
return;
|
|
}
|
|
msg_put(mb, MISDN_HEADER_LEN);
|
|
mb->h->prim = prim;
|
|
mb->h->id = 0;
|
|
mb->addr = l2i->l2addr;
|
|
}
|
|
dprint(DBGM_L3DATA, "port%d send %s to tei %d\n",
|
|
l2i->l3->l2master.l2addr.dev,
|
|
_mi_msg_type2str(prim), l2i->l2addr.tei);
|
|
if (l2i->l3->l2sock)
|
|
ret = sendto(l2i->l3->l2sock, mb->head, mb->len, 0, (struct sockaddr *)&mb->addr, sizeof(mb->addr));
|
|
if (l2i->l3->l2inst)
|
|
ret = mISDN_data_sendmsg(l2i->l3->l2inst, mb->head, mb->len, 0, &mb->addr);
|
|
if (ret < 0)
|
|
eprint("%s write socket error %s\n", __FUNCTION__, strerror(errno));
|
|
free_mbuffer(mb);
|
|
}
|
|
|
|
#define DREL_TIMER_VALUE 10000
|
|
|
|
static void
|
|
lc_activate(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
|
|
FsmChangeState(fi, ST_L3_LC_ESTAB_WAIT);
|
|
l3down(l2i, DL_ESTABLISH_REQ, NULL);
|
|
}
|
|
|
|
static void
|
|
lc_connect(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
struct mbuffer *mb;
|
|
int dequeued = 0;
|
|
|
|
FsmChangeState(fi, ST_L3_LC_ESTAB);
|
|
while ((mb = mdequeue(&l2i->squeue))) {
|
|
l3down(l2i, DL_DATA_REQ, mb);
|
|
dequeued++;
|
|
}
|
|
dprint(DBGM_L3DATA, "port%d sent %d messages to L2\n", l2i->l3->l2master.l2addr.dev, dequeued);
|
|
// pc = get_first_l3process4ces(l2i->l3, l2i->l2addr.channel);
|
|
// if ((!pc) && (!test_bit(MISDN_FLG_L2_HOLD, &l2i->l3->ml3.options)) && dequeued) {
|
|
// FsmEvent(fi, EV_RELEASE_REQ, NULL);
|
|
// } else {
|
|
// l3ml3p(l2i->l3, DL_ESTABLISH_IND, l2i->l2addr.channel);
|
|
// }
|
|
l3ml3p(l2i->l3, DL_ESTABLISH_IND, l2i->l2addr.channel);
|
|
l2i->l3->ml3.from_layer3(&l2i->l3->ml3, MT_L2ESTABLISH, l2i->l2addr.tei, NULL);
|
|
}
|
|
|
|
static void
|
|
lc_connected(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
struct mbuffer *mb;
|
|
int dequeued = 0;
|
|
|
|
FsmDelTimer(&l2i->l3m_timer, 51);
|
|
FsmChangeState(fi, ST_L3_LC_ESTAB);
|
|
while ((mb = mdequeue(&l2i->squeue))) {
|
|
l3down(l2i, DL_DATA_REQ, mb);
|
|
dequeued++;
|
|
}
|
|
dprint(DBGM_L3DATA, "port%d sent %d messages to L2\n", l2i->l3->l2master.l2addr.dev, dequeued);
|
|
// pc = get_first_l3process4ces(l2i->l3, l2i->l2addr.channel);
|
|
// if ((!pc) && (!test_bit(MISDN_FLG_L2_HOLD, &l2i->l3->ml3.options)) && dequeued) {
|
|
// FsmEvent(fi, EV_RELEASE_REQ, NULL);
|
|
// } else {
|
|
// l3ml3p(l2i->l3, DL_ESTABLISH_IND, l2i->l2addr.channel);
|
|
// }
|
|
l3ml3p(l2i->l3, DL_ESTABLISH_IND, l2i->l2addr.channel);
|
|
l2i->l3->ml3.from_layer3(&l2i->l3->ml3, MT_L2ESTABLISH, l2i->l2addr.tei, NULL);
|
|
}
|
|
|
|
static void
|
|
lc_start_delay(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
|
|
FsmChangeState(fi, ST_L3_LC_REL_DELAY);
|
|
FsmAddTimer(&l2i->l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50);
|
|
}
|
|
|
|
static void
|
|
lc_release_req(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
|
|
if (test_bit(FLG_L2BLOCK, &l2i->l3->ml3.options)) {
|
|
/* restart release timer */
|
|
FsmAddTimer(&l2i->l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 51);
|
|
} else {
|
|
FsmChangeState(fi, ST_L3_LC_REL_WAIT);
|
|
l3down(l2i, DL_RELEASE_REQ, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
lc_release_ind(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
|
|
FsmDelTimer(&l2i->l3m_timer, 52);
|
|
FsmChangeState(fi, ST_L3_LC_REL);
|
|
mqueue_purge(&l2i->squeue);
|
|
l3ml3p(l2i->l3, DL_RELEASE_IND, l2i->l2addr.channel);
|
|
l2i->l3->ml3.from_layer3(&l2i->l3->ml3, MT_L2RELEASE, l2i->l2addr.tei, NULL);
|
|
}
|
|
|
|
static void
|
|
lc_release_cnf(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
struct l2l3if *l2i = fi->userdata;
|
|
|
|
FsmChangeState(fi, ST_L3_LC_REL);
|
|
mqueue_purge(&l2i->squeue);
|
|
l3ml3p(l2i->l3, DL_RELEASE_CNF, l2i->l2addr.channel);
|
|
l2i->l3->ml3.from_layer3(&l2i->l3->ml3, MT_L2RELEASE, l2i->l2addr.tei, NULL);
|
|
}
|
|
|
|
|
|
/* *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_IND, lc_connected},
|
|
{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))
|
|
|
|
|
|
void
|
|
l3_manager(struct l2l3if *l2i, unsigned int pr)
|
|
{
|
|
switch (pr) {
|
|
case DL_ESTABLISH_REQ:
|
|
FsmEvent(&l2i->l3m, EV_ESTABLISH_REQ, NULL);
|
|
break;
|
|
case DL_RELEASE_REQ:
|
|
FsmEvent(&l2i->l3m, EV_RELEASE_REQ, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsigned int
|
|
request_new_pid(struct mlayer3 *ml3)
|
|
{
|
|
layer3_t *l3 = container_of(ml3, layer3_t, ml3);
|
|
l3_process_t *proc;
|
|
|
|
proc = create_new_process(l3, MISDN_CES_MASTER, 0, NULL);
|
|
return proc ? proc->pid : MISDN_PID_NONE;
|
|
}
|
|
|
|
static int
|
|
to_layer3(struct mlayer3 *ml3, unsigned int prim, unsigned int pid, struct l3_msg *l3m)
|
|
{
|
|
layer3_t *l3;
|
|
int ret, id;
|
|
l3_process_t *proc;
|
|
struct mbuffer *mb;
|
|
struct mqueue *q = NULL;
|
|
|
|
l3 = container_of(ml3, layer3_t, ml3);
|
|
switch(prim) {
|
|
case MT_ASSIGN:
|
|
eprint("%s: MT_ASSIGN via %s is depreciated - use new request_new_pid(struct mlayer3 *)\n", __func__, __func__);
|
|
proc = create_new_process(l3, MISDN_CES_MASTER, 0, NULL);
|
|
if (!proc)
|
|
return -EBUSY;
|
|
dprint(DBGM_L3, "port%d: new procpid(%x)\n",
|
|
l3->l2master.l2addr.dev, proc->pid);
|
|
ml3->from_layer3(ml3, MT_ASSIGN, proc->pid, NULL);
|
|
break;
|
|
case MT_L2ESTABLISH:
|
|
case MT_L2RELEASE:
|
|
q = &l3->mgr_queue;
|
|
break;
|
|
case MT_ALERTING:
|
|
case MT_CALL_PROCEEDING:
|
|
case MT_CONNECT:
|
|
case MT_CONNECT_ACKNOWLEDGE:
|
|
case MT_DISCONNECT:
|
|
case MT_INFORMATION:
|
|
case MT_FACILITY:
|
|
case MT_NOTIFY:
|
|
case MT_PROGRESS:
|
|
case MT_RELEASE:
|
|
case MT_RELEASE_COMPLETE:
|
|
case MT_SETUP:
|
|
case MT_SETUP_ACKNOWLEDGE:
|
|
case MT_RESUME_ACKNOWLEDGE:
|
|
case MT_RESUME_REJECT:
|
|
case MT_SUSPEND_ACKNOWLEDGE:
|
|
case MT_SUSPEND_REJECT:
|
|
case MT_USER_INFORMATION:
|
|
case MT_RESTART:
|
|
case MT_RESTART_ACKNOWLEDGE:
|
|
case MT_CONGESTION_CONTROL:
|
|
case MT_STATUS:
|
|
case MT_STATUS_ENQUIRY:
|
|
case MT_HOLD:
|
|
case MT_HOLD_ACKNOWLEDGE:
|
|
case MT_HOLD_REJECT:
|
|
case MT_RETRIEVE:
|
|
case MT_RETRIEVE_ACKNOWLEDGE:
|
|
case MT_RETRIEVE_REJECT:
|
|
case MT_RESUME: /* RESUME only in user->net */
|
|
case MT_SUSPEND: /* SUSPEND only in user->net */
|
|
case MT_REGISTER: /* REGISTER only in user->net */
|
|
q = &l3->app_queue;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (q) {
|
|
if (!l3m) {
|
|
l3m = alloc_l3_msg();
|
|
if (!l3m)
|
|
return -ENOMEM;
|
|
}
|
|
mb = container_of(l3m, struct mbuffer, l3);
|
|
l3m->type = prim;
|
|
l3m->pid = pid;
|
|
mqueue_tail(q, mb);
|
|
id = 0;
|
|
if (l3->tbase.tdev) {
|
|
/* wake up worker */
|
|
ret = ioctl(l3->tbase.tdev, IMADDTIMER, &id);
|
|
if (ret)
|
|
eprint("%s wake up worker error %s\n", __FUNCTION__, strerror(errno));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
init_l2if(struct l2l3if *l2i, layer3_t *l3)
|
|
{
|
|
INIT_LIST_HEAD(&l2i->list);
|
|
l2i->l3 = l3;
|
|
mqueue_init(&l2i->squeue);
|
|
l2i->l3m.fsm = &l3fsm;
|
|
l2i->l3m.state = ST_L3_LC_REL;
|
|
l2i->l3m.userdata = l2i;
|
|
l2i->l3m.tb = &l3->tbase;
|
|
l2i->l3m.userint = 0;
|
|
FsmInitTimer(&l2i->l3m, &l2i->l3m_timer);
|
|
l2i->l3m.printdebug = l3fi_debug;
|
|
l2i->l3m.debug = mI_debug_mask & DBGM_L2_STATE;
|
|
}
|
|
|
|
static struct l2l3if *
|
|
create_l2l3if(layer3_t *l3, struct sockaddr_mISDN *addr)
|
|
{
|
|
struct l2l3if *l2i;
|
|
|
|
if (l3->l2master.l2addr.tei == addr->tei &&
|
|
!(test_bit(FLG_USER, &l3->ml3.options) && !test_bit(MISDN_FLG_PTP, &l3->ml3.options)))
|
|
l2i = &l3->l2master;
|
|
else
|
|
l2i = get_l2if(l3, addr->channel);
|
|
if (l2i) {
|
|
dprint(DBGM_L3, "port%d: changing tei: chan(%x) old tei(%x) new tei(%x)\n",
|
|
l2i->l2addr.dev, addr->channel, addr->tei, l2i->l2addr.tei);
|
|
l2i->l2addr = *addr;
|
|
return l2i;
|
|
}
|
|
l2i = calloc(1, sizeof(struct l2l3if));
|
|
if (!l2i) {
|
|
eprint("%s: no memory\n", __FUNCTION__);
|
|
return NULL;
|
|
}
|
|
init_l2if(l2i, l3);
|
|
l2i->l2addr = *addr;
|
|
list_add_tail(&l2i->list, &l3->l2master.list);
|
|
dprint(DBGM_L3, "port%d: creating tei: chan(%x) tei(%x)\n",
|
|
l2i->l2addr.dev, addr->channel, addr->tei);
|
|
return l2i;
|
|
}
|
|
|
|
void
|
|
init_l3(layer3_t *l3)
|
|
{
|
|
int err;
|
|
|
|
INIT_LIST_HEAD(&l3->plist);
|
|
INIT_LIST_HEAD(&l3->tbase.pending_timer);
|
|
l3->global.l2if = &l3->l2master;
|
|
l3->global.L3 = l3;
|
|
l3->dummy.l2if = &l3->l2master;
|
|
l3->dummy.pid = MISDN_PID_DUMMY;
|
|
l3->dummy.L3 = l3;
|
|
L3TimerInit(l3, MISDN_PID_GLOBAL, &l3->global.timer1);
|
|
L3TimerInit(l3, MISDN_PID_GLOBAL, &l3->global.timer2);
|
|
L3TimerInit(l3, MISDN_PID_DUMMY, &l3->dummy.timer1);
|
|
L3TimerInit(l3, MISDN_PID_DUMMY, &l3->dummy.timer2);
|
|
l3->debug = 0xff; // FIXME dynamic
|
|
init_l2if(&l3->l2master, l3);
|
|
/*
|
|
* Broadcast is always established.
|
|
* PTP l2master process follows tei 0 process
|
|
*/
|
|
if (!test_bit(MISDN_FLG_PTP, &l3->ml3.options))
|
|
l3->l2master.l3m.state = ST_L3_LC_ESTAB;
|
|
mqueue_init(&l3->app_queue);
|
|
mqueue_init(&l3->mgr_queue);
|
|
l3->ml3.to_layer3 = to_layer3;
|
|
err = pthread_mutex_init(&l3->run, NULL);
|
|
if (err)
|
|
eprint("error on init l3 mutex - %s\n", strerror(err));
|
|
l3->next_cr = 1;
|
|
}
|
|
|
|
static void
|
|
release_if(struct l2l3if *l2i)
|
|
{
|
|
mqueue_purge(&l2i->squeue);
|
|
FsmDelTimer(&l2i->l3m_timer, 54);
|
|
}
|
|
|
|
void
|
|
release_l3(layer3_t *l3)
|
|
{
|
|
l3_process_t *p, *np;
|
|
struct l2l3if *l2i, *nl2i;
|
|
|
|
list_for_each_entry_safe(p, np, &l3->plist, list)
|
|
release_l3_process(p);
|
|
StopAllL3Timer(&l3->global);
|
|
StopAllL3Timer(&l3->dummy);
|
|
release_if(&l3->l2master);
|
|
mqueue_purge(&l3->app_queue);
|
|
mqueue_purge(&l3->mgr_queue);
|
|
list_for_each_entry_safe(l2i, nl2i, &l3->l2master.list, list) {
|
|
release_if(l2i);
|
|
list_del(&l2i->list);
|
|
free(l2i);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
static void
|
|
handle_l2msg(struct _layer3 *l3, struct mbuffer *mb)
|
|
{
|
|
struct l2l3if *l2i;
|
|
int ret;
|
|
|
|
switch (mb->h->prim) {
|
|
case DL_DATA_IND:
|
|
case DL_UNITDATA_IND:
|
|
dprint(DBGM_L3, "port%d: DL_(UNIT)DATA_IND: chan(%x) tei(%x)\n",
|
|
mb->addr.dev, mb->addr.channel, mb->addr.tei);
|
|
l3->from_l2(l3, mb);
|
|
return;
|
|
case DL_INFORMATION_IND:
|
|
dprint(DBGM_L3, "port%d: DL_INFORMATION_IND: chan(%x) tei(%x)\n",
|
|
mb->addr.dev, mb->addr.channel, mb->addr.tei);
|
|
l2i = create_l2l3if(l3, &mb->addr);
|
|
goto free_out;
|
|
case MPH_INFORMATION_IND:
|
|
case MPH_ACTIVATE_IND:
|
|
case MPH_DEACTIVATE_IND:
|
|
ret = l3->ml3.from_layer3(&l3->ml3, mb->h->prim, mb->h->id, &mb->l3);
|
|
if (ret)
|
|
goto free_out;
|
|
return;
|
|
}
|
|
l2i = get_l2if(l3, mb->addr.channel);
|
|
if (!l2i) {
|
|
eprint("%s: cannot find layer2/3 interface for chan(%x)\n", __FUNCTION__, mb->addr.channel);
|
|
goto free_out;
|
|
}
|
|
switch (mb->h->prim) {
|
|
case DL_ESTABLISH_CNF:
|
|
dprint(DBGM_L3, "port%d: DL_ESTABLISH_CNF: chan(%x) tei(%x)\n",
|
|
mb->addr.dev, mb->addr.channel, mb->addr.tei);
|
|
FsmEvent(&l2i->l3m, EV_ESTABLISH_CNF, NULL);
|
|
break;
|
|
case DL_ESTABLISH_IND:
|
|
dprint(DBGM_L3, "port%d: DL_ESTABLISH_IND: chan(%x) tei(%x)\n",
|
|
mb->addr.dev, mb->addr.channel, mb->addr.tei);
|
|
FsmEvent(&l2i->l3m, EV_ESTABLISH_IND, NULL);
|
|
break;
|
|
case DL_RELEASE_IND:
|
|
dprint(DBGM_L3, "port%d: DL_RELEASE_IND: chan(%x) tei(%x)\n",
|
|
mb->addr.dev, mb->addr.channel, mb->addr.tei);
|
|
FsmEvent(&l2i->l3m, EV_RELEASE_IND, NULL);
|
|
break;
|
|
case DL_RELEASE_CNF:
|
|
dprint(DBGM_L3, "port%d: DL_RELEASE_CNF: chan(%x) tei(%x)\n",
|
|
mb->addr.dev, mb->addr.channel, mb->addr.tei);
|
|
FsmEvent(&l2i->l3m, EV_RELEASE_CNF, NULL);
|
|
break;
|
|
default:
|
|
dprint(DBGM_L3, "port%d: unknown prim(%x) chan(%x)\n",
|
|
mb->addr.dev, mb->h->prim, mb->addr.channel);
|
|
}
|
|
free_out:
|
|
free_mbuffer(mb);
|
|
}
|
|
|
|
static void
|
|
to_l2(layer3_t *l3, struct l3_msg *l3m)
|
|
{
|
|
struct l2l3if *l2i;
|
|
|
|
/* given tei or 0=first tei, but not 127 */
|
|
dprint(DBGM_L3, "port%d got %s tei %d\n",
|
|
l3->l2master.l2addr.dev, _mi_msg_type2str(l3m->type), l3m->pid);
|
|
if (l3m->pid == l3->l2master.l2addr.tei
|
|
|| (l3m->pid == 0 && l3->l2master.l2addr.tei != 127)) {
|
|
dprint(DBGM_L3, "port%d %s tei %d match first\n",
|
|
l3->l2master.l2addr.dev, _mi_msg_type2str(l3m->type), l3m->pid);
|
|
switch(l3m->type) {
|
|
case MT_L2ESTABLISH:
|
|
FsmEvent(&l3->l2master.l3m, EV_ESTABLISH_REQ, NULL);
|
|
break;
|
|
case MT_L2RELEASE:
|
|
FsmEvent(&l3->l2master.l3m, EV_RELEASE_REQ, NULL);
|
|
break;
|
|
}
|
|
free_l3_msg(l3m);
|
|
return;
|
|
}
|
|
list_for_each_entry(l2i, &l3->l2master.list, list) {
|
|
/* given tei or 0=first tei, but not 127 */
|
|
if (l3m->pid == l2i->l2addr.tei
|
|
|| (l3m->pid == 0 && l2i->l2addr.tei != 127)) {
|
|
dprint(DBGM_L3, "port%d %s tei %d match second\n", l3->l2master.l2addr.dev,
|
|
_mi_msg_type2str(l3m->type), l3m->pid);
|
|
switch(l3m->type) {
|
|
case MT_L2ESTABLISH:
|
|
FsmEvent(&l2i->l3m, EV_ESTABLISH_REQ, NULL);
|
|
break;
|
|
case MT_L2RELEASE:
|
|
FsmEvent(&l2i->l3m, EV_RELEASE_REQ, NULL);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
free_l3_msg(l3m);
|
|
}
|
|
|
|
/* one shot */
|
|
int
|
|
layer3_work(struct _layer3 *l3)
|
|
{
|
|
fd_set rfd;
|
|
struct mbuffer *mb;
|
|
int ret = 0, id;
|
|
socklen_t alen;
|
|
struct l2l3if *l2i;
|
|
int work = 0;
|
|
|
|
/* only select and process kernel timer when using kernel mISDN */
|
|
again:
|
|
if (l3->l2sock) {
|
|
FD_ZERO(&rfd);
|
|
FD_SET(l3->l2sock, &rfd);
|
|
FD_SET(l3->tbase.tdev, &rfd);
|
|
ret = select(l3->maxfd +1, &rfd, NULL, NULL, NULL);
|
|
if (ret < 0) {
|
|
if (errno == EINTR)
|
|
goto again;
|
|
eprint("%s aborted: %s\n", __FUNCTION__, strerror(errno));
|
|
return work;
|
|
}
|
|
if (FD_ISSET(l3->tbase.tdev, &rfd)) {
|
|
ret = read(l3->tbase.tdev, &id, sizeof(id));
|
|
if (ret < 0) {
|
|
eprint("%s read timer error %s\n", __FUNCTION__, strerror(errno));
|
|
} else {
|
|
if (ret == sizeof(id) && id) {
|
|
work |= expire_timer(&l3->tbase, id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (l3->l2inst) {
|
|
/* loops through all timers until there is no expired timer anymore */
|
|
work |= expire_timer(&l3->tbase, -1);
|
|
}
|
|
/* check rdf when using kernel mISDN, poll when using user space mISDN */
|
|
if ((l3->l2sock && FD_ISSET(l3->l2sock, &rfd))
|
|
|| (l3->l2inst)) {
|
|
mb = alloc_mbuffer();
|
|
if (!mb) {
|
|
eprint("%s no memory for mbuffer\n", __FUNCTION__);
|
|
return work;
|
|
}
|
|
alen = sizeof(mb->addr);
|
|
if (l3->l2inst)
|
|
ret = mISDN_data_recvmsg(l3->l2inst, mb->head, MBUFFER_DATA_SIZE, 0, &mb->addr);
|
|
if (l3->l2sock)
|
|
ret = recvfrom(l3->l2sock, mb->head, MBUFFER_DATA_SIZE, 0, (struct sockaddr *) &mb->addr, &alen);
|
|
if (ret < 0) {
|
|
if (ret != -EAGAIN)
|
|
eprint("%s read socket error %s\n", __FUNCTION__, strerror(errno));
|
|
// here ignore when no message available from mISDN
|
|
} else if (ret < MISDN_HEADER_LEN) {
|
|
eprint("%s read socket short frame\n", __FUNCTION__);
|
|
} else {
|
|
work |= 1;
|
|
mb->len = ret;
|
|
msg_pull(mb, MISDN_HEADER_LEN);
|
|
handle_l2msg(l3, mb);
|
|
}
|
|
}
|
|
while ((mb = mdequeue(&l3->app_queue))) {
|
|
work |= 1;
|
|
ret = l3->to_l3(l3, &mb->l3);
|
|
if (ret < 0)
|
|
free_mbuffer(mb);
|
|
}
|
|
if (l3->l2master.l3m.state == ST_L3_LC_ESTAB) {
|
|
while ((mb = mdequeue(&l3->l2master.squeue))) {
|
|
work |= 1;
|
|
dprint(DBGM_L3DATA, "port%d send %s to tei %d\n",
|
|
l3->l2master.l2addr.dev, _mi_msg_type2str(mb->h->prim), mb->addr.tei);
|
|
if (l3->l2sock)
|
|
ret = sendto(l3->l2sock, mb->head, mb->len, 0, (struct sockaddr *)&mb->addr, sizeof(mb->addr));
|
|
if (l3->l2inst)
|
|
ret = mISDN_data_sendmsg(l3->l2inst, mb->head, mb->len, 0, &mb->addr);
|
|
if (ret < 0)
|
|
eprint("%s write socket error %s\n", __FUNCTION__, strerror(errno));
|
|
free_mbuffer(mb);
|
|
}
|
|
}
|
|
list_for_each_entry(l2i, &l3->l2master.list, list) {
|
|
if (l2i->l3m.state == ST_L3_LC_ESTAB) {
|
|
while ((mb = mdequeue(&l2i->squeue))) {
|
|
work |= 1;
|
|
dprint(DBGM_L2, "port%d send msg (%s) len=%d to L2 tei %d\n",
|
|
mb->addr.dev, _mi_msg_type2str(mb->h->prim), mb->len, mb->addr.tei);
|
|
if (l3->l2sock)
|
|
ret = sendto(l3->l2sock, mb->head, mb->len, 0, (struct sockaddr *)&mb->addr, sizeof(mb->addr));
|
|
if (l3->l2inst)
|
|
ret = mISDN_data_sendmsg(l3->l2inst, mb->head, mb->len, 0, &mb->addr);
|
|
if (ret < 0)
|
|
eprint("%s write socket error %s\n", __FUNCTION__, strerror(errno));
|
|
free_mbuffer(mb);
|
|
}
|
|
}
|
|
}
|
|
while ((mb = mdequeue(&l3->mgr_queue))) {
|
|
work |= 1;
|
|
ret = 0;
|
|
switch(mb->l3.type) {
|
|
case MT_L2ESTABLISH:
|
|
case MT_L2RELEASE:
|
|
to_l2(l3, &mb->l3);
|
|
break;
|
|
default:
|
|
wprint("mgr_queue: msg type %x not handled\n", mb->l3.type);
|
|
ret = -EINVAL;
|
|
}
|
|
if (ret)
|
|
free_mbuffer(mb);
|
|
}
|
|
|
|
return work;
|
|
}
|
|
|
|
static void *
|
|
layer3_thread(void *arg)
|
|
{
|
|
struct _layer3 *l3 = arg;
|
|
|
|
while(!test_bit(FLG_ABORT, &l3->ml3.options)) {
|
|
layer3_work(l3);
|
|
if (test_bit(FLG_RUN_WAIT, &l3->ml3.options)) {
|
|
test_and_clear_bit(FLG_RUN_WAIT, &l3->ml3.options);
|
|
pthread_mutex_unlock(&l3->run);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
l3_start(struct _layer3 *l3)
|
|
{
|
|
int ret;
|
|
|
|
pthread_mutex_lock(&l3->run);
|
|
test_and_set_bit(FLG_RUN_WAIT, &l3->ml3.options);
|
|
ret = mi_thread_create(&l3->worker, NULL, layer3_thread, (void *)l3);
|
|
if (ret) {
|
|
eprint("%s cannot start worker thread %s\n", __FUNCTION__, strerror(ret));
|
|
} else
|
|
pthread_mutex_lock(&l3->run); // wait until tread is running
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
l3_stop(struct _layer3 *l3)
|
|
{
|
|
int ret;
|
|
|
|
test_and_set_bit(FLG_ABORT, &l3->ml3.options);
|
|
ret = pthread_cancel(l3->worker);
|
|
if (ret)
|
|
eprint("%s cannot cancel worker thread %s\n", __FUNCTION__, strerror(ret));
|
|
ret = pthread_join(l3->worker, NULL);
|
|
if (ret)
|
|
eprint("%s cannot join worker thread %s\n", __FUNCTION__, strerror(ret));
|
|
}
|