/*
 * (llc_sap.c)- driver routines for SAP component.
 *
 * Copyright (c) 1997 by Procom Technology,Inc.
 *
 * This program can be redistributed or modified under the terms of the 
 * GNU General Public License as published by the Free Software Foundation.
 * This program is distributed without any warranty or implied warranty
 * of merchantability or fitness for a particular purpose.
 *
 * See the GNU General Public License for more details.
 *
 */
 

#define LLC_SAP_C


#include <asm/byteorder.h>
#include <linux/netdevice.h>
#include <linux/malloc.h>
#include <net/cm_types.h>
#include <net/cm_mm.h>
#include <net/cm_dll.h>
#include <net/cm_frame.h>
#include <net/llc_if.h>
#include <net/llc_sap.h>
#include <net/llc_s_ev.h>
#include <net/llc_s_ac.h>
#include <net/llc_s_st.h>
#include <net/llc_conn.h>
#include <net/llc_main.h>
#include <net/llc_mac.h>
#include <net/llc_pdu.h>
#include <net/llc_glob.h>
#include <net/lan_hdrs.h>
#include <net/llc_dbg.h>

#ifdef LLC_SAP_DBG
  #define  DBG_MSG(body) { printk body; }
#else
  #define  DBG_MSG(body)  ;
#endif

static us16    sap_rtn_event (sap_t * sap, sap_state_event_t * event);
static us16    sap_next_state (sap_t * sap, sap_state_event_t * event);
static us16    execute_sap_transition_actions (sap_t * sap,
                               sap_state_transition_t * transition,
                                                sap_state_event_t * event);
static us16    sap_find_conn_match (void * this, void * match_value);
static sap_state_transition_t *
               find_sap_transition (sap_t * sap, sap_state_event_t * event);



/*
 * Function : sap_assign_conn 
 * 
 * Description : 
 *  this function adds a connection to connection_list of a SAP.
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  connection_t *connection : pointer to connection.
 * 
 * Returns : 
 *  0 : succes.
 *  1 : failure.
 */
us16 
sap_assign_conn (sap_t * sap, void * connection)
{
	((connection_t *)connection)->parent_sap = (us32)sap;
	return (dll_add (&sap->connection_list, connection, DLL_WHERE_TAIL));
}

/*
 * Function : sap_unassign_conn 
 * 
 * Description : 
 *  this function removes a connection from connection_list of a SAP.
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  connection_t *connection : pointer to connection.
 * 
 * Returns : 
 *  0 : succes.
 *  1 : failure.
 */

us16 
sap_unassign_conn (sap_t * sap, void * connection)
{
	return (dll_remove_this (&sap->connection_list, connection));
}

/*
 * Function : sap_get_event 
 * 
 * Description : 
 *  this function removes an event from event pool of SAP and returnes it.
 *  each SAP has 5 events and use them during execution.
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  void **event : allocated event (output argument).
 * 
 * Returns : 
 *  0 : succes.
 *  1 : failure.
 */

us16 
sap_get_event (sap_t * sap, void ** event)
{
	us16 rc;
	unsigned long flags;

	save_flags(flags);
	cli();
	rc = dll_remove (&sap->avail_events, event, DLL_WHERE_HEAD);
        if (!rc) {
		restore_flags(flags);
		((sap_state_event_t *)*event)->ind_cfm_flag = NO;
		((sap_state_event_t *)*event)->prim = NULL;
	} else {
		restore_flags(flags);
	}
	
	return rc;
}

/*
 * Function : sap_send_event 
 * 
 * Description : 
 *  this function sends an event to SAP state machine. after executing actions
 *  of the event, upper layer will be indicated if needed(on recieving an
 *  UI frame).
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  void *event : pointer to occured event.
 * 
 * Returns : 
 *  Always 0. 
 */

us16 
sap_send_event (sap_t * sap, void * event)
{
	prim_if_block_t *prim;
	us8 flag;

	sap_next_state (sap,event);
	flag = ((sap_state_event_t *)event)->ind_cfm_flag;
	prim = ((sap_state_event_t *)event)->prim;
	sap_rtn_event (sap, event);
	if (flag == INDICATE){
		sap->indicate(prim);      
	} 
	return (0);
}

