/*
|
/*
|
* 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.
|
*
|
*
|
* llc_sendpdu(), llc_sendipdu(), resend() + queue handling code
|
* llc_sendpdu(), llc_sendipdu(), resend() + queue handling code
|
*
|
*
|
* 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 format, style
|
* Alan Cox : Chainsawed into Linux format, style
|
* Added llc_ to function names
|
* Added llc_ to function names
|
*/
|
*/
|
|
|
#include <linux/types.h>
|
#include <linux/types.h>
|
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
#include <linux/slab.h>
|
#include <linux/slab.h>
|
#include <linux/netdevice.h>
|
#include <linux/netdevice.h>
|
#include <linux/skbuff.h>
|
#include <linux/skbuff.h>
|
#include <net/p8022.h>
|
#include <net/p8022.h>
|
#include <linux/stat.h>
|
#include <linux/stat.h>
|
#include <asm/byteorder.h>
|
#include <asm/byteorder.h>
|
#include <net/llc_frame.h>
|
#include <net/llc_frame.h>
|
#include <net/llc.h>
|
#include <net/llc.h>
|
|
|
static unsigned char cntl_byte_encode[] =
|
static unsigned char cntl_byte_encode[] =
|
{
|
{
|
0x00, /* I_CMD */
|
0x00, /* I_CMD */
|
0x01, /* RR_CMD */
|
0x01, /* RR_CMD */
|
0x05, /* RNR_CMD */
|
0x05, /* RNR_CMD */
|
0x09, /* REJ_CMD */
|
0x09, /* REJ_CMD */
|
0x43, /* DISC_CMD */
|
0x43, /* DISC_CMD */
|
0x7F, /* SABME_CMD */
|
0x7F, /* SABME_CMD */
|
0x00, /* I_RSP */
|
0x00, /* I_RSP */
|
0x01, /* RR_RSP */
|
0x01, /* RR_RSP */
|
0x05, /* RNR_RSP */
|
0x05, /* RNR_RSP */
|
0x09, /* REJ_RSP */
|
0x09, /* REJ_RSP */
|
0x63, /* UA_RSP */
|
0x63, /* UA_RSP */
|
0x0F, /* DM_RSP */
|
0x0F, /* DM_RSP */
|
0x87, /* FRMR_RSP */
|
0x87, /* FRMR_RSP */
|
0xFF, /* BAD_FRAME */
|
0xFF, /* BAD_FRAME */
|
0x03, /* UI_CMD */
|
0x03, /* UI_CMD */
|
0xBF, /* XID_CMD */
|
0xBF, /* XID_CMD */
|
0xE3, /* TEST_CMD */
|
0xE3, /* TEST_CMD */
|
0xBF, /* XID_RSP */
|
0xBF, /* XID_RSP */
|
0xE3 /* TEST_RSP */
|
0xE3 /* TEST_RSP */
|
};
|
};
|
|
|
static unsigned char fr_length_encode[] =
|
static unsigned char fr_length_encode[] =
|
{
|
{
|
0x04, /* I_CMD */
|
0x04, /* I_CMD */
|
0x04, /* RR_CMD */
|
0x04, /* RR_CMD */
|
0x04, /* RNR_CMD */
|
0x04, /* RNR_CMD */
|
0x04, /* REJ_CMD */
|
0x04, /* REJ_CMD */
|
0x03, /* DISC_CMD */
|
0x03, /* DISC_CMD */
|
0x03, /* SABME_CMD */
|
0x03, /* SABME_CMD */
|
0x04, /* I_RSP */
|
0x04, /* I_RSP */
|
0x04, /* RR_RSP */
|
0x04, /* RR_RSP */
|
0x04, /* RNR_RSP */
|
0x04, /* RNR_RSP */
|
0x04, /* REJ_RSP */
|
0x04, /* REJ_RSP */
|
0x03, /* UA_RSP */
|
0x03, /* UA_RSP */
|
0x03, /* DM_RSP */
|
0x03, /* DM_RSP */
|
0x03, /* FRMR_RSP */
|
0x03, /* FRMR_RSP */
|
0x00, /* BAD_FRAME */
|
0x00, /* BAD_FRAME */
|
0x03, /* UI_CMD */
|
0x03, /* UI_CMD */
|
0x03, /* XID_CMD */
|
0x03, /* XID_CMD */
|
0x03, /* TEST_CMD */
|
0x03, /* TEST_CMD */
|
0x03, /* XID_RSP */
|
0x03, /* XID_RSP */
|
0x03 /* TEST_RSP */
|
0x03 /* TEST_RSP */
|
};
|
};
|
|
|
static unsigned char cr_bit_encode[] = {
|
static unsigned char cr_bit_encode[] = {
|
0x00, /* I_CMD */
|
0x00, /* I_CMD */
|
0x00, /* RR_CMD */
|
0x00, /* RR_CMD */
|
0x00, /* RNR_CMD */
|
0x00, /* RNR_CMD */
|
0x00, /* REJ_CMD */
|
0x00, /* REJ_CMD */
|
0x00, /* DISC_CMD */
|
0x00, /* DISC_CMD */
|
0x00, /* SABME_CMD */
|
0x00, /* SABME_CMD */
|
0x01, /* I_RSP */
|
0x01, /* I_RSP */
|
0x01, /* RR_RSP */
|
0x01, /* RR_RSP */
|
0x01, /* RNR_RSP */
|
0x01, /* RNR_RSP */
|
0x01, /* REJ_RSP */
|
0x01, /* REJ_RSP */
|
0x01, /* UA_RSP */
|
0x01, /* UA_RSP */
|
0x01, /* DM_RSP */
|
0x01, /* DM_RSP */
|
0x01, /* FRMR_RSP */
|
0x01, /* FRMR_RSP */
|
0x00, /* BAD_FRAME */
|
0x00, /* BAD_FRAME */
|
0x00, /* UI_CMD */
|
0x00, /* UI_CMD */
|
0x00, /* XID_CMD */
|
0x00, /* XID_CMD */
|
0x00, /* TEST_CMD */
|
0x00, /* TEST_CMD */
|
0x01, /* XID_RSP */
|
0x01, /* XID_RSP */
|
0x01 /* TEST_RSP */
|
0x01 /* TEST_RSP */
|
};
|
};
|
|
|
/*
|
/*
|
* Sendpdu() constructs an output frame in a new skb and
|
* Sendpdu() constructs an output frame in a new skb and
|
* gives it to the MAC layer for transmission.
|
* gives it to the MAC layer for transmission.
|
* This function is not used to send I pdus.
|
* This function is not used to send I pdus.
|
* No queues are updated here, nothing is saved for retransmission.
|
* No queues are updated here, nothing is saved for retransmission.
|
*
|
*
|
* Parameter pf controls both the poll/final bit and dsap
|
* Parameter pf controls both the poll/final bit and dsap
|
* fields in the output pdu.
|
* fields in the output pdu.
|
* The dsap trick was needed to implement XID_CMD send with
|
* The dsap trick was needed to implement XID_CMD send with
|
* zero dsap field as described in doc 6.6 item 1 of enum.
|
* zero dsap field as described in doc 6.6 item 1 of enum.
|
*/
|
*/
|
|
|
void llc_sendpdu(llcptr lp, char type, char pf, int data_len, char *pdu_data)
|
void llc_sendpdu(llcptr lp, char type, char pf, int data_len, char *pdu_data)
|
{
|
{
|
frameptr fr; /* ptr to output pdu buffer */
|
frameptr fr; /* ptr to output pdu buffer */
|
unsigned short int fl; /* frame length == 802.3 "length" value */
|
unsigned short int fl; /* frame length == 802.3 "length" value */
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
|
|
fl = data_len + fr_length_encode[(int)type];
|
fl = data_len + fr_length_encode[(int)type];
|
skb = alloc_skb(16 + fl, GFP_ATOMIC);
|
skb = alloc_skb(16 + fl, GFP_ATOMIC);
|
if (skb != NULL)
|
if (skb != NULL)
|
{
|
{
|
skb->dev = lp->dev;
|
skb->dev = lp->dev;
|
skb_reserve(skb, 16);
|
skb_reserve(skb, 16);
|
fr = (frameptr) skb_put(skb, fl);
|
fr = (frameptr) skb_put(skb, fl);
|
memset(fr, 0, fl);
|
memset(fr, 0, fl);
|
/*
|
/*
|
* Construct 802.2 header
|
* Construct 802.2 header
|
*/
|
*/
|
if (pf & 0x02)
|
if (pf & 0x02)
|
fr->pdu_hdr.dsap = 0;
|
fr->pdu_hdr.dsap = 0;
|
else
|
else
|
fr->pdu_hdr.dsap = lp->remote_sap;
|
fr->pdu_hdr.dsap = lp->remote_sap;
|
fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type];
|
fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type];
|
fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type];
|
fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type];
|
/*
|
/*
|
* Fill in pflag and seq nbrs:
|
* Fill in pflag and seq nbrs:
|
*/
|
*/
|
if (IS_SFRAME(fr))
|
if (IS_SFRAME(fr))
|
{
|
{
|
/* case S-frames */
|
/* case S-frames */
|
if (pf & 0x01)
|
if (pf & 0x01)
|
fr->i_hdr.i_pflag = 1;
|
fr->i_hdr.i_pflag = 1;
|
fr->i_hdr.nr = lp->vr;
|
fr->i_hdr.nr = lp->vr;
|
}
|
}
|
else
|
else
|
{
|
{
|
/* case U frames */
|
/* case U frames */
|
if (pf & 0x01)
|
if (pf & 0x01)
|
fr->u_hdr.u_pflag = 1;
|
fr->u_hdr.u_pflag = 1;
|
}
|
}
|
|
|
if (data_len > 0)
|
if (data_len > 0)
|
{ /* append data if any */
|
{ /* append data if any */
|
if (IS_UFRAME(fr))
|
if (IS_UFRAME(fr))
|
{
|
{
|
memcpy(fr->u_hdr.u_info, pdu_data, data_len);
|
memcpy(fr->u_hdr.u_info, pdu_data, data_len);
|
}
|
}
|
else
|
else
|
{
|
{
|
memcpy(fr->i_hdr.is_info, pdu_data, data_len);
|
memcpy(fr->i_hdr.is_info, pdu_data, data_len);
|
}
|
}
|
}
|
}
|
lp->dev->hard_header(skb, lp->dev, ETH_P_802_3,
|
lp->dev->hard_header(skb, lp->dev, ETH_P_802_3,
|
lp->remote_mac, NULL, fl);
|
lp->remote_mac, NULL, fl);
|
skb->dev=lp->dev;
|
skb->dev=lp->dev;
|
dev_queue_xmit(skb);
|
dev_queue_xmit(skb);
|
}
|
}
|
else
|
else
|
printk(KERN_DEBUG "cl2llc: skb_alloc() in llc_sendpdu() failed\n");
|
printk(KERN_DEBUG "cl2llc: skb_alloc() in llc_sendpdu() failed\n");
|
}
|
}
|
|
|
void llc_xid_request(llcptr lp, char opt, int ll, char * data)
|
void llc_xid_request(llcptr lp, char opt, int ll, char * data)
|
{
|
{
|
llc_sendpdu(lp, XID_CMD, opt, ll, data);
|
llc_sendpdu(lp, XID_CMD, opt, ll, data);
|
}
|
}
|
|
|
void llc_test_request(llcptr lp, int ll, char * data)
|
void llc_test_request(llcptr lp, int ll, char * data)
|
{
|
{
|
llc_sendpdu(lp, TEST_CMD, 0, ll, data);
|
llc_sendpdu(lp, TEST_CMD, 0, ll, data);
|
}
|
}
|
|
|
void llc_unit_data_request(llcptr lp, int ll, char * data)
|
void llc_unit_data_request(llcptr lp, int ll, char * data)
|
{
|
{
|
llc_sendpdu(lp, UI_CMD, 0, ll, data);
|
llc_sendpdu(lp, UI_CMD, 0, ll, data);
|
}
|
}
|
|
|
|
|
/*
|
/*
|
* llc_sendipdu() Completes an I pdu in an existing skb and gives it
|
* llc_sendipdu() Completes an I pdu in an existing skb and gives it
|
* to the MAC layer for transmission.
|
* to the MAC layer for transmission.
|
* Parameter "type" must be either I_CMD or I_RSP.
|
* Parameter "type" must be either I_CMD or I_RSP.
|
* The skb is not freed after xmit, it is kept in case a retransmission
|
* The skb is not freed after xmit, it is kept in case a retransmission
|
* is requested. If needed it can be picked up again from the rtq.
|
* is requested. If needed it can be picked up again from the rtq.
|
*/
|
*/
|
|
|
void llc_sendipdu(llcptr lp, char type, char pf, struct sk_buff *skb)
|
void llc_sendipdu(llcptr lp, char type, char pf, struct sk_buff *skb)
|
{
|
{
|
frameptr fr; /* ptr to output pdu buffer */
|
frameptr fr; /* ptr to output pdu buffer */
|
struct sk_buff *tmp;
|
struct sk_buff *tmp;
|
|
|
fr = (frameptr) skb->data;
|
fr = (frameptr) skb->data;
|
|
|
fr->pdu_hdr.dsap = lp->remote_sap;
|
fr->pdu_hdr.dsap = lp->remote_sap;
|
fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type];
|
fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type];
|
fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type];
|
fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type];
|
|
|
if (pf)
|
if (pf)
|
fr->i_hdr.i_pflag = 1; /* p/f and seq numbers */
|
fr->i_hdr.i_pflag = 1; /* p/f and seq numbers */
|
fr->i_hdr.nr = lp->vr;
|
fr->i_hdr.nr = lp->vr;
|
fr->i_hdr.ns = lp->vs;
|
fr->i_hdr.ns = lp->vs;
|
lp->vs++;
|
lp->vs++;
|
if (lp->vs > 127)
|
if (lp->vs > 127)
|
lp->vs = 0;
|
lp->vs = 0;
|
lp->dev->hard_header(skb, lp->dev, ETH_P_802_3,
|
lp->dev->hard_header(skb, lp->dev, ETH_P_802_3,
|
lp->remote_mac, NULL, skb->len);
|
lp->remote_mac, NULL, skb->len);
|
ADD_TO_RTQ(skb); /* add skb to the retransmit queue */
|
ADD_TO_RTQ(skb); /* add skb to the retransmit queue */
|
tmp=skb_clone(skb, GFP_ATOMIC);
|
tmp=skb_clone(skb, GFP_ATOMIC);
|
if(tmp!=NULL)
|
if(tmp!=NULL)
|
{
|
{
|
tmp->dev=lp->dev;
|
tmp->dev=lp->dev;
|
dev_queue_xmit(tmp);
|
dev_queue_xmit(tmp);
|
}
|
}
|
}
|
}
|
|
|
|
|
/*
|
/*
|
* Resend_ipdu() will resend the pdus in the retransmit queue (rtq)
|
* Resend_ipdu() will resend the pdus in the retransmit queue (rtq)
|
* the return value is the number of pdus resend.
|
* the return value is the number of pdus resend.
|
* ack_nr is N(R) of 1st pdu to resent.
|
* ack_nr is N(R) of 1st pdu to resent.
|
* Type is I_CMD or I_RSP for 1st pdu resent.
|
* Type is I_CMD or I_RSP for 1st pdu resent.
|
* p is p/f flag 0 or 1 for 1st pdu resent.
|
* p is p/f flag 0 or 1 for 1st pdu resent.
|
* All subsequent pdus will be sent as I_CMDs with p/f set to 0
|
* All subsequent pdus will be sent as I_CMDs with p/f set to 0
|
*/
|
*/
|
|
|
int llc_resend_ipdu(llcptr lp, unsigned char ack_nr, unsigned char type, char p)
|
int llc_resend_ipdu(llcptr lp, unsigned char ack_nr, unsigned char type, char p)
|
{
|
{
|
struct sk_buff *skb,*tmp;
|
struct sk_buff *skb,*tmp;
|
int resend_count;
|
int resend_count;
|
frameptr fr;
|
frameptr fr;
|
unsigned long flags;
|
unsigned long flags;
|
|
|
|
|
resend_count = 0;
|
resend_count = 0;
|
|
|
save_flags(flags);
|
save_flags(flags);
|
cli();
|
cli();
|
|
|
skb = skb_peek(&lp->rtq);
|
skb = skb_peek(&lp->rtq);
|
|
|
while(skb && skb != (struct sk_buff *)&lp->rtq)
|
while(skb && skb != (struct sk_buff *)&lp->rtq)
|
{
|
{
|
fr = (frameptr) (skb->data + lp->dev->hard_header_len);
|
fr = (frameptr) (skb->data + lp->dev->hard_header_len);
|
if (resend_count == 0)
|
if (resend_count == 0)
|
{
|
{
|
/*
|
/*
|
* Resending 1st pdu:
|
* Resending 1st pdu:
|
*/
|
*/
|
|
|
if (p)
|
if (p)
|
fr->i_hdr.i_pflag = 1;
|
fr->i_hdr.i_pflag = 1;
|
else
|
else
|
fr->i_hdr.i_pflag = 0;
|
fr->i_hdr.i_pflag = 0;
|
|
|
if (type == I_CMD)
|
if (type == I_CMD)
|
fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe;
|
fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe;
|
else
|
else
|
fr->pdu_hdr.ssap = fr->pdu_hdr.ssap | 0x01;
|
fr->pdu_hdr.ssap = fr->pdu_hdr.ssap | 0x01;
|
}
|
}
|
else
|
else
|
{
|
{
|
/*
|
/*
|
* Resending pdu 2...n
|
* Resending pdu 2...n
|
*/
|
*/
|
|
|
fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe;
|
fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe;
|
fr->i_hdr.i_pflag = 0;
|
fr->i_hdr.i_pflag = 0;
|
}
|
}
|
fr->i_hdr.nr = lp->vr;
|
fr->i_hdr.nr = lp->vr;
|
fr->i_hdr.ns = lp->vs;
|
fr->i_hdr.ns = lp->vs;
|
lp->vs++;
|
lp->vs++;
|
if (lp->vs > 127)
|
if (lp->vs > 127)
|
lp->vs = 0;
|
lp->vs = 0;
|
tmp=skb_clone(skb, GFP_ATOMIC);
|
tmp=skb_clone(skb, GFP_ATOMIC);
|
if(tmp!=NULL)
|
if(tmp!=NULL)
|
{
|
{
|
tmp->dev = lp->dev;
|
tmp->dev = lp->dev;
|
dev_queue_xmit(tmp);
|
dev_queue_xmit(tmp);
|
}
|
}
|
resend_count++;
|
resend_count++;
|
skb = skb->next;
|
skb = skb->next;
|
}
|
}
|
restore_flags(flags);
|
restore_flags(flags);
|
return resend_count;
|
return resend_count;
|
}
|
}
|
|
|
/* ************** internal queue management code ****************** */
|
/* ************** internal queue management code ****************** */
|
|
|
|
|
/*
|
/*
|
* Remove one skb from the front of the awaiting transmit queue
|
* Remove one skb from the front of the awaiting transmit queue
|
* (this is the skb longest on the queue) and return a pointer to
|
* (this is the skb longest on the queue) and return a pointer to
|
* that skb.
|
* that skb.
|
*/
|
*/
|
|
|
struct sk_buff *llc_pull_from_atq(llcptr lp)
|
struct sk_buff *llc_pull_from_atq(llcptr lp)
|
{
|
{
|
return skb_dequeue(&lp->atq);
|
return skb_dequeue(&lp->atq);
|
}
|
}
|
|
|
/*
|
/*
|
* Free_acknowledged_skbs(), remove from retransmit queue (rtq)
|
* Free_acknowledged_skbs(), remove from retransmit queue (rtq)
|
* and free all skbs with an N(S) chronologicaly before 'pdu_ack'.
|
* and free all skbs with an N(S) chronologicaly before 'pdu_ack'.
|
* The return value is the number of pdus acknowledged.
|
* The return value is the number of pdus acknowledged.
|
*/
|
*/
|
|
|
int llc_free_acknowledged_skbs(llcptr lp, unsigned char pdu_ack)
|
int llc_free_acknowledged_skbs(llcptr lp, unsigned char pdu_ack)
|
{
|
{
|
struct sk_buff *pp;
|
struct sk_buff *pp;
|
frameptr fr;
|
frameptr fr;
|
int ack_count;
|
int ack_count;
|
unsigned char ack; /* N(S) of most recently ack'ed pdu */
|
unsigned char ack; /* N(S) of most recently ack'ed pdu */
|
unsigned char ns_save;
|
unsigned char ns_save;
|
unsigned long flags;
|
unsigned long flags;
|
|
|
if (pdu_ack > 0)
|
if (pdu_ack > 0)
|
ack = pdu_ack -1;
|
ack = pdu_ack -1;
|
else
|
else
|
ack = 127;
|
ack = 127;
|
|
|
ack_count = 0;
|
ack_count = 0;
|
|
|
save_flags(flags);
|
save_flags(flags);
|
cli();
|
cli();
|
|
|
pp = skb_dequeue(&lp->rtq);
|
pp = skb_dequeue(&lp->rtq);
|
while (pp != NULL)
|
while (pp != NULL)
|
{
|
{
|
/*
|
/*
|
* Locate skb with N(S) == ack
|
* Locate skb with N(S) == ack
|
*/
|
*/
|
|
|
/*
|
/*
|
* BUG: FIXME - use skb->h.*
|
* BUG: FIXME - use skb->h.*
|
*/
|
*/
|
fr = (frameptr) (pp->data + lp->dev->hard_header_len);
|
fr = (frameptr) (pp->data + lp->dev->hard_header_len);
|
ns_save = fr->i_hdr.ns;
|
ns_save = fr->i_hdr.ns;
|
|
|
kfree_skb(pp);
|
kfree_skb(pp);
|
ack_count++;
|
ack_count++;
|
|
|
if (ns_save == ack)
|
if (ns_save == ack)
|
break;
|
break;
|
pp = skb_dequeue(&lp->rtq);
|
pp = skb_dequeue(&lp->rtq);
|
}
|
}
|
restore_flags(flags);
|
restore_flags(flags);
|
return ack_count;
|
return ack_count;
|
}
|
}
|
|
|
|
|