| /* |
| * 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. |
| * |
| * Class 2 llc algorithm. |
| * Pseudocode interpreter, transition table lookup, |
| * data_request & indicate primitives... |
| * |
| * Code for initialization, termination, registration and |
| * MAC layer glue. |
| * |
| * Copyright 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 |
| * Modified to use llc_ names |
| * Changed callbacks |
| * |
| * This file must be processed by sed before it can be compiled. |
| */ |
| |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/netdevice.h> |
| #include <linux/skbuff.h> |
| #include <net/p8022.h> |
| #include <linux/proc_fs.h> |
| #include <linux/stat.h> |
| #include <asm/byteorder.h> |
| |
| #include "pseudo/pseudocode.h" |
| #include "transit/pdutr.h" |
| #include "transit/timertr.h" |
| #include <net/llc_frame.h> |
| #include <net/llc.h> |
| |
| /* |
| * Data_request() is called by the client to present a data unit |
| * to the llc for transmission. |
| * In the future this function should also check if the transmit window |
| * allows the sending of another pdu, and if not put the skb on the atq |
| * for deferred sending. |
| */ |
| |
| int llc_data_request(llcptr lp, struct sk_buff *skb) |
| { |
| if (skb_headroom(skb) < (lp->dev->hard_header_len +4)){ |
| printk("cl2llc: data_request() not enough headroom in skb\n"); |
| return -1; |
| }; |
| |
| skb_push(skb, 4); |
| |
| if ((lp->state != NORMAL) && (lp->state != BUSY) && (lp->state != REJECT)) |
| { |
| printk("cl2llc: data_request() while no llc connection\n"); |
| return -1; |
| } |
| |
| if (lp->remote_busy) |
| { /* if the remote llc is BUSY, */ |
| ADD_TO_ATQ(skb); /* save skb in the await transmit queue */ |
| return 0; |
| } |
| else |
| { |
| /* |
| * Else proceed with xmit |
| */ |
| |
| switch(lp->state) |
| { |
| case NORMAL: |
| if(lp->p_flag) |
| llc_interpret_pseudo_code(lp, NORMAL2, skb, NO_FRAME); |
| else |
| llc_interpret_pseudo_code(lp, NORMAL1, skb, NO_FRAME); |
| break; |
| case BUSY: |
| if (lp->p_flag) |
| llc_interpret_pseudo_code(lp, BUSY2, skb, NO_FRAME); |
| else |
| llc_interpret_pseudo_code(lp, BUSY1, skb, NO_FRAME); |
| break; |
| case REJECT: |
| if (lp->p_flag) |
| llc_interpret_pseudo_code(lp, REJECT2, skb, NO_FRAME); |
| else |
| llc_interpret_pseudo_code(lp, REJECT1, skb, NO_FRAME); |
| break; |
| default: |
| } |
| if(lp->llc_callbacks) |
| { |
| lp->llc_event(lp); |
| lp->llc_callbacks=0; |
| } |
| return 0; |
| } |
| } |
| |
| |
| |
| /* |
| * Disconnect_request() requests that the llc to terminate a connection |
| */ |
| |
| void disconnect_request(llcptr lp) |
| { |
| if ((lp->state == NORMAL) || |
| (lp->state == BUSY) || |
| (lp->state == REJECT) || |
| (lp->state == AWAIT) || |
| (lp->state == AWAIT_BUSY) || |
| (lp->state == AWAIT_REJECT)) |
| { |
| lp->state = D_CONN; |
| llc_interpret_pseudo_code(lp, SH1, NULL, NO_FRAME); |
| if(lp->llc_callbacks) |
| { |
| lp->llc_event(lp); |
| lp->llc_callbacks=0; |
| } |
| /* |
| * lp may be invalid after the callback |
| */ |
| } |
| } |
| |
| |
| /* |
| * Connect_request() requests that the llc to start a connection |
| */ |
| |
| void connect_request(llcptr lp) |
| { |
| if (lp->state == ADM) |
| { |
| lp->state = SETUP; |
| llc_interpret_pseudo_code(lp, ADM1, NULL, NO_FRAME); |
| if(lp->llc_callbacks) |
| { |
| lp->llc_event(lp); |
| lp->llc_callbacks=0; |
| } |
| /* |
| * lp may be invalid after the callback |
| */ |
| } |
| } |
| |
| |
| /* |
| * Interpret_pseudo_code() executes the actions in the connection component |
| * state transition table. Table 4 in document on p88. |
| * |
| * If this function is called to handle an incoming pdu, skb will point |
| * to the buffer with the pdu and type will contain the decoded pdu type. |
| * |
| * If called by data_request skb points to an skb that was skb_alloc-ed by |
| * the llc client to hold the information unit to be transmitted, there is |
| * no valid type in this case. |
| * |
| * If called because a timer expired no skb is passed, and there is no |
| * type. |
| */ |
| |
| void llc_interpret_pseudo_code(llcptr lp, int pc_label, struct sk_buff *skb, |
| char type) |
| { |
| short int pc; /* program counter in pseudo code array */ |
| char p_flag_received; |
| frameptr fr; |
| int resend_count; /* number of pdus resend by llc_resend_ipdu() */ |
| int ack_count; /* number of pdus acknowledged */ |
| struct sk_buff *skb2; |
| |
| if (skb != NULL) |
| { |
| fr = (frameptr) skb->data; |
| } |
| else |
| fr = NULL; |
| |
| pc = pseudo_code_idx[pc_label]; |
| while(pseudo_code[pc]) |
| { |
| switch(pseudo_code[pc]) |
| { |
| case IF_F=1_CLEAR_REMOTE_BUSY: |
| if ((type != I_CMD) || (fr->i_hdr.i_pflag == 0)) |
| break; |
| case CLEAR_REMOTE_BUSY: |
| lp->remote_busy = 0; |
| llc_stop_timer(lp, BUSY_TIMER); |
| if ((lp->state == NORMAL) || |
| (lp->state == REJECT) || |
| (lp->state == BUSY)) |
| { |
| skb2 = llc_pull_from_atq(lp); |
| if (skb2 != NULL) |
| llc_start_timer(lp, ACK_TIMER); |
| while (skb2 != NULL) |
| { |
| llc_sendipdu( lp, I_CMD, 0, skb2); |
| skb2 = llc_pull_from_atq(lp); |
| } |
| } |
| break; |
| case CONNECT_INDICATION: |
| lp->state = NORMAL; /* needed to eliminate connect_response() */ |
| lp->llc_mode = MODE_ABM; |
| lp->llc_callbacks|=LLC_CONN_INDICATION; |
| break; |
| case CONNECT_CONFIRM: |
| lp->llc_mode = MODE_ABM; |
| lp->llc_callbacks|=LLC_CONN_CONFIRM; |
| break; |
| case DATA_INDICATION: |
| skb_pull(skb, 4); |
| lp->inc_skb=skb; |
| lp->llc_callbacks|=LLC_DATA_INDIC; |
| break; |
| case DISCONNECT_INDICATION: |
| lp->llc_mode = MODE_ADM; |
| lp->llc_callbacks|=LLC_DISC_INDICATION; |
| break; |
| case RESET_INDICATION(LOCAL): |
| lp->llc_callbacks|=LLC_RESET_INDIC_LOC; |
| break; |
| case RESET_INDICATION(REMOTE): |
| lp->llc_callbacks|=LLC_RESET_INDIC_REM; |
| break; |
| case RESET_CONFIRM: |
| lp->llc_callbacks|=LLC_RST_CONFIRM; |
| break; |
| case REPORT_STATUS(FRMR_RECEIVED): |
| lp->llc_callbacks|=LLC_FRMR_RECV; |
| break; |
| case REPORT_STATUS(FRMR_SENT): |
| lp->llc_callbacks|=LLC_FRMR_SENT; |
| break; |
| case REPORT_STATUS(REMOTE_BUSY): |
| lp->llc_callbacks|=LLC_REMOTE_BUSY; |
| break; |
| case REPORT_STATUS(REMOTE_NOT_BUSY): |
| lp->llc_callbacks|=LLC_REMOTE_NOTBUSY; |
| break; |
| case SEND_DISC_CMD(P=X): |
| llc_sendpdu(lp, DISC_CMD, lp->f_flag, 0, NULL); |
| break; |
| case SEND_DM_RSP(F=X): |
| llc_sendpdu(lp, DM_RSP, 0, 0, NULL); |
| break; |
| case SEND_FRMR_RSP(F=X): |
| lp->frmr_info_fld.cntl1 = fr->pdu_cntl.byte1; |
| lp->frmr_info_fld.cntl2 = fr->pdu_cntl.byte2; |
| lp->frmr_info_fld.vs = lp->vs; |
| lp->frmr_info_fld.vr_cr = lp->vr; |
| llc_sendpdu(lp, FRMR_RSP, 0, 5, (char *) &lp->frmr_info_fld); |
| break; |
| case RE-SEND_FRMR_RSP(F=0): |
| llc_sendpdu(lp, FRMR_RSP, 0, 5, (char *) &lp->frmr_info_fld); |
| break; |
| case RE-SEND_FRMR_RSP(F=P): |
| llc_sendpdu(lp, FRMR_RSP, lp->p_flag, |
| 5, (char *) &lp->frmr_info_fld); |
| break; |
| case SEND_I_CMD(P=1): |
| llc_sendipdu(lp, I_CMD, 1, skb); |
| break; |
| case RE-SEND_I_CMD(P=1): |
| resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 1); |
| break; |
| case RE-SEND_I_CMD(P=1)_OR_SEND_RR: |
| resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 1); |
| if (resend_count == 0) |
| { |
| llc_sendpdu(lp, RR_CMD, 1, 0, NULL); |
| } |
| break; |
| case SEND_I_XXX(X=0): |
| llc_sendipdu(lp, I_CMD, 0, skb); |
| break; |
| case RE-SEND_I_XXX(X=0): |
| resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 0); |
| break; |
| case RE-SEND_I_XXX(X=0)_OR_SEND_RR: |
| resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 0); |
| if (resend_count == 0) |
| { |
| llc_sendpdu(lp, RR_CMD, 0, 0, NULL); |
| } |
| break; |
| case RE-SEND_I_RSP(F=1): |
| resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_RSP, 1); |
| break; |
| case SEND_REJ_CMD(P=1): |
| llc_sendpdu(lp, REJ_CMD, 1, 0, NULL); |
| break; |
| case SEND_REJ_RSP(F=1): |
| llc_sendpdu(lp, REJ_RSP, 1, 0, NULL); |
| break; |
| case SEND_REJ_XXX(X=0): |
| if (IS_RSP(fr)) |
| llc_sendpdu(lp, REJ_CMD, 0, 0, NULL); |
| else |
| llc_sendpdu(lp, REJ_RSP, 0, 0, NULL); |
| break; |
| case SEND_RNR_CMD(F=1): |
| llc_sendpdu(lp, RNR_CMD, 1, 0, NULL); |
| break; |
| case SEND_RNR_RSP(F=1): |
| llc_sendpdu(lp, RNR_RSP, 1, 0, NULL); |
| break; |
| case SEND_RNR_XXX(X=0): |
| if (IS_RSP(fr)) |
| llc_sendpdu(lp, RNR_CMD, 0, 0, NULL); |
| else |
| llc_sendpdu(lp, RNR_RSP, 0, 0, NULL); |
| break; |
| case SET_REMOTE_BUSY: |
| if (lp->remote_busy == 0) |
| { |
| lp->remote_busy = 1; |
| llc_start_timer(lp, BUSY_TIMER); |
| lp->llc_callbacks|=LLC_REMOTE_BUSY; |
| } |
| else if (lp->timer_state[BUSY_TIMER] == TIMER_IDLE) |
| { |
| llc_start_timer(lp, BUSY_TIMER); |
| } |
| break; |
| case OPTIONAL_SEND_RNR_XXX(X=0): |
| if (IS_RSP(fr)) |
| llc_sendpdu(lp, RNR_CMD, 0, 0, NULL); |
| else |
| llc_sendpdu(lp, RNR_RSP, 0, 0, NULL); |
| break; |
| case SEND_RR_CMD(P=1): |
| llc_sendpdu(lp, RR_CMD, 1, 0, NULL); |
| break; |
| case SEND_ACKNOWLEDGE_CMD(P=1): |
| llc_sendpdu(lp, RR_CMD, 1, 0, NULL); |
| break; |
| case SEND_RR_RSP(F=1): |
| llc_sendpdu(lp, RR_RSP, 1, 0, NULL); |
| break; |
| case SEND_ACKNOWLEDGE_RSP(F=1): |
| llc_sendpdu(lp, RR_RSP, 1, 0, NULL); |
| break; |
| case SEND_RR_XXX(X=0): |
| llc_sendpdu(lp, RR_RSP, 0, 0, NULL); |
| break; |
| case SEND_ACKNOWLEDGE_XXX(X=0): |
| if (IS_RSP(fr)) |
| llc_sendpdu(lp, RR_CMD, 0, 0, NULL); |
| else |
| llc_sendpdu(lp, RR_RSP, 0, 0, NULL); |
| break; |
| case SEND_SABME_CMD(P=X): |
| llc_sendpdu(lp, SABME_CMD, 0, 0, NULL); |
| lp->f_flag = 0; |
| break; |
| case SEND_UA_RSP(F=X): |
| llc_sendpdu(lp, UA_RSP, lp->f_flag, 0, NULL); |
| break; |
| case S_FLAG:=0: |
| lp->s_flag = 0; |
| break; |
| case S_FLAG:=1: |
| lp->s_flag = 1; |
| break; |
| case START_P_TIMER: |
| if(lp->timer_state[P_TIMER] == TIMER_RUNNING) |
| llc_stop_timer(lp, P_TIMER); |
| llc_start_timer(lp, P_TIMER); |
| if (lp->p_flag == 0) |
| { |
| lp->retry_count = 0; |
| lp->p_flag = 1; |
| } |
| break; |
| case START_ACK_TIMER_IF_NOT_RUNNING: |
| if (lp->timer_state[ACK_TIMER] == TIMER_IDLE) |
| llc_start_timer(lp, ACK_TIMER); |
| break; |
| case START_ACK_TIMER: |
| llc_start_timer(lp, ACK_TIMER); |
| break; |
| case START_REJ_TIMER: |
| llc_start_timer(lp, REJ_TIMER); |
| break; |
| case STOP_ACK_TIMER: |
| llc_stop_timer(lp, ACK_TIMER); |
| break; |
| case STOP_P_TIMER: |
| llc_stop_timer(lp, ACK_TIMER); |
| lp->p_flag = 0; |
| break; |
| case IF_DATA_FLAG=2_STOP_REJ_TIMER: |
| if (lp->data_flag == 2) |
| llc_stop_timer(lp, REJ_TIMER); |
| break; |
| case STOP_REJ_TIMER: |
| llc_stop_timer(lp, REJ_TIMER); |
| break; |
| case STOP_ALL_TIMERS: |
| llc_stop_timer(lp, ACK_TIMER); |
| llc_stop_timer(lp, P_TIMER); |
| llc_stop_timer(lp, REJ_TIMER); |
| llc_stop_timer(lp, BUSY_TIMER); |
| break; |
| case STOP_OTHER_TIMERS: |
| llc_stop_timer(lp, P_TIMER); |
| llc_stop_timer(lp, REJ_TIMER); |
| llc_stop_timer(lp, BUSY_TIMER); |
| break; |
| case UPDATE_N(R)_RECEIVED: |
| ack_count = llc_free_acknowledged_skbs(lp, |
| (unsigned char) fr->s_hdr.nr); |
| if (ack_count > 0) |
| { |
| lp->retry_count = 0; |
| llc_stop_timer(lp, ACK_TIMER); |
| if (skb_peek(&lp->rtq) != NULL) |
| { |
| /* |
| * Re-transmit queue not empty |
| */ |
| llc_start_timer(lp, ACK_TIMER); |
| } |
| } |
| break; |
| case UPDATE_P_FLAG: |
| if (IS_UFRAME(fr)) |
| p_flag_received = fr->u_hdr.u_pflag; |
| else |
| p_flag_received = fr->i_hdr.i_pflag; |
| if ((fr->pdu_hdr.ssap & 0x01) && (p_flag_received)) |
| { |
| lp->p_flag = 0; |
| llc_stop_timer(lp, P_TIMER); |
| } |
| break; |
| case DATA_FLAG:=2: |
| lp->data_flag = 2; |
| break; |
| case DATA_FLAG:=0: |
| lp->data_flag = 0; |
| break; |
| case DATA_FLAG:=1: |
| lp->data_flag = 1; |
| break; |
| case IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1: |
| if (lp->data_flag == 0) |
| lp->data_flag = 1; |
| break; |
| case P_FLAG:=0: |
| lp->p_flag = 0; |
| break; |
| case P_FLAG:=P: |
| lp->p_flag = lp->f_flag; |
| break; |
| case REMOTE_BUSY:=0: |
| lp->remote_busy = 0; |
| break; |
| case RETRY_COUNT:=0: |
| lp->retry_count = 0; |
| break; |
| case RETRY_COUNT:=RETRY_COUNT+1: |
| lp->retry_count++; |
| break; |
| case V(R):=0: |
| lp->vr = 0; |
| break; |
| case V(R):=V(R)+1: |
| lp->vr++; |
| break; |
| case V(S):=0: |
| lp->vs = 0; |
| break; |
| case V(S):=N(R): |
| lp->vs = fr->i_hdr.nr; |
| break; |
| case F_FLAG:=P: |
| if (IS_UFRAME(fr)) |
| lp->f_flag = fr->u_hdr.u_pflag; |
| else |
| lp->f_flag = fr->i_hdr.i_pflag; |
| break; |
| default: |
| } |
| pc++; |
| } |
| } |
| |
| |
| /* |
| * Process_otype2_frame will handle incoming frames |
| * for 802.2 Type 2 Procedure. |
| */ |
| |
| void llc_process_otype2_frame(llcptr lp, struct sk_buff *skb, char type) |
| { |
| int idx; /* index in transition table */ |
| int pc_label; /* action to perform, from tr tbl */ |
| int validation; /* result of validate_seq_nos */ |
| int p_flag_received; /* p_flag in received frame */ |
| frameptr fr; |
| |
| fr = (frameptr) skb->data; |
| |
| if (IS_UFRAME(fr)) |
| p_flag_received = fr->u_hdr.u_pflag; |
| else |
| p_flag_received = fr->i_hdr.i_pflag; |
| |
| switch(lp->state) |
| { |
| /* Compute index in transition table: */ |
| case ADM: |
| idx = type; |
| idx = (idx << 1) + p_flag_received; |
| break; |
| case CONN: |
| case RESET_WAIT: |
| case RESET_CHECK: |
| case ERROR: |
| idx = type; |
| break; |
| case SETUP: |
| case RESET: |
| case D_CONN: |
| idx = type; |
| idx = (idx << 1) + lp->p_flag; |
| break; |
| case NORMAL: |
| case BUSY: |
| case REJECT: |
| case AWAIT: |
| case AWAIT_BUSY: |
| case AWAIT_REJECT: |
| validation = llc_validate_seq_nos(lp, fr); |
| if (validation > 3) |
| type = BAD_FRAME; |
| idx = type; |
| idx = (idx << 1); |
| if (validation & 1) |
| idx = idx +1; |
| idx = (idx << 1) + p_flag_received; |
| idx = (idx << 1) + lp->p_flag; |
| default: |
| printk("llc_proc: bad state\n"); |
| return; |
| } |
| idx = (idx << 1) + pdutr_offset[lp->state]; |
| lp->state = pdutr_entry[idx +1]; |
| pc_label = pdutr_entry[idx]; |
| if (pc_label != NOP) |
| { |
| llc_interpret_pseudo_code(lp, pc_label, skb, type); |
| if(lp->llc_callbacks) |
| { |
| lp->llc_event(lp); |
| lp->llc_callbacks=0; |
| } |
| /* |
| * lp may no longer be valid after this point. Be |
| * careful what is added! |
| */ |
| } |
| } |
| |
| |
| void llc_timer_expired(llcptr lp, int t) |
| { |
| int idx; /* index in transition table */ |
| int pc_label; /* action to perform, from tr tbl */ |
| |
| lp->timer_state[t] = TIMER_EXPIRED; |
| idx = lp->state; /* Compute index in transition table: */ |
| idx = (idx << 2) + t; |
| idx = idx << 1; |
| if (lp->retry_count >= lp->n2) |
| idx = idx + 1; |
| idx = (idx << 1) + lp->s_flag; |
| idx = (idx << 1) + lp->p_flag; |
| idx = idx << 1; /* 2 bytes per entry: action & newstate */ |
| |
| pc_label = timertr_entry[idx]; |
| if (pc_label != NOP) |
| { |
| llc_interpret_pseudo_code(lp, pc_label, NULL, NO_FRAME); |
| lp->state = timertr_entry[idx +1]; |
| } |
| lp->timer_state[t] = TIMER_IDLE; |
| if(lp->llc_callbacks) |
| { |
| lp->llc_event(lp); |
| lp->llc_callbacks=0; |
| } |
| /* |
| * And lp may have vanished in the event callback |
| */ |
| } |
| |