/*
 * Function : sap_rtn_pdu 
 * 
 * Description : 
 *  This function informs upper layer on recieving an UI,XID or TEST pdu.
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  frame_t *pdu_frame : recieved pdu.
 *  void *event : pointer to occured event.
 * 
 * Returns : 
 *  Always 0. 
 */

us16 
sap_rtn_pdu (sap_t * sap, frame_t * pdu_frame, void *event)
{   
	pdu_un_t *pdu;
	prim_if_block_t *prim = &Ind_prim;
	prim_data_u *prim_data = Ind_prim.data;
	us8 lfb;

	pdu_decode_sa (pdu_frame, prim_data->udata.source_addr.mac);
	pdu_decode_da (pdu_frame, prim_data->udata.dest_addr.mac);
	pdu_decode_dsap (pdu_frame, &prim_data->udata.dest_addr.lsap);
	pdu_decode_ssap (pdu_frame, &prim_data->udata.source_addr.lsap);
	prim_data->udata.priority = 0;
	prim_data->udata.unit = (us8 *) pdu_frame;
	pdu = (pdu_un_t *) pdu_frame->llc_hdr;
	switch (LLC_U_PDU_RSP(pdu)) {
		case LLC_1_PDU_CMD_TEST:
			prim->primitive = TEST_PRIM;
			break;
		case LLC_1_PDU_CMD_XID:
			prim->primitive = XID_PRIM;
			break;
		case LLC_1_PDU_CMD_UI:
			if ( pdu_frame->mac_type == ETH_P_TR_802_2 ){
				if (((token_ring_mac_hdr_t *)pdu_frame->mac_hdr)->rcf != 0) {
					lfb = ntohs(((token_ring_mac_hdr_t *)
						pdu_frame->mac_hdr)->rcf) & 0x0070;
					prim_data->udata.lfb = lfb >> 4;
				}
				else{
					lfb = 0xFF;
					prim_data->udata.lfb = 0xFF;
				}
#ifdef TR_DBG
				printk("\n unit data indication : lfb=0x%x\n",lfb);
#endif
			}
			prim->primitive = DATAUNIT_PRIM;
			break;
	}
	prim->data = prim_data;
	prim->sap = (us32) sap;
	((sap_state_event_t *)event)->ind_cfm_flag = INDICATE;
	((sap_state_event_t *)event)->prim = prim;

	return (0);
}

/*
 * Function : sap_send_pdu 
 * 
 * Description : 
 *  This function sends a frame to MAC layer for transmition. 
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  frame_t *pdu_frame : pdu that must be sent.
 * 
 * Returns : 
 *  Always 0. 
 */
us16 
sap_send_pdu (sap_t * sap, frame_t * pdu)
{
	mac_send_pdu (pdu);
	return (0);
}

/*
 * Function : sap_find_conn 
 * 
 * Description : 
 *  This function searches connection list of a SAP and finds a connection
 *  by given address. if can't find connection, allocates new connection
 *  and adds it to connection_list of SAP. 
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  address_t *remote_addr : MAC address of remote machine (base of search).
 *  void **conn_ptr : pointer to found connection (output argument).
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure. 
 */
us16 
sap_find_conn (sap_t * sap, address_t * remote_addr, void ** conn_ptr)
{
	us16 rc;


	/* search for the corresponding active connection */
	rc = dll_match (&sap->connection_list, sap_find_conn_match,
                               (void *) remote_addr, (void **) conn_ptr,
                                         (DLL_MATCH_PEEK | DLL_MATCH_ONE));
	if (rc) {
       /*
        * didn't find an active connection; get an inactive connection
        * to use; associate it with this SAP
        */
		rc = llc_connection_get ((connection_t **) conn_ptr);
		if (!rc) {
			rc = sap_assign_conn (sap, *conn_ptr);
			if (!rc) {
				memcpy (&(((connection_t *) *conn_ptr)->remote_dl_addr),
                                           remote_addr, sizeof (address_t));
			}
		}
	}
	return (rc);
}

