2005-04-17 02:20:36 +04:00
/* $Id: isdnl3.c,v 2.22.2.3 2004/01/13 14:31:25 keil Exp $
*
* Author Karsten Keil
* based on the teles driver from Jan den Ouden
* Copyright by Karsten Keil < keil @ isdn4linux . de >
*
* This software may be used and distributed according to the terms
* of the GNU General Public License , incorporated herein by reference .
*
* For changes and modifications please read
* Documentation / isdn / HiSax . cert
*
* Thanks to Jan den Ouden
* Fritz Elfert
*
*/
# include <linux/init.h>
# include "hisax.h"
# include "isdnl3.h"
const char * l3_revision = " $Revision: 2.22.2.3 $ " ;
static struct Fsm l3fsm ;
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 , . . . )
{
va_list args ;
struct PStack * st = fi - > userdata ;
va_start ( args , fmt ) ;
VHiSax_putstatus ( st - > l1 . hardware , st - > l3 . debug_id , fmt , args ) ;
va_end ( 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 ( * p & 0x80 )
p + + ;
else {
if ( codeset = = wanted_set ) {
if ( * p = = ie )
{ /* improved length check (Werner Cornelius) */
if ( ( pend - p ) < 2 )
return ( NULL ) ;
if ( * ( p + 1 ) > ( pend - ( p + 2 ) ) )
return ( NULL ) ;
return ( p ) ;
}
if ( * p > ie )
return ( NULL ) ;
}
p + + ;
l = * p + + ;
p + = l ;
codeset = maincodeset ;
}
}
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 ( struct l3_process * pc , int state )
{
if ( pc - > debug & L3_DEB_STATE )
l3_debug ( pc - > st , " newstate cr %d %d --> %d " ,
pc - > callref & 0x7F ,
pc - > state , state ) ;
pc - > state = state ;
}
static void
L3ExpireTimer ( struct L3Timer * t )
{
t - > pc - > st - > lli . l4l3 ( t - > pc - > st , t - > event , t - > pc ) ;
}
void
L3InitTimer ( struct l3_process * pc , struct L3Timer * t )
{
t - > pc = pc ;
t - > tl . function = ( void * ) L3ExpireTimer ;
t - > tl . data = ( long ) t ;
init_timer ( & t - > tl ) ;
}
void
L3DelTimer ( struct L3Timer * t )
{
del_timer ( & t - > tl ) ;
}
int
L3AddTimer ( struct L3Timer * 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 ( struct l3_process * pc )
{
L3DelTimer ( & pc - > timer ) ;
}
struct sk_buff *
l3_alloc_skb ( int len )
{
struct sk_buff * skb ;
if ( ! ( skb = alloc_skb ( len + MAX_HEADER_LEN , GFP_ATOMIC ) ) ) {
printk ( KERN_WARNING " HiSax: No skb for D-channel \n " ) ;
return ( NULL ) ;
}
skb_reserve ( skb , MAX_HEADER_LEN ) ;
return ( skb ) ;
}
static void
no_l3_proto ( struct PStack * st , int pr , void * arg )
{
struct sk_buff * skb = arg ;
HiSax_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 " HiSax: no specific protocol handler for proto %lu \n " , ic - > arg & 0xFF ) ;
return ( - 1 ) ;
}
struct l3_process
* getl3proc ( struct PStack * st , int cr )
{
struct l3_process * p = st - > l3 . proc ;
while ( p )
if ( p - > callref = = cr )
return ( p ) ;
else
p = p - > next ;
return ( NULL ) ;
}
struct l3_process
* new_l3_process ( struct PStack * st , int cr )
{
struct l3_process * p , * np ;
if ( ! ( p = kmalloc ( sizeof ( struct l3_process ) , GFP_ATOMIC ) ) ) {
printk ( KERN_ERR " HiSax can't get memory for cr %d \n " , cr ) ;
return ( NULL ) ;
}
if ( ! st - > l3 . proc )
st - > l3 . proc = p ;
else {
np = st - > l3 . proc ;
while ( np - > next )
np = np - > next ;
np - > next = p ;
}
p - > next = NULL ;
p - > debug = st - > l3 . debug ;
p - > callref = cr ;
p - > state = 0 ;
p - > chan = NULL ;
p - > st = st ;
p - > N303 = st - > l3 . N303 ;
L3InitTimer ( p , & p - > timer ) ;
return ( p ) ;
} ;
void
release_l3_process ( struct l3_process * p )
{
struct l3_process * np , * pp = NULL ;
if ( ! p )
return ;
np = p - > st - > l3 . proc ;
while ( np ) {
if ( np = = p ) {
StopAllL3Timer ( p ) ;
if ( pp )
pp - > next = np - > next ;
else if ( ! ( p - > st - > l3 . proc = np - > next ) & &
! test_bit ( FLG_PTP , & p - > st - > l2 . flag ) ) {
if ( p - > debug )
l3_debug ( p - > st , " release_l3_process: last process " ) ;
2005-07-09 01:57:23 +04:00
if ( skb_queue_empty ( & p - > st - > l3 . squeue ) ) {
2005-04-17 02:20:36 +04:00
if ( p - > debug )
l3_debug ( p - > st , " release_l3_process: release link " ) ;
if ( p - > st - > protocol ! = ISDN_PTYPE_NI1 )
FsmEvent ( & p - > st - > l3 . l3m , EV_RELEASE_REQ , NULL ) ;
else
FsmEvent ( & p - > st - > l3 . l3m , EV_RELEASE_IND , NULL ) ;
} else {
if ( p - > debug )
l3_debug ( p - > st , " release_l3_process: not release link " ) ;
}
}
kfree ( p ) ;
return ;
}
pp = np ;
np = np - > next ;
}
printk ( KERN_ERR " HiSax internal L3 error CR(%d) not in list \n " , p - > callref ) ;
l3_debug ( p - > st , " HiSax internal L3 error CR(%d) not in list " , p - > callref ) ;
} ;
static void
l3ml3p ( struct PStack * st , int pr )
{
struct l3_process * p = st - > l3 . proc ;
struct l3_process * np ;
while ( p ) {
/* p might be kfreed under us, so we need to save where we want to go on */
np = p - > next ;
st - > l3 . l3ml3 ( st , pr , p ) ;
p = np ;
}
}
void
setstack_l3dc ( struct PStack * st , struct Channel * chanp )
{
char tmp [ 64 ] ;
st - > l3 . proc = NULL ;
st - > l3 . global = NULL ;
skb_queue_head_init ( & st - > l3 . squeue ) ;
st - > l3 . l3m . fsm = & l3fsm ;
st - > l3 . l3m . state = ST_L3_LC_REL ;
st - > l3 . l3m . debug = 1 ;
st - > l3 . l3m . userdata = st ;
st - > l3 . l3m . userint = 0 ;
st - > l3 . l3m . printdebug = l3m_debug ;
FsmInitTimer ( & st - > l3 . l3m , & st - > l3 . l3m_timer ) ;
strcpy ( st - > l3 . debug_id , " L3DC " ) ;
st - > lli . l4l3_proto = no_l3_proto_spec ;
# ifdef CONFIG_HISAX_EURO
if ( st - > protocol = = ISDN_PTYPE_EURO ) {
setstack_dss1 ( st ) ;
} else
# endif
# ifdef CONFIG_HISAX_NI1
if ( st - > protocol = = ISDN_PTYPE_NI1 ) {
setstack_ni1 ( st ) ;
} else
# endif
# ifdef CONFIG_HISAX_1TR6
if ( st - > protocol = = ISDN_PTYPE_1TR6 ) {
setstack_1tr6 ( st ) ;
} else
# endif
if ( st - > protocol = = ISDN_PTYPE_LEASED ) {
st - > lli . l4l3 = no_l3_proto ;
st - > l2 . l2l3 = no_l3_proto ;
st - > l3 . l3ml3 = no_l3_proto ;
printk ( KERN_INFO " HiSax: Leased line mode \n " ) ;
} else {
st - > lli . l4l3 = no_l3_proto ;
st - > l2 . l2l3 = no_l3_proto ;
st - > l3 . l3ml3 = no_l3_proto ;
sprintf ( tmp , " protocol %s not supported " ,
( st - > protocol = = ISDN_PTYPE_1TR6 ) ? " 1tr6 " :
( st - > protocol = = ISDN_PTYPE_EURO ) ? " euro " :
( st - > protocol = = ISDN_PTYPE_NI1 ) ? " ni1 " :
" unknown " ) ;
printk ( KERN_WARNING " HiSax: %s \n " , tmp ) ;
st - > protocol = - 1 ;
}
}
2005-06-26 01:59:18 +04:00
static void
2005-04-17 02:20:36 +04:00
isdnl3_trans ( struct PStack * st , int pr , void * arg ) {
st - > l3 . l3l2 ( st , pr , arg ) ;
}
void
releasestack_isdnl3 ( struct PStack * st )
{
while ( st - > l3 . proc )
release_l3_process ( st - > l3 . proc ) ;
if ( st - > l3 . global ) {
StopAllL3Timer ( st - > l3 . global ) ;
kfree ( st - > l3 . global ) ;
st - > l3 . global = NULL ;
}
FsmDelTimer ( & st - > l3 . l3m_timer , 54 ) ;
skb_queue_purge ( & st - > l3 . squeue ) ;
}
void
setstack_l3bc ( struct PStack * st , struct Channel * chanp )
{
st - > l3 . proc = NULL ;
st - > l3 . global = NULL ;
skb_queue_head_init ( & st - > l3 . squeue ) ;
st - > l3 . l3m . fsm = & l3fsm ;
st - > l3 . l3m . state = ST_L3_LC_REL ;
st - > l3 . l3m . debug = 1 ;
st - > l3 . l3m . userdata = st ;
st - > l3 . l3m . userint = 0 ;
st - > l3 . l3m . printdebug = l3m_debug ;
strcpy ( st - > l3 . debug_id , " L3BC " ) ;
st - > lli . l4l3 = isdnl3_trans ;
}
# define DREL_TIMER_VALUE 40000
static void
lc_activate ( struct FsmInst * fi , int event , void * arg )
{
struct PStack * st = fi - > userdata ;
FsmChangeState ( fi , ST_L3_LC_ESTAB_WAIT ) ;
st - > l3 . l3l2 ( st , DL_ESTABLISH | REQUEST , NULL ) ;
}
static void
lc_connect ( struct FsmInst * fi , int event , void * arg )
{
struct PStack * st = fi - > userdata ;
struct sk_buff * skb = arg ;
int dequeued = 0 ;
FsmChangeState ( fi , ST_L3_LC_ESTAB ) ;
while ( ( skb = skb_dequeue ( & st - > l3 . squeue ) ) ) {
st - > l3 . l3l2 ( st , DL_DATA | REQUEST , skb ) ;
dequeued + + ;
}
if ( ( ! st - > l3 . proc ) & & dequeued ) {
if ( st - > l3 . debug )
l3_debug ( st , " lc_connect: release link " ) ;
FsmEvent ( & st - > l3 . l3m , EV_RELEASE_REQ , NULL ) ;
} else
l3ml3p ( st , DL_ESTABLISH | INDICATION ) ;
}
static void
lc_connected ( struct FsmInst * fi , int event , void * arg )
{
struct PStack * st = fi - > userdata ;
struct sk_buff * skb = arg ;
int dequeued = 0 ;
FsmDelTimer ( & st - > l3 . l3m_timer , 51 ) ;
FsmChangeState ( fi , ST_L3_LC_ESTAB ) ;
while ( ( skb = skb_dequeue ( & st - > l3 . squeue ) ) ) {
st - > l3 . l3l2 ( st , DL_DATA | REQUEST , skb ) ;
dequeued + + ;
}
if ( ( ! st - > l3 . proc ) & & dequeued ) {
if ( st - > l3 . debug )
l3_debug ( st , " lc_connected: release link " ) ;
FsmEvent ( & st - > l3 . l3m , EV_RELEASE_REQ , NULL ) ;
} else
l3ml3p ( st , DL_ESTABLISH | CONFIRM ) ;
}
static void
lc_start_delay ( struct FsmInst * fi , int event , void * arg )
{
struct PStack * st = fi - > userdata ;
FsmChangeState ( fi , ST_L3_LC_REL_DELAY ) ;
FsmAddTimer ( & st - > l3 . l3m_timer , DREL_TIMER_VALUE , EV_TIMEOUT , NULL , 50 ) ;
}
static void
lc_start_delay_check ( struct FsmInst * fi , int event , void * arg )
/* 20/09/00 - GE timer not user for NI-1 as layer 2 should stay up */
{
struct PStack * st = fi - > userdata ;
FsmChangeState ( fi , ST_L3_LC_REL_DELAY ) ;
/* 19/09/00 - GE timer not user for NI-1 */
if ( st - > protocol ! = ISDN_PTYPE_NI1 )
FsmAddTimer ( & st - > l3 . l3m_timer , DREL_TIMER_VALUE , EV_TIMEOUT , NULL , 50 ) ;
}
static void
lc_release_req ( struct FsmInst * fi , int event , void * arg )
{
struct PStack * st = fi - > userdata ;
if ( test_bit ( FLG_L2BLOCK , & st - > l2 . flag ) ) {
if ( st - > l3 . debug )
l3_debug ( st , " lc_release_req: l2 blocked " ) ;
/* restart release timer */
FsmAddTimer ( & st - > l3 . l3m_timer , DREL_TIMER_VALUE , EV_TIMEOUT , NULL , 51 ) ;
} else {
FsmChangeState ( fi , ST_L3_LC_REL_WAIT ) ;
st - > l3 . l3l2 ( st , DL_RELEASE | REQUEST , NULL ) ;
}
}
static void
lc_release_ind ( struct FsmInst * fi , int event , void * arg )
{
struct PStack * st = fi - > userdata ;
FsmDelTimer ( & st - > l3 . l3m_timer , 52 ) ;
FsmChangeState ( fi , ST_L3_LC_REL ) ;
skb_queue_purge ( & st - > l3 . squeue ) ;
l3ml3p ( st , DL_RELEASE | INDICATION ) ;
}
static void
lc_release_cnf ( struct FsmInst * fi , int event , void * arg )
{
struct PStack * st = fi - > userdata ;
FsmChangeState ( fi , ST_L3_LC_REL ) ;
skb_queue_purge ( & st - > l3 . squeue ) ;
l3ml3p ( st , DL_RELEASE | CONFIRM ) ;
}
/* *INDENT-OFF* */
static struct FsmNode L3FnList [ ] __initdata =
{
{ 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_check } ,
{ 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_msg ( struct PStack * st , int pr , void * arg )
{
switch ( pr ) {
case ( DL_DATA | REQUEST ) :
if ( st - > l3 . l3m . state = = ST_L3_LC_ESTAB ) {
st - > l3 . l3l2 ( st , pr , arg ) ;
} else {
struct sk_buff * skb = arg ;
skb_queue_tail ( & st - > l3 . squeue , skb ) ;
FsmEvent ( & st - > l3 . l3m , EV_ESTABLISH_REQ , NULL ) ;
}
break ;
case ( DL_ESTABLISH | REQUEST ) :
FsmEvent ( & st - > l3 . l3m , EV_ESTABLISH_REQ , NULL ) ;
break ;
case ( DL_ESTABLISH | CONFIRM ) :
FsmEvent ( & st - > l3 . l3m , EV_ESTABLISH_CNF , NULL ) ;
break ;
case ( DL_ESTABLISH | INDICATION ) :
FsmEvent ( & st - > l3 . l3m , EV_ESTABLISH_IND , NULL ) ;
break ;
case ( DL_RELEASE | INDICATION ) :
FsmEvent ( & st - > l3 . l3m , EV_RELEASE_IND , NULL ) ;
break ;
case ( DL_RELEASE | CONFIRM ) :
FsmEvent ( & st - > l3 . l3m , EV_RELEASE_CNF , NULL ) ;
break ;
case ( DL_RELEASE | REQUEST ) :
FsmEvent ( & st - > l3 . l3m , EV_RELEASE_REQ , NULL ) ;
break ;
}
}
int __init
Isdnl3New ( void )
{
l3fsm . state_count = L3_STATE_COUNT ;
l3fsm . event_count = L3_EVENT_COUNT ;
l3fsm . strEvent = strL3Event ;
l3fsm . strState = strL3State ;
return FsmNew ( & l3fsm , L3FnList , L3_FN_COUNT ) ;
}
void
Isdnl3Free ( void )
{
FsmFree ( & l3fsm ) ;
}