1394 lines
34 KiB
C
1394 lines
34 KiB
C
/* $Id$
|
|
*
|
|
* Linux modular ISDN subsystem, mISDN
|
|
* X.25/X.31 Layer3 for DTE mode
|
|
*
|
|
* Author Karsten Keil (kkeil@suse.de)
|
|
*
|
|
* Copyright 2003 by Karsten Keil (kkeil@suse.de)
|
|
*
|
|
* This software may be used and distributed according to the terms
|
|
* of the GNU General Public License, incorporated herein by reference.
|
|
*
|
|
*/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/module.h>
|
|
#include "x25_l3.h"
|
|
#include "helper.h"
|
|
#include "debug.h"
|
|
|
|
static int debug = 0;
|
|
|
|
static mISDNobject_t x25dte_obj;
|
|
|
|
static char *mISDN_dte_revision = "$Revision$";
|
|
|
|
/* local prototypes */
|
|
static x25_channel_t * dte_create_channel(x25_l3_t *, int, u_char, __u16, int, u_char *);
|
|
|
|
|
|
/* X.25 Restart state machine */
|
|
static struct Fsm dte_rfsm = {NULL, 0, 0, NULL, NULL};
|
|
|
|
/* X.25 connection state machine */
|
|
static struct Fsm dte_pfsm = {NULL, 0, 0, NULL, NULL};
|
|
|
|
/* X.25 Flowcontrol state machine */
|
|
static struct Fsm dte_dfsm = {NULL, 0, 0, NULL, NULL};
|
|
|
|
|
|
/* X.25 Restart state machine implementation */
|
|
|
|
static void
|
|
r_llready(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_l3_t *l3 = fi->userdata;
|
|
|
|
mISDN_FsmChangeState(fi, ST_R1);
|
|
if (test_and_clear_bit(X25_STATE_ESTABLISH, &l3->state)) {
|
|
mISDN_FsmEvent(&l3->x25r, EV_L3_RESTART_REQ, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
r_r0_restart_l3(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_l3_t *l3 = fi->userdata;
|
|
|
|
if (arg)
|
|
memcpy(l3->cause, arg, 2);
|
|
else
|
|
memset(l3->cause, 0, 2);
|
|
test_and_set_bit(X25_STATE_ESTABLISH, &l3->state);
|
|
mISDN_FsmEvent(&l3->l2l3m, EV_L3_ESTABLISH_REQ, NULL);
|
|
}
|
|
|
|
static void
|
|
r_restart_l3(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_l3_t *l3 = fi->userdata;
|
|
|
|
if (arg)
|
|
memcpy(l3->cause, arg, 2);
|
|
mISDN_FsmChangeState(fi, ST_R2);
|
|
l3->TRval = T20_VALUE;
|
|
l3->TRrep = R20_VALUE;
|
|
X25sendL3frame(NULL, l3, X25_PTYPE_RESTART, 2, l3->cause);
|
|
mISDN_FsmAddTimer(&l3->TR, l3->TRval, EV_L3_RESTART_TOUT, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
r_restart_ind(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_l3_t *l3 = fi->userdata;
|
|
|
|
mISDN_FsmChangeState(fi, ST_R3);
|
|
X25sendL3frame(NULL, l3, X25_PTYPE_RESTART_CNF, 0, NULL);
|
|
mISDN_FsmChangeState(fi, ST_R1);
|
|
mISDN_FsmDelTimer(&l3->TR, 0);
|
|
X25_restart(l3);
|
|
}
|
|
|
|
static void
|
|
r_restart_cnf(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_l3_t *l3 = fi->userdata;
|
|
|
|
mISDN_FsmChangeState(fi, ST_R1);
|
|
mISDN_FsmDelTimer(&l3->TR, 0);
|
|
X25_restart(l3);
|
|
}
|
|
|
|
static void
|
|
r_restart_cnf_err(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_l3_t *l3 = fi->userdata;
|
|
u_char cause[2] = {0, 17};
|
|
|
|
if (fi->state == ST_R3)
|
|
cause[1] = 19;
|
|
mISDN_FsmEvent(&l3->x25r, EV_L3_RESTART_REQ, cause);
|
|
}
|
|
|
|
static void
|
|
r_timeout(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_l3_t *l3 = fi->userdata;
|
|
|
|
if (l3->TRrep) {
|
|
X25sendL3frame(NULL, l3, X25_PTYPE_RESTART, 2, l3->cause);
|
|
mISDN_FsmRestartTimer(&l3->TR, l3->TRval, EV_L3_RESTART_TOUT, NULL, 0);
|
|
l3->TRrep--;
|
|
} else {
|
|
mISDN_FsmDelTimer(&l3->TR, 0);
|
|
mISDN_FsmChangeState(fi, ST_R1);
|
|
/* signal failure */
|
|
}
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
static struct FsmNode RFnList[] =
|
|
{
|
|
{ST_R0, EV_LL_READY, r_llready},
|
|
{ST_R0, EV_L3_RESTART_REQ, r_r0_restart_l3},
|
|
{ST_R1, EV_LL_READY, r_llready},
|
|
{ST_R1, EV_L3_RESTART_REQ, r_restart_l3},
|
|
{ST_R1, EV_L2_RESTART, r_restart_ind},
|
|
{ST_R1, EV_L2_RESTART_CNF, r_restart_cnf_err},
|
|
{ST_R2, EV_L3_RESTART_REQ, r_restart_l3},
|
|
{ST_R2, EV_L2_RESTART, r_restart_cnf},
|
|
{ST_R2, EV_L2_RESTART_CNF, r_restart_cnf},
|
|
{ST_R2, EV_L3_RESTART_TOUT, r_timeout},
|
|
{ST_R2, EV_LL_READY, r_llready},
|
|
{ST_R3, EV_L3_RESTART_REQ, r_restart_l3},
|
|
{ST_R3, EV_L2_RESTART_CNF, r_restart_cnf_err},
|
|
{ST_R3, EV_LL_READY, r_llready},
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
#define R_FN_COUNT (sizeof(RFnList)/sizeof(struct FsmNode))
|
|
|
|
/* X.25 connection state machine */
|
|
|
|
static void
|
|
p_p0_ready(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
if (test_bit(X25_STATE_PERMANENT, &l3c->state)) {
|
|
mISDN_FsmChangeState(fi, ST_P4);
|
|
/* connect */
|
|
mISDN_FsmEvent(&l3c->x25d, EV_L3_CONNECT, NULL);
|
|
} else {
|
|
mISDN_FsmChangeState(fi, ST_P1);
|
|
if (test_bit(X25_STATE_ORGINATE, &l3c->state))
|
|
mISDN_FsmEvent(fi, EV_L3_OUTGOING_CALL, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
X25_clear_connection(x25_channel_t *l3c, struct sk_buff *skb, int reason)
|
|
{
|
|
if (skb) {
|
|
if (test_bit(X25_STATE_DBIT, &l3c->state))
|
|
reason |= 0x10000;
|
|
X25sendL4frame(l3c, l3c->l3, CAPI_DISCONNECT_B3_IND, reason, skb->len, skb->data);
|
|
} else
|
|
X25sendL4frame(l3c, l3c->l3, CAPI_DISCONNECT_B3_IND, reason, 0, NULL);
|
|
}
|
|
|
|
static void
|
|
p_p0_outgoing(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
}
|
|
|
|
static void
|
|
p_ready(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
// x25_channel_t *l3c = fi->userdata;
|
|
}
|
|
|
|
static void
|
|
p_outgoing(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
struct sk_buff *skb = arg;
|
|
|
|
mISDN_FsmChangeState(fi, ST_P2);
|
|
if (skb)
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CALL, skb->len, skb->data);
|
|
else
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CALL, l3c->ncpi_len, l3c->ncpi_data);
|
|
mISDN_FsmAddTimer(&l3c->TP, T21_VALUE, EV_L3_CALL_TOUT, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
p_incoming(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
int flg = 0;
|
|
|
|
if (test_bit(X25_STATE_DBIT, &l3c->state))
|
|
flg = 0x10000;
|
|
mISDN_FsmChangeState(fi, ST_P3);
|
|
X25sendL4frame(l3c, l3c->l3, CAPI_CONNECT_B3_IND, flg, l3c->ncpi_len, l3c->ncpi_data);
|
|
}
|
|
|
|
static void
|
|
p_call_accept(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
struct sk_buff *skb = arg;
|
|
|
|
if (skb)
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CALL_CNF, skb->len, skb->data);
|
|
else
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CALL_CNF, 0, NULL);
|
|
mISDN_FsmChangeState(fi, ST_P4);
|
|
mISDN_FsmEvent(&l3c->x25d, EV_L3_CONNECT, NULL);
|
|
X25sendL4frame(l3c, l3c->l3, CAPI_CONNECT_B3_ACTIVE_IND, 0, 0, NULL);
|
|
}
|
|
|
|
static void
|
|
p_collision(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
mISDN_FsmChangeState(fi, ST_P5);
|
|
}
|
|
|
|
static void
|
|
p_connect(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
struct sk_buff *skb = arg;
|
|
int flg = 0;
|
|
|
|
mISDN_FsmDelTimer(&l3c->TP, 0);
|
|
mISDN_FsmChangeState(fi, ST_P4);
|
|
if (test_bit(X25_STATE_DBIT, &l3c->state))
|
|
flg = 0x10000;
|
|
mISDN_FsmEvent(&l3c->x25d, EV_L3_CONNECT, NULL);
|
|
if (skb)
|
|
X25sendL4frame(l3c, l3c->l3, CAPI_CONNECT_B3_ACTIVE_IND, flg, skb->len, skb->data);
|
|
else
|
|
X25sendL4frame(l3c, l3c->l3, CAPI_CONNECT_B3_ACTIVE_IND, flg, 0, NULL);
|
|
}
|
|
|
|
static void
|
|
p_call_timeout(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
mISDN_FsmChangeState(fi, ST_P6);
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[1] = 49;
|
|
l3c->TPval = T23_VALUE;
|
|
l3c->TPrep = R23_VALUE;
|
|
mISDN_FsmAddTimer(&l3c->TP, l3c->TPval, EV_L3_CLEAR_TOUT, NULL, 0);
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CLEAR, 2, l3c->cause);
|
|
}
|
|
|
|
static void
|
|
p_clear_ind(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
mISDN_FsmChangeState(fi, ST_P7);
|
|
mISDN_FsmDelTimer(&l3c->TP, 0);
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CLEAR_CNF, 0, NULL);
|
|
mISDN_FsmChangeState(fi, ST_P1);
|
|
X25_clear_connection(l3c, arg, 0);
|
|
}
|
|
|
|
static void
|
|
p_clear_cnf(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
mISDN_FsmChangeState(fi, ST_P1);
|
|
mISDN_FsmDelTimer(&l3c->TP, 0);
|
|
X25_clear_connection(l3c, arg, 0);
|
|
}
|
|
|
|
static void
|
|
p_clear_timeout(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
if (l3c->TPrep) {
|
|
mISDN_FsmAddTimer(&l3c->TP, l3c->TPval, EV_L3_CLEAR_TOUT, NULL, 0);
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CLEAR, 2, l3c->cause);
|
|
} else {
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[0] = 50;
|
|
mISDN_FsmChangeState(fi, ST_P1);
|
|
X25_clear_connection(l3c, NULL, 0x3303);
|
|
}
|
|
}
|
|
|
|
static void
|
|
p_clearing_req(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
struct sk_buff *skb = arg;
|
|
|
|
mISDN_FsmChangeState(fi, ST_P6);
|
|
l3c->TPval = T23_VALUE;
|
|
l3c->TPrep = R23_VALUE;
|
|
mISDN_FsmAddTimer(&l3c->TP, l3c->TPval, EV_L3_CLEAR_TOUT, NULL, 0);
|
|
if (skb) {
|
|
if (skb->len >= 2) {
|
|
memcpy(l3c->cause, skb->data, 2);
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CLEAR, skb->len, skb->data);
|
|
return;
|
|
}
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[1] = 0;
|
|
}
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CLEAR, 2, l3c->cause);
|
|
}
|
|
|
|
static void
|
|
p_invalid_pkt(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
l3c->cause[0] = 0;
|
|
switch(fi->state) {
|
|
case ST_P1:
|
|
l3c->cause[1] = 20;
|
|
break;
|
|
case ST_P2:
|
|
l3c->cause[1] = 21;
|
|
break;
|
|
case ST_P3:
|
|
l3c->cause[1] = 22;
|
|
break;
|
|
case ST_P4:
|
|
l3c->cause[1] = 23;
|
|
break;
|
|
case ST_P5:
|
|
l3c->cause[1] = 24;
|
|
break;
|
|
case ST_P6:
|
|
l3c->cause[1] = 25;
|
|
break;
|
|
case ST_P7:
|
|
l3c->cause[1] = 26;
|
|
break;
|
|
default:
|
|
l3c->cause[1] = 16;
|
|
break;
|
|
}
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_CLEAR, 2, l3c->cause);
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
static struct FsmNode PFnList[] =
|
|
{
|
|
{ST_P0, EV_L3_READY, p_p0_ready},
|
|
{ST_P0, EV_L3_OUTGOING_CALL, p_p0_outgoing},
|
|
{ST_P0, EV_L3_CLEARING, p_clearing_req},
|
|
{ST_P1, EV_L3_READY, p_ready},
|
|
{ST_P1, EV_L3_OUTGOING_CALL, p_outgoing},
|
|
{ST_P1, EV_L2_INCOMING_CALL, p_incoming},
|
|
{ST_P1, EV_L2_CLEAR, p_clear_ind},
|
|
{ST_P1, EV_L2_CALL_CNF, p_invalid_pkt},
|
|
{ST_P1, EV_L2_CLEAR_CNF, p_invalid_pkt},
|
|
{ST_P1, EV_L2_INVALPKT, p_invalid_pkt},
|
|
{ST_P1, EV_L3_CLEARING, p_clearing_req},
|
|
{ST_P2, EV_L2_INCOMING_CALL, p_collision},
|
|
{ST_P2, EV_L2_CALL_CNF, p_connect},
|
|
{ST_P2, EV_L2_CLEAR, p_clear_ind},
|
|
{ST_P2, EV_L3_CALL_TOUT, p_call_timeout},
|
|
{ST_P2, EV_L2_CLEAR_CNF, p_invalid_pkt},
|
|
{ST_P2, EV_L2_INVALPKT, p_invalid_pkt},
|
|
{ST_P2, EV_L3_CLEARING, p_clearing_req},
|
|
{ST_P3, EV_L3_CALL_ACCEPT, p_call_accept},
|
|
{ST_P3, EV_L2_CLEAR, p_clear_ind},
|
|
{ST_P3, EV_L2_INCOMING_CALL, p_invalid_pkt},
|
|
{ST_P3, EV_L2_CALL_CNF, p_invalid_pkt},
|
|
{ST_P3, EV_L2_CLEAR_CNF, p_invalid_pkt},
|
|
{ST_P3, EV_L2_INVALPKT, p_invalid_pkt},
|
|
{ST_P3, EV_L3_CLEARING, p_clearing_req},
|
|
{ST_P4, EV_L2_CLEAR, p_clear_ind},
|
|
{ST_P4, EV_L2_INCOMING_CALL, p_invalid_pkt},
|
|
{ST_P4, EV_L2_CALL_CNF, p_invalid_pkt},
|
|
{ST_P4, EV_L2_CLEAR_CNF, p_invalid_pkt},
|
|
{ST_P4, EV_L3_CLEARING, p_clearing_req},
|
|
{ST_P5, EV_L2_CALL_CNF, p_connect},
|
|
{ST_P5, EV_L3_CALL_ACCEPT, p_call_accept},
|
|
{ST_P5, EV_L2_CLEAR, p_clear_ind},
|
|
{ST_P5, EV_L3_CALL_TOUT, p_call_timeout},
|
|
{ST_P5, EV_L2_INCOMING_CALL, p_invalid_pkt},
|
|
{ST_P5, EV_L2_CLEAR_CNF, p_invalid_pkt},
|
|
{ST_P5, EV_L2_INVALPKT, p_invalid_pkt},
|
|
{ST_P5, EV_L3_CLEARING, p_clearing_req},
|
|
{ST_P6, EV_L2_CLEAR_CNF, p_clear_cnf},
|
|
{ST_P6, EV_L3_CLEAR_TOUT, p_clear_timeout},
|
|
{ST_P6, EV_L3_CLEARING, p_clearing_req},
|
|
{ST_P7, EV_L2_INCOMING_CALL, p_invalid_pkt},
|
|
{ST_P7, EV_L2_CALL_CNF, p_invalid_pkt},
|
|
{ST_P7, EV_L2_CLEAR_CNF, p_invalid_pkt},
|
|
{ST_P7, EV_L2_INVALPKT, p_invalid_pkt},
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
#define P_FN_COUNT (sizeof(PFnList)/sizeof(struct FsmNode))
|
|
|
|
static void
|
|
d_connect(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
mISDN_FsmChangeState(fi, ST_D1);
|
|
}
|
|
|
|
static void
|
|
d_reset_req(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
struct sk_buff *skb = arg;
|
|
|
|
mISDN_FsmChangeState(fi, ST_D2);
|
|
l3c->TDval = T22_VALUE;
|
|
l3c->TDrep = R22_VALUE;
|
|
mISDN_FsmAddTimer(&l3c->TD, l3c->TDval, EV_L3_RESET_TOUT, NULL, 0);
|
|
if (skb) {
|
|
if (skb->len >= 2) {
|
|
memcpy(l3c->cause, skb->data, 2);
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_RESET, skb->len, skb->data);
|
|
return;
|
|
}
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[1] = 0;
|
|
}
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_RESET, 2, l3c->cause);
|
|
}
|
|
|
|
static void
|
|
d_reset_ind(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
mISDN_FsmChangeState(fi, ST_D3);
|
|
X25_reset_channel(l3c, arg);
|
|
mISDN_FsmChangeState(fi, ST_D1);
|
|
/* TODO normal operation trigger */
|
|
}
|
|
|
|
static void
|
|
d_reset_cnf(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
mISDN_FsmDelTimer(&l3c->TD, 0);
|
|
X25_reset_channel(l3c, NULL);
|
|
/* TODO normal opration trigger */
|
|
mISDN_FsmChangeState(fi, ST_D1);
|
|
}
|
|
|
|
static void
|
|
d_reset_cnf_err(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
if (arg)
|
|
memcpy(l3c->cause, arg, 2);
|
|
mISDN_FsmChangeState(fi, ST_D2);
|
|
l3c->TDval = T22_VALUE;
|
|
l3c->TDrep = R22_VALUE;
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_RESET, 2, l3c->cause);
|
|
mISDN_FsmAddTimer(&l3c->TD, l3c->TDval, EV_L3_RESET_TOUT, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
d_reset_timeout(struct FsmInst *fi, int event, void *arg)
|
|
{
|
|
x25_channel_t *l3c = fi->userdata;
|
|
|
|
if (l3c->TDrep) {
|
|
X25sendL3frame(l3c, l3c->l3, X25_PTYPE_RESET, 2, l3c->cause);
|
|
l3c->TDrep--;
|
|
} else {
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[1] = 51;
|
|
mISDN_FsmChangeState(fi, ST_D0);
|
|
if (test_bit(X25_STATE_PERMANENT, &l3c->state))
|
|
X25_clear_connection(l3c, NULL, 0x3303);
|
|
else
|
|
mISDN_FsmEvent(&l3c->x25p, EV_L3_CLEARING, NULL);
|
|
}
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
static struct FsmNode DFnList[] =
|
|
{
|
|
{ST_D0, EV_L3_CONNECT, d_connect},
|
|
{ST_D1, EV_L2_RESET, d_reset_ind},
|
|
{ST_D1, EV_L2_RESET_CNF, d_reset_cnf_err},
|
|
{ST_D1, EV_L3_RESETING, d_reset_req},
|
|
{ST_D2, EV_L2_RESET, d_reset_cnf},
|
|
{ST_D2, EV_L2_RESET_CNF, d_reset_cnf},
|
|
{ST_D3, EV_L2_RESET_CNF, d_reset_cnf_err},
|
|
{ST_D3, EV_L3_RESETING, d_reset_req},
|
|
{ST_D1, EV_L3_RESET_TOUT, d_reset_timeout},
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
#define D_FN_COUNT (sizeof(DFnList)/sizeof(struct FsmNode))
|
|
|
|
static int
|
|
got_diagnositic(x25_l3_t *l3, struct sk_buff *skb, u_char gfi, __u16 channel)
|
|
{
|
|
/* not implemented yet */
|
|
return(X25_ERRCODE_DISCARD);
|
|
}
|
|
|
|
static int
|
|
got_register(x25_l3_t *l3, struct sk_buff *skb, u_char gfi, __u16 channel)
|
|
{
|
|
/* not implemented yet */
|
|
return(X25_ERRCODE_DISCARD);
|
|
}
|
|
|
|
static int
|
|
got_register_cnf(x25_l3_t *l3, struct sk_buff *skb, u_char gfi, __u16 channel)
|
|
{
|
|
/* not implemented yet */
|
|
return(X25_ERRCODE_DISCARD);
|
|
}
|
|
|
|
static int
|
|
dte_data_ind_d(x25_channel_t *chan, struct sk_buff *skb, u_char gfi, u_char ptype)
|
|
{
|
|
int pr_m, ps, event;
|
|
u_char ptype_s = ptype;
|
|
|
|
if ((ptype & 1) == 0)
|
|
ptype = X25_PTYPE_DATA;
|
|
else {
|
|
if ((!test_bit(X25_STATE_MOD128, &chan->state)) &&
|
|
!test_bit(X25_STATE_MOD32768, &chan->state))
|
|
ptype = ptype & 0x1f;
|
|
if ((ptype != X25_PTYPE_RR) &&
|
|
(ptype != X25_PTYPE_RNR) &&
|
|
(ptype != X25_PTYPE_REJ))
|
|
ptype = ptype_s;
|
|
}
|
|
switch (ptype) {
|
|
case X25_PTYPE_RESET:
|
|
event = EV_L2_RESET;
|
|
break;
|
|
case X25_PTYPE_RESET_CNF:
|
|
event = EV_L2_RESET_CNF;
|
|
break;
|
|
case X25_PTYPE_NOTYPE:
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 38;
|
|
event = EV_L3_RESETING;
|
|
break;
|
|
case X25_PTYPE_RESTART:
|
|
case X25_PTYPE_RESTART_CNF:
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 41;
|
|
event = EV_L3_RESETING;
|
|
break;
|
|
case X25_PTYPE_INTERRUPT:
|
|
case X25_PTYPE_INTERRUPT_CNF:
|
|
case X25_PTYPE_DATA:
|
|
case X25_PTYPE_RR:
|
|
case X25_PTYPE_RNR:
|
|
case X25_PTYPE_REJ:
|
|
if (chan->x25d.state == ST_D2)
|
|
return(X25_ERRCODE_DISCARD);
|
|
else if (chan->x25d.state == ST_D3) {
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 29;
|
|
event = EV_L3_RESETING;
|
|
} else
|
|
event = -1;
|
|
break;
|
|
default:
|
|
/* unknown paket type */
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 33;
|
|
event = EV_L3_RESETING;
|
|
break;
|
|
}
|
|
if (event != -1) {
|
|
if (event == EV_L3_RESETING)
|
|
mISDN_FsmEvent(&chan->x25d, event, NULL);
|
|
else
|
|
mISDN_FsmEvent(&chan->x25d, event, skb);
|
|
return(X25_ERRCODE_DISCARD);
|
|
}
|
|
switch (ptype) {
|
|
case X25_PTYPE_INTERRUPT:
|
|
if (test_and_set_bit(X25_STATE_DXE_INTSENT, &chan->state)) {
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 44;
|
|
mISDN_FsmEvent(&chan->x25d, EV_L3_RESETING, NULL);
|
|
} else {
|
|
// X25_got_interrupt(chan, skb);
|
|
}
|
|
break;
|
|
case X25_PTYPE_INTERRUPT_CNF:
|
|
if (!test_and_clear_bit(X25_STATE_DTE_INTSENT, &chan->state)) {
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 43;
|
|
mISDN_FsmEvent(&chan->x25d, EV_L3_RESETING, NULL);
|
|
}
|
|
break;
|
|
case X25_PTYPE_RR:
|
|
case X25_PTYPE_RNR:
|
|
case X25_PTYPE_REJ:
|
|
pr_m = X25_get_and_test_pr(chan, ptype_s, skb);
|
|
if (pr_m < 0) {
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = -pr_m;
|
|
mISDN_FsmEvent(&chan->x25d, EV_L3_RESETING, NULL);
|
|
} else if (skb->len) {
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 39;
|
|
mISDN_FsmEvent(&chan->x25d, EV_L3_RESETING, NULL);
|
|
} else {
|
|
if (ptype == X25_PTYPE_RR) {
|
|
test_and_clear_bit(X25_STATE_DXE_RNR, &chan->state);
|
|
if (X25_cansend(chan))
|
|
X25_invoke_sending(chan);
|
|
} else if (ptype == X25_PTYPE_RNR) {
|
|
if (!test_and_set_bit(X25_STATE_DXE_RNR, &chan->state)) {
|
|
/* action for DXE RNR */
|
|
}
|
|
} else {
|
|
/* TODO REJ */
|
|
}
|
|
}
|
|
break;
|
|
case X25_PTYPE_DATA:
|
|
ps = X25_get_and_test_ps(chan, ptype_s, skb);
|
|
if (ps == -38)
|
|
pr_m = ps;
|
|
else
|
|
pr_m = X25_get_and_test_pr(chan, ptype_s, skb);
|
|
if (pr_m < 0) {
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = -pr_m;
|
|
mISDN_FsmEvent(&chan->x25d, EV_L3_RESETING, NULL);
|
|
} else if (ps < 0) {
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = -ps;
|
|
mISDN_FsmEvent(&chan->x25d, EV_L3_RESETING, NULL);
|
|
} else {
|
|
int flag = (pr_m & 1) ? CAPI_FLAG_MOREDATA : 0;
|
|
|
|
if (gfi & X25_GFI_QBIT)
|
|
flag |= CAPI_FLAG_QUALIFIER;
|
|
if (gfi & X25_GFI_DBIT)
|
|
flag |= CAPI_FLAG_DELIVERCONF;
|
|
return(X25_receive_data(chan, ps, flag, skb));
|
|
}
|
|
break;
|
|
}
|
|
return(X25_ERRCODE_DISCARD);
|
|
}
|
|
|
|
static int
|
|
dte_data_ind_p(x25_l3_t *l3, struct sk_buff *skb, u_char gfi, __u16 channel, u_char ptype)
|
|
{
|
|
x25_channel_t *chan = X25_get_channel(l3, channel);
|
|
int event, ret = X25_ERRCODE_DISCARD;
|
|
|
|
if (ptype == X25_PTYPE_CALL) {
|
|
if (!chan)
|
|
chan = dte_create_channel(l3, X25_CHANNEL_INCOMING, gfi, channel, skb->len, skb->data);
|
|
if (chan) {
|
|
mISDN_FsmEvent(&chan->x25p, EV_L2_INCOMING_CALL, skb);
|
|
} else {
|
|
ret = 36; /* unassigned channel */
|
|
}
|
|
return(ret);
|
|
}
|
|
if (!chan)
|
|
return(36); /* unassigned channel */
|
|
switch (ptype) {
|
|
case X25_PTYPE_CALL_CNF:
|
|
event = EV_L2_CALL_CNF;
|
|
break;
|
|
case X25_PTYPE_CLEAR:
|
|
event = EV_L2_CLEAR;
|
|
break;
|
|
case X25_PTYPE_CLEAR_CNF:
|
|
event = EV_L2_CLEAR_CNF;
|
|
break;
|
|
case X25_PTYPE_NOTYPE:
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 38;
|
|
event = EV_L3_CLEARING;
|
|
break;
|
|
case X25_PTYPE_RESTART:
|
|
case X25_PTYPE_RESTART_CNF:
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 41;
|
|
event = EV_L3_CLEARING;
|
|
break;
|
|
case X25_PTYPE_RESET:
|
|
case X25_PTYPE_RESET_CNF:
|
|
case X25_PTYPE_INTERRUPT:
|
|
case X25_PTYPE_INTERRUPT_CNF:
|
|
event = EV_L2_INVALPKT;
|
|
break;
|
|
default:
|
|
if ((ptype & 1) == 0) {
|
|
event = EV_L2_INVALPKT;
|
|
break;
|
|
}
|
|
if (!test_bit(X25_STATE_MOD128, &chan->state) &&
|
|
!test_bit(X25_STATE_MOD32768, &chan->state))
|
|
event = ptype & 0x1f;
|
|
else
|
|
event = ptype;
|
|
if ((event == X25_PTYPE_RR) ||
|
|
(event == X25_PTYPE_RNR) ||
|
|
(event == X25_PTYPE_REJ)) {
|
|
event = EV_L2_INVALPKT;
|
|
break;
|
|
}
|
|
/* unknown paket type */
|
|
chan->cause[0] = 0;
|
|
chan->cause[1] = 33;
|
|
event = EV_L3_CLEARING;
|
|
break;
|
|
}
|
|
if (chan->x25p.state == ST_P4) {
|
|
if ((event == EV_L2_INVALPKT) || (event == EV_L3_CLEARING))
|
|
return(dte_data_ind_d(chan, skb, gfi, ptype));
|
|
}
|
|
if (event == EV_L3_CLEARING)
|
|
mISDN_FsmEvent(&chan->x25p, event, NULL);
|
|
else
|
|
mISDN_FsmEvent(&chan->x25p, event, skb);
|
|
return(ret);
|
|
}
|
|
|
|
static int
|
|
dte_data_ind_r(x25_l3_t *l3, struct sk_buff *skb, u_char gfi, __u16 channel, u_char ptype)
|
|
{
|
|
int ret = X25_ERRCODE_DISCARD;
|
|
|
|
|
|
if (ptype == X25_PTYPE_NOTYPE) {
|
|
if (channel) {
|
|
if (l3->x25r.state == ST_R1)
|
|
return(dte_data_ind_p(l3, skb, gfi, channel, ptype));
|
|
if (l3->x25r.state == ST_R2) {
|
|
l3->cause[0] = 0;
|
|
l3->cause[1] = 38;
|
|
mISDN_FsmEvent(&l3->x25r, EV_L3_RESTART_REQ, NULL);
|
|
}
|
|
} else {
|
|
if (l3->x25r.state == ST_R1)
|
|
ret = 38; // packet too short
|
|
else if (l3->x25r.state == ST_R3) {
|
|
l3->cause[0] = 0;
|
|
l3->cause[1] = 38;
|
|
mISDN_FsmEvent(&l3->x25r, EV_L3_RESTART_REQ, NULL);
|
|
}
|
|
}
|
|
return(ret);
|
|
}
|
|
if ((ptype == X25_PTYPE_RESTART) || (ptype == X25_PTYPE_RESTART_CNF)) {
|
|
if (channel) {
|
|
if (l3->x25r.state == ST_R1)
|
|
return(dte_data_ind_p(l3, skb, gfi, channel, ptype));
|
|
else if (l3->x25r.state == ST_R3) {
|
|
l3->cause[0] = 0;
|
|
l3->cause[1] = 41;
|
|
mISDN_FsmEvent(&l3->x25r, EV_L3_RESTART_REQ, NULL);
|
|
}
|
|
return(ret);
|
|
}
|
|
if (ptype == X25_PTYPE_RESTART)
|
|
mISDN_FsmEvent(&l3->x25r, EV_L2_RESTART, skb);
|
|
else
|
|
mISDN_FsmEvent(&l3->x25r, EV_L2_RESTART_CNF, skb);
|
|
} else {
|
|
if (l3->x25r.state == ST_R1)
|
|
return(dte_data_ind_p(l3, skb, gfi, channel, ptype));
|
|
if (l3->x25r.state == ST_R3) {
|
|
l3->cause[0] = 0;
|
|
l3->cause[1] = 19;
|
|
mISDN_FsmEvent(&l3->x25r, EV_L3_RESTART_REQ, NULL);
|
|
}
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
static int
|
|
dte_dl_data_ind(x25_l3_t *l3, struct sk_buff *skb)
|
|
{
|
|
int ret;
|
|
__u16 channel;
|
|
u_char gfi, ptype = 0;
|
|
|
|
ret = X25_get_header(l3, skb, &gfi, &channel, &ptype);
|
|
if (ret && (ptype != X25_PTYPE_NOTYPE)) {
|
|
if (test_bit(X25_STATE_DTEDTE, &l3->state))
|
|
X25_send_diagnostic(l3, skb, ret, channel);
|
|
dev_kfree_skb(skb);
|
|
return(0);
|
|
}
|
|
switch(ptype) {
|
|
case X25_PTYPE_DIAGNOSTIC:
|
|
ret = got_diagnositic(l3, skb, gfi, channel);
|
|
break;
|
|
case X25_PTYPE_REGISTER:
|
|
ret = got_register(l3, skb, gfi, channel);
|
|
break;
|
|
case X25_PTYPE_REGISTER_CNF:
|
|
ret = got_register_cnf(l3, skb, gfi, channel);
|
|
break;
|
|
default:
|
|
ret = dte_data_ind_r(l3, skb, gfi, channel, ptype);
|
|
break;
|
|
}
|
|
if (ret == X25_ERRCODE_DISCARD) {
|
|
dev_kfree_skb(skb);
|
|
ret = 0;
|
|
} else if (ret) {
|
|
if (test_bit(X25_STATE_DTEDTE, &l3->state)) {
|
|
if (ptype != X25_PTYPE_NOTYPE) {
|
|
if (test_bit(X25_STATE_MOD32768, &l3->state))
|
|
skb_push(skb, 4);
|
|
else
|
|
skb_push(skb, 3);
|
|
}
|
|
X25_send_diagnostic(l3, skb, ret, channel);
|
|
}
|
|
dev_kfree_skb(skb);
|
|
ret = 0;
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
static int
|
|
dte_from_down(x25_l3_t *l3, struct sk_buff *skb, mISDN_head_t *hh)
|
|
{
|
|
int ret = -EINVAL;
|
|
|
|
if (l3->debug)
|
|
printk(KERN_DEBUG "%s: prim(%x) dinfo(%x)\n", __FUNCTION__, hh->prim, hh->dinfo);
|
|
switch(hh->prim) {
|
|
case DL_DATA_IND:
|
|
ret = dte_dl_data_ind(l3, skb);
|
|
break;
|
|
case DL_DATA | CONFIRM:
|
|
break;
|
|
case DL_ESTABLISH_CNF:
|
|
ret = mISDN_FsmEvent(&l3->l2l3m, EV_LL_ESTABLISH_CNF, NULL);
|
|
if (ret) {
|
|
int_error();
|
|
}
|
|
ret = 0;
|
|
dev_kfree_skb(skb);
|
|
break;
|
|
case DL_ESTABLISH_IND:
|
|
ret = mISDN_FsmEvent(&l3->l2l3m, EV_LL_ESTABLISH_IND, NULL);
|
|
if (ret) {
|
|
int_error();
|
|
}
|
|
ret = 0;
|
|
dev_kfree_skb(skb);
|
|
break;
|
|
case DL_RELEASE_CNF:
|
|
ret = mISDN_FsmEvent(&l3->l2l3m, EV_LL_RELEASE_CNF, NULL);
|
|
if (ret) {
|
|
int_error();
|
|
}
|
|
ret = 0;
|
|
dev_kfree_skb(skb);
|
|
break;
|
|
case DL_RELEASE_IND:
|
|
ret = mISDN_FsmEvent(&l3->l2l3m, EV_LL_RELEASE_IND, NULL);
|
|
if (ret) {
|
|
int_error();
|
|
}
|
|
ret = 0;
|
|
dev_kfree_skb(skb);
|
|
break;
|
|
default:
|
|
int_error();
|
|
break;
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
static x25_channel_t *
|
|
dte_create_channel(x25_l3_t *l3, int typ, u_char flag, __u16 ch, int len, u_char *data)
|
|
{
|
|
x25_channel_t *l3c;
|
|
__u16 nch = ch;
|
|
int ret;
|
|
|
|
if (typ == X25_CHANNEL_OUTGOING) {
|
|
if (ch == 0) {
|
|
/* first search for allready created channels in P1 state */
|
|
if (l3->B3cfg.HOC) {
|
|
for (nch = l3->B3cfg.HOC; (nch && (nch >= l3->B3cfg.LOC)); nch--) {
|
|
l3c = X25_get_channel(l3, nch);
|
|
if (l3c && (l3c->x25p.state == ST_P1)) {
|
|
X25_realloc_ncpi_data(l3c, len, data);
|
|
return(l3c);
|
|
}
|
|
}
|
|
}
|
|
if (l3->B3cfg.HTC) {
|
|
for (nch = l3->B3cfg.HTC; (nch && (nch >= l3->B3cfg.LTC)); nch--) {
|
|
l3c = X25_get_channel(l3, nch);
|
|
if (l3c && (l3c->x25p.state == ST_P1)) {
|
|
X25_realloc_ncpi_data(l3c, len, data);
|
|
return(l3c);
|
|
}
|
|
}
|
|
}
|
|
/* now search for still unused channels */
|
|
nch = 0;
|
|
if (l3->B3cfg.HOC) {
|
|
l3c = (x25_channel_t *)1; /* if loop is not executed */
|
|
for (nch = l3->B3cfg.HOC; (nch && (nch >= l3->B3cfg.LOC)); nch--) {
|
|
l3c = X25_get_channel(l3, nch);
|
|
if (!l3c)
|
|
break;
|
|
}
|
|
if (l3c)
|
|
nch = 0;
|
|
}
|
|
if ((nch == 0) && l3->B3cfg.HTC) {
|
|
l3c = (x25_channel_t *)1; /* if loop is not executed */
|
|
for (nch = l3->B3cfg.HTC; (nch && (nch >= l3->B3cfg.LTC)); nch--) {
|
|
l3c = X25_get_channel(l3, nch);
|
|
if (!l3c)
|
|
break;
|
|
}
|
|
if (l3c)
|
|
nch = 0;
|
|
}
|
|
} else {
|
|
if (ch >= l3->B3cfg.LIC) /* not a permanent channel */
|
|
return(NULL);
|
|
l3c = X25_get_channel(l3, nch);
|
|
if (l3c) {
|
|
if (test_bit(X25_STATE_PERMANENT, &l3c->state)) {
|
|
if (l3c->ncci) /* allready in use */
|
|
return(NULL);
|
|
else {
|
|
X25_realloc_ncpi_data(l3c, len, data);
|
|
return(l3c);
|
|
}
|
|
}
|
|
}
|
|
nch = ch;
|
|
}
|
|
if (flag & 1)
|
|
flag = X25_GFI_DBIT;
|
|
} else {
|
|
if (!ch) /* not Reference Number procedure implemented */
|
|
return(NULL);
|
|
if (l3->B3cfg.HTC) {
|
|
if (ch > l3->B3cfg.HTC)
|
|
return(NULL);
|
|
} else if (l3->B3cfg.HIC) {
|
|
if (ch > l3->B3cfg.HIC)
|
|
return(NULL);
|
|
}
|
|
nch = ch;
|
|
if (l3->B3cfg.LIC && ch < l3->B3cfg.LIC) /* permanent channel */
|
|
nch = ch;
|
|
else {
|
|
nch = ch;
|
|
ch = 0;
|
|
}
|
|
}
|
|
if (!nch)
|
|
return(NULL);
|
|
ret = new_x25_channel(l3, &l3c, nch, len, data);
|
|
if (ret)
|
|
return(NULL);
|
|
l3c->x25p.fsm = &dte_pfsm;
|
|
l3c->x25d.fsm = &dte_dfsm;
|
|
if (ch) {
|
|
test_and_set_bit(X25_STATE_PERMANENT, &l3c->state);
|
|
if (l3c->l3->x25r.state == ST_R1) {
|
|
l3c->x25p.state = ST_P4;
|
|
l3c->x25d.state = ST_D1;
|
|
} else {
|
|
l3c->x25p.state = ST_P0;
|
|
l3c->x25d.state = ST_D0;
|
|
}
|
|
} else {
|
|
test_and_clear_bit(X25_STATE_PERMANENT, &l3c->state);
|
|
if (l3c->l3->x25r.state == ST_R1) {
|
|
l3c->x25p.state = ST_P1;
|
|
l3c->x25d.state = ST_D0;
|
|
} else {
|
|
l3c->x25p.state = ST_P0;
|
|
l3c->x25d.state = ST_D0;
|
|
}
|
|
}
|
|
if (flag & X25_GFI_DBIT)
|
|
test_and_set_bit(X25_STATE_DBIT, &l3c->state);
|
|
else
|
|
test_and_clear_bit(X25_STATE_DBIT, &l3c->state);
|
|
if (flag & X25_GFI_ABIT)
|
|
test_and_set_bit(X25_STATE_ABIT, &l3c->state);
|
|
else
|
|
test_and_clear_bit(X25_STATE_DBIT, &l3c->state);
|
|
return(l3c);
|
|
}
|
|
|
|
static int
|
|
dte_from_up(x25_l3_t *l3, struct sk_buff *skb, mISDN_head_t *hh)
|
|
{
|
|
x25_channel_t *l3c;
|
|
__u32 addr;
|
|
__u16 info = 0;
|
|
int err = 0;
|
|
|
|
if (skb->len < 4) {
|
|
printk(KERN_WARNING "%s: skb too short (%d)\n", __FUNCTION__, skb->len);
|
|
return(-EINVAL);
|
|
} else {
|
|
addr = CAPIMSG_U32(skb->data, 0);
|
|
skb_pull(skb, 4);
|
|
}
|
|
if (l3->debug)
|
|
printk(KERN_DEBUG "%s: addr(%x)\n", __FUNCTION__, addr);
|
|
l3c = X25_get_channel4NCCI(l3, addr);
|
|
switch(hh->prim) {
|
|
case CAPI_DATA_B3_REQ:
|
|
info = x25_data_b3_req(l3c, hh->dinfo, skb);
|
|
if (info) {
|
|
if (info == CAPI_SENDQUEUEFULL) {
|
|
err = -EXFULL;
|
|
break;
|
|
}
|
|
skb_trim(skb, 0);
|
|
memcpy(skb_put(skb, 2), &info, 2);
|
|
err = X25sendL4skb(l3c, l3, addr, CAPI_RESET_B3_CONF, hh->dinfo, skb);
|
|
} else
|
|
err = 0;
|
|
break;
|
|
case CAPI_DATA_B3_RESP:
|
|
return(x25_data_b3_resp(l3c, hh->dinfo, skb));
|
|
case CAPI_CONNECT_B3_REQ:
|
|
if (!l3c) {
|
|
x25_ncpi_t *ncpi;
|
|
if (skb->len <= 4) { // default NCPI
|
|
u_char a = 0;
|
|
l3c = dte_create_channel(l3, X25_CHANNEL_OUTGOING, 0, 0, 1, &a);
|
|
} else {
|
|
ncpi = (x25_ncpi_t *)skb->data;
|
|
l3c = dte_create_channel(l3, X25_CHANNEL_OUTGOING, ncpi->Flags,
|
|
(ncpi->Group<<8) | ncpi->Channel,
|
|
ncpi->len - 3, &ncpi->Contens[0]);
|
|
}
|
|
if (l3c)
|
|
l3c->ncci = addr | (l3c->lchan << 16);
|
|
}
|
|
if (l3c) {
|
|
err = 0;
|
|
if (l3->x25r.state == ST_R0)
|
|
mISDN_FsmEvent(&l3->x25r, EV_L3_RESTART_REQ, "\000\000");
|
|
else
|
|
err = mISDN_FsmEvent(&l3c->x25p, EV_L3_OUTGOING_CALL, NULL);
|
|
if (err)
|
|
info = 0x2001; /* wrong state */
|
|
else
|
|
info = 0;
|
|
} else
|
|
info = 0x2004; /* no NCCI available */
|
|
skb_trim(skb, 0);
|
|
memcpy(skb_put(skb, 2), &info, 2);
|
|
err = X25sendL4skb(l3c, l3, addr, CAPI_CONNECT_B3_CONF, hh->dinfo, skb);
|
|
break;
|
|
case CAPI_RESET_B3_REQ:
|
|
if (l3c) {
|
|
if (!(l3c->ncci & 0xffff))
|
|
l3c->ncci = addr;
|
|
if (skb->len <= 4) { // default NCPI
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[1] = 0;
|
|
err = mISDN_FsmEvent(&l3c->x25d, EV_L3_RESETING, NULL);
|
|
} else {
|
|
skb_pull(skb, 4);
|
|
err = mISDN_FsmEvent(&l3c->x25d, EV_L3_RESETING, skb);
|
|
skb_push(skb, 4);
|
|
}
|
|
if (err)
|
|
info = 0x2001;
|
|
else
|
|
info = 0;
|
|
} else
|
|
info = 0x2002;
|
|
skb_trim(skb, 0);
|
|
memcpy(skb_put(skb, 2), &info, 2);
|
|
err = X25sendL4skb(l3c, l3, addr, CAPI_RESET_B3_CONF, hh->dinfo, skb);
|
|
break;
|
|
case CAPI_DISCONNECT_B3_REQ:
|
|
if (l3c) {
|
|
if (!(l3c->ncci & 0xffff))
|
|
l3c->ncci = addr;
|
|
if (skb->len <= 4) { // default NCPI
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[1] = 0;
|
|
err = mISDN_FsmEvent(&l3c->x25p, EV_L3_CLEARING, NULL);
|
|
} else {
|
|
skb_pull(skb, 4);
|
|
err = mISDN_FsmEvent(&l3c->x25p, EV_L3_CLEARING, skb);
|
|
skb_push(skb, 4);
|
|
}
|
|
if (err)
|
|
info = 0x2001;
|
|
else
|
|
info = 0;
|
|
} else
|
|
info = 0x2002;
|
|
|
|
skb_trim(skb, 0);
|
|
memcpy(skb_put(skb, 2), &info, 2);
|
|
err = X25sendL4skb(l3c, l3, addr, CAPI_DISCONNECT_B3_CONF, hh->dinfo, skb);
|
|
break;
|
|
case CAPI_CONNECT_B3_RESP:
|
|
if (l3c) {
|
|
int event = EV_L3_CLEARING;
|
|
|
|
l3c->ncci = addr;
|
|
if (skb->len <= 2) {
|
|
printk(KERN_WARNING "%s: CAPI_CONNECT_B3_RESP skb too short (%d)\n",
|
|
__FUNCTION__, skb->len);
|
|
skb_push(skb, 4);
|
|
return(-EINVAL);
|
|
}
|
|
info = CAPIMSG_U16(skb->data, 0);
|
|
skb_pull(skb, 2);
|
|
if (info == 0)
|
|
event = EV_L3_CALL_ACCEPT;
|
|
if (skb->len <= 4) { // default NCPI
|
|
l3c->cause[0] = 0;
|
|
l3c->cause[1] = 0;
|
|
err = mISDN_FsmEvent(&l3c->x25p, event, NULL);
|
|
} else {
|
|
skb_pull(skb, 4);
|
|
err = mISDN_FsmEvent(&l3c->x25p, event, skb);
|
|
}
|
|
} else {
|
|
skb_push(skb, 4);
|
|
printk(KERN_WARNING "%s: CAPI_CONNECT_B3_RESP no channel found\n",
|
|
__FUNCTION__);
|
|
return(-ENODEV);
|
|
}
|
|
dev_kfree_skb(skb);
|
|
err = 0;
|
|
break;
|
|
case CAPI_CONNECT_B3_ACTIVE_RESP:
|
|
case CAPI_RESET_B3_RESP:
|
|
if (!l3c) {
|
|
skb_push(skb, 4);
|
|
printk(KERN_WARNING "%s: prim %x dinfo %x no channel found\n",
|
|
__FUNCTION__, hh->prim, hh->dinfo);
|
|
return(-ENODEV);
|
|
}
|
|
dev_kfree_skb(skb);
|
|
err = 0;
|
|
break;
|
|
case CAPI_DISCONNECT_B3_RESP:
|
|
if (l3c) {
|
|
l3c->ncci = 0;
|
|
// TODO release NCCI
|
|
} else {
|
|
skb_push(skb, 4);
|
|
printk(KERN_WARNING "%s: CAPI_DISCONNECT_B3_RESP no channel found\n",
|
|
__FUNCTION__);
|
|
return(-ENODEV);
|
|
}
|
|
dev_kfree_skb(skb);
|
|
err = 0;
|
|
break;
|
|
default:
|
|
printk(KERN_WARNING "%s: unknown prim %x dinfo %x\n",
|
|
__FUNCTION__, hh->prim, hh->dinfo);
|
|
err = -EINVAL;
|
|
}
|
|
return(err);
|
|
}
|
|
|
|
static int
|
|
dte_function(mISDNinstance_t *inst, struct sk_buff *skb)
|
|
{
|
|
x25_l3_t *l3;
|
|
mISDN_head_t *hh;
|
|
int ret = -EINVAL;
|
|
|
|
l3 = inst->privat;
|
|
hh = mISDN_HEAD_P(skb);
|
|
if (l3->debug)
|
|
printk(KERN_DEBUG "%s: addr(%08x) prim(%x) dinfo(%x) len(%d)\n", __FUNCTION__,
|
|
hh->addr, hh->prim, hh->dinfo, skb->len);
|
|
if (debug)
|
|
printk(KERN_DEBUG "%s: addr(%08x) prim(%x)\n", __FUNCTION__, hh->addr, hh->prim);
|
|
if (!l3)
|
|
return(ret);
|
|
|
|
switch(hh->addr & MSG_DIR_MASK) {
|
|
case FLG_MSG_DOWN:
|
|
ret = dte_from_up(l3, skb, hh);
|
|
break;
|
|
case FLG_MSG_UP:
|
|
case MSG_BROADCAST: // we define broaadcast comes from down below
|
|
ret = dte_from_down(l3, skb, hh);
|
|
break;
|
|
case MSG_TO_OWNER:
|
|
/* FIXME: must be handled depending on type */
|
|
int_errtxt("not implemented yet");
|
|
break;
|
|
default:
|
|
/* FIXME: must be handled depending on type */
|
|
int_errtxt("not implemented yet");
|
|
break;
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
static int
|
|
new_l3(mISDNstack_t *st, mISDN_pid_t *pid) {
|
|
x25_l3_t *n_l3;
|
|
int err;
|
|
|
|
err = new_x25_l3(&n_l3, st, pid, &x25dte_obj, debug, dte_function);
|
|
if (err)
|
|
return(err);
|
|
n_l3->x25r.fsm = &dte_rfsm;
|
|
n_l3->x25r.state = ST_R0;
|
|
return(0);
|
|
}
|
|
|
|
|
|
static char MName[] = "X25_DTE";
|
|
|
|
#ifdef MODULE
|
|
MODULE_AUTHOR("Karsten Keil");
|
|
MODULE_PARM(debug, "1i");
|
|
#ifdef MODULE_LICENSE
|
|
MODULE_LICENSE("GPL");
|
|
#endif
|
|
#endif
|
|
|
|
static int
|
|
dte_manager(void *data, u_int prim, void *arg) {
|
|
mISDNinstance_t *inst = data;
|
|
x25_l3_t *l3_l;
|
|
int err = -EINVAL;
|
|
u_long flags;
|
|
|
|
if (debug & DEBUG_L3X25_MGR)
|
|
printk(KERN_DEBUG "l3x25_manager data:%p prim:%x arg:%p\n", data, prim, arg);
|
|
if (!data)
|
|
return(err);
|
|
spin_lock_irqsave(&x25dte_obj.lock, flags);
|
|
list_for_each_entry(l3_l, &x25dte_obj.ilist, list) {
|
|
if (&l3_l->inst == inst) {
|
|
err = 0;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&x25dte_obj.lock, flags);
|
|
if (prim == (MGR_NEWLAYER | REQUEST))
|
|
return(new_l3(data, arg));
|
|
if (err) {
|
|
printk(KERN_WARNING "l3x25_manager prim(%x) no instance\n", prim);
|
|
return(err);
|
|
}
|
|
switch(prim) {
|
|
case MGR_NEWENTITY | CONFIRM:
|
|
l3_l->entity = (u_long)arg & 0xffffffff;
|
|
break;
|
|
case MGR_ADDSTPARA | INDICATION:
|
|
{
|
|
mISDN_stPara_t *stp = arg;
|
|
|
|
if (stp->down_headerlen)
|
|
l3_l->down_headerlen = stp->down_headerlen;
|
|
if (stp->up_headerlen)
|
|
l3_l->up_headerlen = stp->up_headerlen;
|
|
printk(KERN_DEBUG "MGR_ADDSTPARA: (%d/%d/%d)\n",
|
|
stp->maxdatalen, stp->down_headerlen, stp->up_headerlen);
|
|
}
|
|
break;
|
|
case MGR_CLRSTPARA | INDICATION:
|
|
#ifdef OBSOLETE
|
|
case MGR_CLONELAYER | REQUEST:
|
|
break;
|
|
case MGR_CONNECT | REQUEST:
|
|
return(mISDN_ConnectIF(inst, arg));
|
|
case MGR_SETIF | REQUEST:
|
|
case MGR_SETIF | INDICATION:
|
|
return(mISDN_SetIF(inst, arg, prim, dte_from_up, dte_from_down, l3_l));
|
|
case MGR_DISCONNECT | REQUEST:
|
|
case MGR_DISCONNECT | INDICATION:
|
|
return(mISDN_DisConnectIF(inst, arg));
|
|
#endif
|
|
case MGR_UNREGLAYER | REQUEST:
|
|
case MGR_RELEASE | INDICATION:
|
|
if (debug & DEBUG_L3X25_MGR)
|
|
printk(KERN_DEBUG "X25_release_l3 id %x\n", l3_l->inst.st->id);
|
|
X25_release_l3(l3_l);
|
|
break;
|
|
// case MGR_STATUS | REQUEST:
|
|
// return(l3x25_status(l3x25_l, arg));
|
|
default:
|
|
if (debug & DEBUG_L3X25_MGR)
|
|
printk(KERN_WARNING "l3x25_manager prim %x not handled\n", prim);
|
|
return(-EINVAL);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
static int
|
|
x25_dte_init(void)
|
|
{
|
|
int err;
|
|
|
|
printk(KERN_INFO "X25 DTE modul version %s\n", mISDN_getrev(mISDN_dte_revision));
|
|
#ifdef MODULE
|
|
x25dte_obj.owner = THIS_MODULE;
|
|
#endif
|
|
x25dte_obj.name = MName;
|
|
x25dte_obj.BPROTO.protocol[3] = ISDN_PID_L3_B_X25DTE;
|
|
x25dte_obj.own_ctrl = dte_manager;
|
|
spin_lock_init(&x25dte_obj.lock);
|
|
INIT_LIST_HEAD(&x25dte_obj.ilist);
|
|
if ((err = mISDN_register(&x25dte_obj))) {
|
|
printk(KERN_ERR "Can't register %s error(%d)\n", MName, err);
|
|
} else {
|
|
X25_l3_init();
|
|
dte_rfsm.state_count = R_STATE_COUNT;
|
|
dte_rfsm.event_count = R_EVENT_COUNT;
|
|
dte_rfsm.strEvent = X25strREvent;
|
|
dte_rfsm.strState = X25strRState;
|
|
mISDN_FsmNew(&dte_rfsm, RFnList, R_FN_COUNT);
|
|
dte_pfsm.state_count = P_STATE_COUNT;
|
|
dte_pfsm.event_count = P_EVENT_COUNT;
|
|
dte_pfsm.strEvent = X25strPEvent;
|
|
dte_pfsm.strState = X25strPState;
|
|
mISDN_FsmNew(&dte_pfsm, PFnList, P_FN_COUNT);
|
|
dte_dfsm.state_count = D_STATE_COUNT;
|
|
dte_dfsm.event_count = D_EVENT_COUNT;
|
|
dte_dfsm.strEvent = X25strDEvent;
|
|
dte_dfsm.strState = X25strDState;
|
|
mISDN_FsmNew(&dte_dfsm, DFnList, D_FN_COUNT);
|
|
}
|
|
return(err);
|
|
}
|
|
|
|
static void
|
|
x25_dte_cleanup(void)
|
|
{
|
|
x25_l3_t *l3, *nl3;
|
|
int err;
|
|
|
|
if ((err = mISDN_unregister(&x25dte_obj))) {
|
|
printk(KERN_ERR "Can't unregister l3x25 error(%d)\n", err);
|
|
}
|
|
if(!list_empty(&x25dte_obj.ilist)) {
|
|
printk(KERN_WARNING "l3x25 inst list not empty\n");
|
|
list_for_each_entry_safe(l3, nl3, &x25dte_obj.ilist, list)
|
|
X25_release_l3(l3);
|
|
}
|
|
X25_l3_cleanup();
|
|
mISDN_FsmFree(&dte_rfsm);
|
|
mISDN_FsmFree(&dte_pfsm);
|
|
mISDN_FsmFree(&dte_dfsm);
|
|
}
|
|
|
|
module_init(x25_dte_init);
|
|
module_exit(x25_dte_cleanup);
|