/*
 * Function : sap_rtn_event 
 * 
 * Description : 
 *  This function returnes event to SAP event pool. 
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  sap_state_event_t *event : released event.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure. 
 */

static us16 
sap_rtn_event (sap_t * sap, sap_state_event_t * event)
{
	pdu_un_t *pdu;
	us16 rc;
	unsigned long flags;

	if ( event->type == SAP_EV_TYPE_PDU ){
		pdu = (pdu_un_t *)((frame_t *) event->data.pdu.frame)->llc_hdr;
		if ( LLC_U_PDU_CMD(pdu) != LLC_1_PDU_CMD_UI )
			frame_skb_free((frame_t *)event->data.pdu.frame);
	}
	save_flags(flags);
	cli();
	rc = dll_add (&sap->avail_events, event, DLL_WHERE_TAIL);
	restore_flags(flags);

	return (rc);
}

/*
 * Function : sap_next_state 
 * 
 * Description : 
 *  This function finds transition that matches with happend event, then
 *  executes related actions and finally changes state of SAP.  
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  sap_state_event_t *event : happend event.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure. 
 */

static us16 
sap_next_state (sap_t * sap, sap_state_event_t * event)
{
	us16 rc = 1;
	sap_state_transition_t *transition;

	if (sap->state <= NBR_SAP_STATES) {
		transition = find_sap_transition (sap, event);
		if (transition) {
	           /*
	            * got the state to which we next transition; perform the
	            * actions associated with this transition before actually
	            * transitioning to the next state
	            */
			rc = execute_sap_transition_actions (sap, transition,
									 event);
			if (!rc) {
               			/*
               			 * transition SAP to next state if all actions
               			 * execute successfully; 
               			 */
				sap->state = transition->next_state;
			}
		}
	}
	return(rc);     
}

/*
 * Function : find_sap_transition 
 * 
 * Description : 
 *  This function finds transition that matches with happend event.
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  sap_state_event_t *event : happend event.
 * 
 * Returns : 
 *  pointer to found transition : success.
 *  NULL : failure.
 *   
 */

static sap_state_transition_t * 
find_sap_transition (sap_t * sap, sap_state_event_t * event)
{
	us16 i;
	sap_state_t *curr_state;
	sap_state_transition_t **next_transition;
	unsigned long flags;

	save_flags(flags);
	cli();
	curr_state = &Sap_state_table [sap->state - 1];
	restore_flags(flags);
	   /*
	    * search thru events for this state until list exhausted 
	    * or until its obvious the event is not valid for the current state
	    */
	for (i = 0, next_transition = curr_state->transitions;
                                           next_transition [i]->event; i++) {
		if (!next_transition [i]->event (sap, event)) {
			/* got event match; return it */
			return (next_transition [i]);
		}
	}

	return ((sap_state_transition_t *) NULL);
}


/*
 * Function : execute_sap_transition_actions 
 * 
 * Description : 
 *  This function executes actions that is related to happend event. 
 * 
 * Parameters :
 *  sap_t *sap : pointer to SAP.
 *  sap_state_transition_t *transition : pointer to transition that it's
 *  ations must be performed. 
 *  sap_state_event_t *event : happend event.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure of at least one action.
 *   
 */
static us16 
execute_sap_transition_actions (sap_t * sap, 
		sap_state_transition_t * transition, sap_state_event_t * event)
{
	us16 rc = 0;       
	sap_action_t *next_action;

	for (next_action = transition->event_actions; next_action &&
						 *next_action; next_action++) {
		if ((*next_action) (sap, event)) {
			rc = 1;
		}
	}

	return (rc);
}


/*
 * Function : find_find_conn_match 
 * 
 * Description : 
 *  This function is a match function that is used by dll_match at start of 
 *  sap_find_conn. 
 */
static us16 
sap_find_conn_match (void * this, void * match_value)
{
	us16 rc = 1;
	connection_t *conn = (connection_t *) this;
	address_t *remote_addr = (address_t *) match_value;

	if (!memcmp (conn->remote_dl_addr.mac, remote_addr->mac, MAC_ADDR_LEN)) {
		rc = 0;
	}
	return (rc);
}
