/* * NET An implementation of the IEEE 802.2 LLC protocol for the * LINUX operating system. LLC is implemented as a set of * state machines and callbacks for higher networking layers. * * llc_sendpdu(), llc_sendipdu(), resend() + queue handling code * * Written by Tim Alpaerts, Tim_Alpaerts@toyota-motor-europe.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Changes * Alan Cox : Chainsawed into Linux format, style * Added llc_ to function names */ #include #include #include #include #include #include #include #include #include #include static unsigned char cntl_byte_encode[] = { 0x00, /* I_CMD */ 0x01, /* RR_CMD */ 0x05, /* RNR_CMD */ 0x09, /* REJ_CMD */ 0x43, /* DISC_CMD */ 0x7F, /* SABME_CMD */ 0x00, /* I_RSP */ 0x01, /* RR_RSP */ 0x05, /* RNR_RSP */ 0x09, /* REJ_RSP */ 0x63, /* UA_RSP */ 0x0F, /* DM_RSP */ 0x87, /* FRMR_RSP */ 0xFF, /* BAD_FRAME */ 0x03, /* UI_CMD */ 0xBF, /* XID_CMD */ 0xE3, /* TEST_CMD */ 0xBF, /* XID_RSP */ 0xE3 /* TEST_RSP */ }; static unsigned char fr_length_encode[] = { 0x04, /* I_CMD */ 0x04, /* RR_CMD */ 0x04, /* RNR_CMD */ 0x04, /* REJ_CMD */ 0x03, /* DISC_CMD */ 0x03, /* SABME_CMD */ 0x04, /* I_RSP */ 0x04, /* RR_RSP */ 0x04, /* RNR_RSP */ 0x04, /* REJ_RSP */ 0x03, /* UA_RSP */ 0x03, /* DM_RSP */ 0x03, /* FRMR_RSP */ 0x00, /* BAD_FRAME */ 0x03, /* UI_CMD */ 0x03, /* XID_CMD */ 0x03, /* TEST_CMD */ 0x03, /* XID_RSP */ 0x03 /* TEST_RSP */ }; static unsigned char cr_bit_encode[] = { 0x00, /* I_CMD */ 0x00, /* RR_CMD */ 0x00, /* RNR_CMD */ 0x00, /* REJ_CMD */ 0x00, /* DISC_CMD */ 0x00, /* SABME_CMD */ 0x01, /* I_RSP */ 0x01, /* RR_RSP */ 0x01, /* RNR_RSP */ 0x01, /* REJ_RSP */ 0x01, /* UA_RSP */ 0x01, /* DM_RSP */ 0x01, /* FRMR_RSP */ 0x00, /* BAD_FRAME */ 0x00, /* UI_CMD */ 0x00, /* XID_CMD */ 0x00, /* TEST_CMD */ 0x01, /* XID_RSP */ 0x01 /* TEST_RSP */ }; /* * Sendpdu() constructs an output frame in a new skb and * gives it to the MAC layer for transmision. * This function is not used to send I pdus. * No queues are updated here, nothing is saved for retransmission. * * Parameter pf controls both the poll/final bit and dsap * fields in the output pdu. * The dsap trick was needed to implement XID_CMD send with * 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) { frameptr fr; /* ptr to output pdu buffer */ unsigned short int fl; /* frame length == 802.3 "length" value */ struct sk_buff *skb; fl = data_len + fr_length_encode[(int)type]; skb = alloc_skb(16 + fl, GFP_ATOMIC); if (skb != NULL) { skb->dev = lp->dev; skb_reserve(skb, 16); fr = (frameptr) skb_put(skb, fl); memset(fr, 0, fl); /* * Construct 802.2 header */ if (pf & 0x02) fr->pdu_hdr.dsap = 0; else fr->pdu_hdr.dsap = lp->remote_sap; fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type]; fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type]; /* * Fill in pflag and seq nbrs: */ if (IS_SFRAME(fr)) { /* case S-frames */ if (pf & 0x01) fr->i_hdr.i_pflag = 1; fr->i_hdr.nr = lp->vr; } else { /* case U frames */ if (pf & 0x01) fr->u_hdr.u_pflag = 1; } if (data_len > 0) { /* append data if any */ if (IS_UFRAME(fr)) { memcpy(fr->u_hdr.u_info, pdu_data, data_len); } else { memcpy(fr->i_hdr.is_info, pdu_data, data_len); } } lp->dev->hard_header(skb, lp->dev, ETH_P_802_3, lp->remote_mac, NULL, fl); skb->arp = 1; skb->dev=lp->dev; dev_queue_xmit(skb); } else printk(KERN_DEBUG "cl2llc: skb_alloc() in llc_sendpdu() failed\n"); } void llc_xid_request(llcptr lp, char opt, int ll, char * data) { llc_sendpdu(lp, XID_CMD, opt, ll, data); } void llc_test_request(llcptr lp, int ll, char * data) { llc_sendpdu(lp, TEST_CMD, 0, ll, data); } void llc_unit_data_request(llcptr lp, int ll, char * data) { llc_sendpdu(lp, UI_CMD, 0, ll, data); } /* * llc_sendipdu() Completes an I pdu in an existing skb and gives it * to the MAC layer for transmision. * Parameter "type" must be either I_CMD or I_RSP. * 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. */ void llc_sendipdu(llcptr lp, char type, char pf, struct sk_buff *skb) { frameptr fr; /* ptr to output pdu buffer */ struct sk_buff *tmp; fr = (frameptr) skb->data; fr->pdu_hdr.dsap = lp->remote_sap; fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type]; fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type]; if (pf) fr->i_hdr.i_pflag = 1; /* p/f and seq numbers */ fr->i_hdr.nr = lp->vr; fr->i_hdr.ns = lp->vs; lp->vs++; if (lp->vs > 127) lp->vs = 0; lp->dev->hard_header(skb, lp->dev, ETH_P_802_3, lp->remote_mac, NULL, skb->len); skb->arp = 1; ADD_TO_RTQ(skb); /* add skb to the retransmit queue */ tmp=skb_clone(skb, GFP_ATOMIC); if(tmp!=NULL) { tmp->dev=lp->dev; dev_queue_xmit(tmp); } } /* * Resend_ipdu() will resend the pdus in the retransmit queue (rtq) * the return value is the number of pdus resend. * ack_nr is N(R) of 1st pdu to resent. * Type is I_CMD or I_RSP 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 */ int llc_resend_ipdu(llcptr lp, unsigned char ack_nr, unsigned char type, char p) { struct sk_buff *skb,*tmp; int resend_count; frameptr fr; unsigned long flags; resend_count = 0; save_flags(flags); cli(); skb = skb_peek(&lp->rtq); while(skb && skb != (struct sk_buff *)&lp->rtq) { fr = (frameptr) (skb->data + lp->dev->hard_header_len); if (resend_count == 0) { /* * Resending 1st pdu: */ if (p) fr->i_hdr.i_pflag = 1; else fr->i_hdr.i_pflag = 0; if (type == I_CMD) fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe; else fr->pdu_hdr.ssap = fr->pdu_hdr.ssap | 0x01; } else { /* * Resending pdu 2...n */ fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe; fr->i_hdr.i_pflag = 0; } fr->i_hdr.nr = lp->vr; fr->i_hdr.ns = lp->vs; lp->vs++; if (lp->vs > 127) lp->vs = 0; tmp=skb_clone(skb, GFP_ATOMIC); if(tmp!=NULL) { tmp->arp = 1; tmp->dev = lp->dev; dev_queue_xmit(skb); } resend_count++; skb = skb->next; } restore_flags(flags); return resend_count; } /* ************** internal queue management code ****************** */ /* * Remove one skb from the front of the awaiting transmit queue * (this is the skb longest on the queue) and return a pointer to * that skb. */ struct sk_buff *llc_pull_from_atq(llcptr lp) { return skb_dequeue(&lp->atq); } /* * Free_acknowledged_skbs(), remove from retransmit queue (rtq) * and free all skbs with an N(S) chronologicaly before 'pdu_ack'. * The return value is the number of pdus acknowledged. */ int llc_free_acknowledged_skbs(llcptr lp, unsigned char pdu_ack) { struct sk_buff *pp; frameptr fr; int ack_count; unsigned char ack; /* N(S) of most recently ack'ed pdu */ unsigned char ns_save; unsigned long flags; if (pdu_ack > 0) ack = pdu_ack -1; else ack = 127; ack_count = 0; save_flags(flags); cli(); pp = skb_dequeue(&lp->rtq); while (pp != NULL) { /* * Locate skb with N(S) == ack */ /* * BUG: FIXME - use skb->h.* */ fr = (frameptr) (pp->data + lp->dev->hard_header_len); ns_save = fr->i_hdr.ns; kfree_skb(pp, FREE_WRITE); ack_count++; if (ns_save == ack) break; pp = skb_dequeue(&lp->rtq); } restore_flags(flags); return ack_count; }