/*
|
/*
|
* NET An implementation of the IEEE 802.2 LLC protocol for the
|
* NET An implementation of the IEEE 802.2 LLC protocol for the
|
* LINUX operating system. LLC is implemented as a set of
|
* LINUX operating system. LLC is implemented as a set of
|
* state machines and callbacks for higher networking layers.
|
* state machines and callbacks for higher networking layers.
|
*
|
*
|
* Small utilities, Linux timer handling.
|
* Small utilities, Linux timer handling.
|
*
|
*
|
* Written by Tim Alpaerts, Tim_Alpaerts@toyota-motor-europe.com
|
* Written by Tim Alpaerts, Tim_Alpaerts@toyota-motor-europe.com
|
*
|
*
|
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
* as published by the Free Software Foundation; either version
|
* as published by the Free Software Foundation; either version
|
* 2 of the License, or (at your option) any later version.
|
* 2 of the License, or (at your option) any later version.
|
*
|
*
|
* Changes
|
* Changes
|
* Alan Cox : Chainsawed into Linux form.
|
* Alan Cox : Chainsawed into Linux form.
|
* Added llc_ function name prefixes.
|
* Added llc_ function name prefixes.
|
* Fixed bug in stop/start timer.
|
* Fixed bug in stop/start timer.
|
* Added llc_cancel_timers for closing
|
* Added llc_cancel_timers for closing
|
* down an llc
|
* down an llc
|
*/
|
*/
|
|
|
#include <linux/types.h>
|
#include <linux/types.h>
|
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
#include <linux/netdevice.h>
|
#include <linux/netdevice.h>
|
#include <linux/skbuff.h>
|
#include <linux/skbuff.h>
|
#include <linux/proc_fs.h>
|
#include <linux/proc_fs.h>
|
#include <linux/stat.h>
|
#include <linux/stat.h>
|
#include <net/llc_frame.h>
|
#include <net/llc_frame.h>
|
#include <net/llc.h>
|
#include <net/llc.h>
|
|
|
int llc_decode_frametype(frameptr fr)
|
int llc_decode_frametype(frameptr fr)
|
{
|
{
|
if (IS_UFRAME(fr))
|
if (IS_UFRAME(fr))
|
{ /* unnumbered cmd/rsp */
|
{ /* unnumbered cmd/rsp */
|
switch(fr->u_mm.mm & 0x3B)
|
switch(fr->u_mm.mm & 0x3B)
|
{
|
{
|
case 0x1B:
|
case 0x1B:
|
return(SABME_CMD);
|
return(SABME_CMD);
|
break;
|
break;
|
case 0x10:
|
case 0x10:
|
return(DISC_CMD);
|
return(DISC_CMD);
|
break;
|
break;
|
case 0x18:
|
case 0x18:
|
return(UA_RSP);
|
return(UA_RSP);
|
break;
|
break;
|
case 0x03:
|
case 0x03:
|
return(DM_RSP);
|
return(DM_RSP);
|
break;
|
break;
|
case 0x21:
|
case 0x21:
|
return(FRMR_RSP);
|
return(FRMR_RSP);
|
break;
|
break;
|
case 0x00:
|
case 0x00:
|
return(UI_CMD);
|
return(UI_CMD);
|
break;
|
break;
|
case 0x2B:
|
case 0x2B:
|
if (IS_RSP(fr))
|
if (IS_RSP(fr))
|
return(XID_RSP);
|
return(XID_RSP);
|
else
|
else
|
return(XID_CMD);
|
return(XID_CMD);
|
break;
|
break;
|
case 0x38:
|
case 0x38:
|
if (IS_RSP(fr))
|
if (IS_RSP(fr))
|
return(TEST_RSP);
|
return(TEST_RSP);
|
else
|
else
|
return(TEST_CMD);
|
return(TEST_CMD);
|
break;
|
break;
|
default:
|
default:
|
return(BAD_FRAME);
|
return(BAD_FRAME);
|
}
|
}
|
}
|
}
|
else if (IS_SFRAME(fr))
|
else if (IS_SFRAME(fr))
|
{ /* supervisory cmd/rsp */
|
{ /* supervisory cmd/rsp */
|
switch(fr->s_hdr.ss)
|
switch(fr->s_hdr.ss)
|
{
|
{
|
case 0x00:
|
case 0x00:
|
if (IS_RSP(fr))
|
if (IS_RSP(fr))
|
return(RR_RSP);
|
return(RR_RSP);
|
else
|
else
|
return(RR_CMD);
|
return(RR_CMD);
|
break;
|
break;
|
case 0x02:
|
case 0x02:
|
if (IS_RSP(fr))
|
if (IS_RSP(fr))
|
return(REJ_RSP);
|
return(REJ_RSP);
|
else
|
else
|
return(REJ_CMD);
|
return(REJ_CMD);
|
break;
|
break;
|
case 0x01:
|
case 0x01:
|
if (IS_RSP(fr))
|
if (IS_RSP(fr))
|
return(RNR_RSP);
|
return(RNR_RSP);
|
else
|
else
|
return(RNR_CMD);
|
return(RNR_CMD);
|
break;
|
break;
|
default:
|
default:
|
return(BAD_FRAME);
|
return(BAD_FRAME);
|
}
|
}
|
}
|
}
|
else
|
else
|
{ /* information xfer */
|
{ /* information xfer */
|
if (IS_RSP(fr))
|
if (IS_RSP(fr))
|
return(I_RSP);
|
return(I_RSP);
|
else
|
else
|
return(I_CMD);
|
return(I_CMD);
|
}
|
}
|
}
|
}
|
|
|
|
|
/*
|
/*
|
* Validate_seq_nos will check N(S) and N(R) to see if they are
|
* Validate_seq_nos will check N(S) and N(R) to see if they are
|
* invalid or unexpected.
|
* invalid or unexpected.
|
* "unexpected" is explained on p44 Send State Variable.
|
* "unexpected" is explained on p44 Send State Variable.
|
* The return value is:
|
* The return value is:
|
* 4 * invalid N(R) +
|
* 4 * invalid N(R) +
|
* 2 * invalid N(S) +
|
* 2 * invalid N(S) +
|
* 1 * unexpected N(S)
|
* 1 * unexpected N(S)
|
*/
|
*/
|
|
|
int llc_validate_seq_nos(llcptr lp, frameptr fr)
|
int llc_validate_seq_nos(llcptr lp, frameptr fr)
|
{
|
{
|
int res;
|
int res;
|
|
|
/*
|
/*
|
* A U-frame is always good
|
* A U-frame is always good
|
*/
|
*/
|
|
|
if (IS_UFRAME(fr))
|
if (IS_UFRAME(fr))
|
return(0);
|
return(0);
|
|
|
/*
|
/*
|
* For S- and I-frames check N(R):
|
* For S- and I-frames check N(R):
|
*/
|
*/
|
|
|
if (fr->i_hdr.nr == lp->vs)
|
if (fr->i_hdr.nr == lp->vs)
|
{ /* if N(R) = V(S) */
|
{ /* if N(R) = V(S) */
|
res = 0; /* N(R) is good */
|
res = 0; /* N(R) is good */
|
}
|
}
|
else
|
else
|
{ /* lp->k = transmit window size */
|
{ /* lp->k = transmit window size */
|
if (lp->vs >= lp->k)
|
if (lp->vs >= lp->k)
|
{ /* if window not wrapped around 127 */
|
{ /* if window not wrapped around 127 */
|
if ((fr->i_hdr.nr < lp->vs) &&
|
if ((fr->i_hdr.nr < lp->vs) &&
|
(fr->i_hdr.nr > (lp->vs - lp->k)))
|
(fr->i_hdr.nr > (lp->vs - lp->k)))
|
res = 0;
|
res = 0;
|
else
|
else
|
res = 4; /* N(R) invalid */
|
res = 4; /* N(R) invalid */
|
}
|
}
|
else
|
else
|
{ /* window wraps around 127 */
|
{ /* window wraps around 127 */
|
if ((fr->i_hdr.nr < lp->vs) ||
|
if ((fr->i_hdr.nr < lp->vs) ||
|
(fr->i_hdr.nr > (128 + lp->vs - lp->k)))
|
(fr->i_hdr.nr > (128 + lp->vs - lp->k)))
|
res = 0;
|
res = 0;
|
else
|
else
|
res = 4; /* N(R) invalid */
|
res = 4; /* N(R) invalid */
|
}
|
}
|
}
|
}
|
|
|
/*
|
/*
|
* For an I-frame, must check N(S) also:
|
* For an I-frame, must check N(S) also:
|
*/
|
*/
|
|
|
if (IS_IFRAME(fr))
|
if (IS_IFRAME(fr))
|
{
|
{
|
if (fr->i_hdr.ns == lp->vr)
|
if (fr->i_hdr.ns == lp->vr)
|
return res; /* N(S) good */
|
return res; /* N(S) good */
|
if (lp->vr >= lp->rw)
|
if (lp->vr >= lp->rw)
|
{
|
{
|
/* if receive window not wrapped */
|
/* if receive window not wrapped */
|
|
|
if ((fr->i_hdr.ns < lp->vr) &&
|
if ((fr->i_hdr.ns < lp->vr) &&
|
(fr->i_hdr.ns > (lp->vr - lp->k)))
|
(fr->i_hdr.ns > (lp->vr - lp->k)))
|
res = res +1; /* N(S) unexpected */
|
res = res +1; /* N(S) unexpected */
|
else
|
else
|
res = res +2; /* N(S) invalid */
|
res = res +2; /* N(S) invalid */
|
}
|
}
|
else
|
else
|
{
|
{
|
/* Window wraps around 127 */
|
/* Window wraps around 127 */
|
|
|
if ((fr->i_hdr.ns < lp->vr) ||
|
if ((fr->i_hdr.ns < lp->vr) ||
|
(fr->i_hdr.ns > (128 + lp->vr - lp->k)))
|
(fr->i_hdr.ns > (128 + lp->vr - lp->k)))
|
res = res +1; /* N(S) unexpected */
|
res = res +1; /* N(S) unexpected */
|
else
|
else
|
res = res +2; /* N(S) invalid */
|
res = res +2; /* N(S) invalid */
|
}
|
}
|
}
|
}
|
return(res);
|
return(res);
|
}
|
}
|
|
|
/* **************** timer management routines ********************* */
|
/* **************** timer management routines ********************* */
|
|
|
static void llc_p_timer_expired(unsigned long ulp)
|
static void llc_p_timer_expired(unsigned long ulp)
|
{
|
{
|
llc_timer_expired((llcptr) ulp, P_TIMER);
|
llc_timer_expired((llcptr) ulp, P_TIMER);
|
}
|
}
|
|
|
static void llc_rej_timer_expired(unsigned long ulp)
|
static void llc_rej_timer_expired(unsigned long ulp)
|
{
|
{
|
llc_timer_expired((llcptr) ulp, REJ_TIMER);
|
llc_timer_expired((llcptr) ulp, REJ_TIMER);
|
}
|
}
|
|
|
static void llc_ack_timer_expired(unsigned long ulp)
|
static void llc_ack_timer_expired(unsigned long ulp)
|
{
|
{
|
llc_timer_expired((llcptr) ulp, ACK_TIMER);
|
llc_timer_expired((llcptr) ulp, ACK_TIMER);
|
}
|
}
|
|
|
static void llc_busy_timer_expired(unsigned long ulp)
|
static void llc_busy_timer_expired(unsigned long ulp)
|
{
|
{
|
llc_timer_expired((llcptr) ulp, BUSY_TIMER);
|
llc_timer_expired((llcptr) ulp, BUSY_TIMER);
|
}
|
}
|
|
|
/* exp_fcn is an array holding the 4 entry points of the
|
/* exp_fcn is an array holding the 4 entry points of the
|
timer expiry routines above.
|
timer expiry routines above.
|
It is required to keep start_timer() generic.
|
It is required to keep start_timer() generic.
|
Thank you cdecl.
|
Thank you cdecl.
|
*/
|
*/
|
|
|
static void (* exp_fcn[])(unsigned long) =
|
static void (* exp_fcn[])(unsigned long) =
|
{
|
{
|
llc_p_timer_expired,
|
llc_p_timer_expired,
|
llc_rej_timer_expired,
|
llc_rej_timer_expired,
|
llc_ack_timer_expired,
|
llc_ack_timer_expired,
|
llc_busy_timer_expired
|
llc_busy_timer_expired
|
};
|
};
|
|
|
void llc_start_timer(llcptr lp, int t)
|
void llc_start_timer(llcptr lp, int t)
|
{
|
{
|
if (lp->timer_state[t] == TIMER_IDLE)
|
if (lp->timer_state[t] == TIMER_IDLE)
|
{
|
{
|
lp->tl[t].expires = jiffies + lp->timer_interval[t];
|
lp->tl[t].expires = jiffies + lp->timer_interval[t];
|
lp->tl[t].data = (unsigned long) lp;
|
lp->tl[t].data = (unsigned long) lp;
|
lp->tl[t].function = exp_fcn[t];
|
lp->tl[t].function = exp_fcn[t];
|
add_timer(&lp->tl[t]);
|
add_timer(&lp->tl[t]);
|
lp->timer_state[t] = TIMER_RUNNING;
|
lp->timer_state[t] = TIMER_RUNNING;
|
}
|
}
|
}
|
}
|
|
|
void llc_stop_timer(llcptr lp, int t)
|
void llc_stop_timer(llcptr lp, int t)
|
{
|
{
|
if (lp->timer_state[t] == TIMER_RUNNING)
|
if (lp->timer_state[t] == TIMER_RUNNING)
|
{
|
{
|
del_timer(&lp->tl[t]);
|
del_timer(&lp->tl[t]);
|
lp->timer_state[t] = TIMER_IDLE;
|
lp->timer_state[t] = TIMER_IDLE;
|
}
|
}
|
}
|
}
|
|
|
void llc_cancel_timers(llcptr lp)
|
void llc_cancel_timers(llcptr lp)
|
{
|
{
|
llc_stop_timer(lp, P_TIMER);
|
llc_stop_timer(lp, P_TIMER);
|
llc_stop_timer(lp, REJ_TIMER);
|
llc_stop_timer(lp, REJ_TIMER);
|
llc_stop_timer(lp, ACK_TIMER);
|
llc_stop_timer(lp, ACK_TIMER);
|
llc_stop_timer(lp, BUSY_TIMER);
|
llc_stop_timer(lp, BUSY_TIMER);
|
}
|
}
|
|
|
|
|