/*
 * (llc_if.c) - Defines LLC interface to upper layer 
 *
 * 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.
 *
 */
 
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <asm/errno.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_conn.h>
#include <net/llc_c_ev.h>
#include <net/llc_c_ac.h>
#include <net/llc_c_st.h>
#include <net/llc_main.h>
#include <net/llc_mac.h>
#include <net/llc_glob.h>
#include <net/llc_rout.h>
#include <net/llc_dbg.h>

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

static int        sap_request (prim_if_block_t * prim);
static int        unitdata_req_handler (prim_if_block_t * prim);
static int        test_req_handler (prim_if_block_t * prim);
static int        xid_req_handler (prim_if_block_t * prim);
static int        data_req_handler (prim_if_block_t * prim);
static int        connect_req_handler (prim_if_block_t * prim);
static int        disconnect_req_handler (prim_if_block_t * prim);
static int        reset_req_handler (prim_if_block_t * prim);
static int        flowcontrol_req_handler (prim_if_block_t * prim);
static int        sap_response (prim_if_block_t * prim);
static int        connect_rsp_handler (prim_if_block_t * prim);
static int        reset_rsp_handler (prim_if_block_t * prim);
static int        no_rsp_handler (prim_if_block_t * prim);
       us16 	  process_rxframes_events(connection_t *conn);

extern void P8022_register_sap(unsigned char sap,
   int (* rcvfunc)(struct sk_buff *, struct device *, struct packet_type *));
extern void P8022_unregister_sap(unsigned char sap);

		/* table of request handler functions */

primitive_call_t       Request_primitives [NBR_PRIMITIVES] =
{
   unitdata_req_handler,           /* order of functions must not change */
   connect_req_handler,
   data_req_handler,
   disconnect_req_handler,
   reset_req_handler,
   flowcontrol_req_handler,
   NULLPTR,
   xid_req_handler,
   test_req_handler
};


		/* table of response handler functions */

primitive_call_t       Response_primitives [NBR_PRIMITIVES] =
{
   no_rsp_handler,                  /* order of functions must not change */
   connect_rsp_handler,
   no_rsp_handler,
   no_rsp_handler,
   reset_rsp_handler,
   no_rsp_handler
};



/*
 * Function : inc_conn_busy 
 * 
 * Description : 
 *  this function increments busy field of connection. this is a mechanism for
 *  locking connection structure and protecting this from interrupt side effects. 
 * 
 * Parameters :
 *  connection_t *conn : pointer to connection that must be locked. 
 * 
 * Returns : 
 *  none.
 */


inline void 
inc_conn_busy(connection_t *conn) 
{
	conn->busy ++;
	barrier();
}

/*
 * Function : dec_conn_busy 
 * 
 * Description : 
 *  this function decrements busy field of connection. this is a mechanism for
 *  unlocking connection structure. 
 * 
 * Parameters :
 *  connection_t *conn : pointer to connection that must be unlocked. 
 * 
 * Returns : 
 *  none.
 */
inline void 
dec_conn_busy(connection_t *conn) 
{
	barrier();
	conn->busy --;
}

/*
 * Function : llc_sap_open  
 * 
 * Description : 
 *  this function is a interface funtion to upper layer. each one who wants to
 *  get a SAP (for example NetBEUI) should call this function.
 * 
 * Parameters :
 *   primitive_call_t nw_indicate : pointer to indicate function of upper
 *   layer.
 *   primitive_call_t nw_confirm : pointer to confirm function of upper
 *   layer.
 *   us8 local_sap : SAP number.
 *   us32 *sap : pointer to allocated SAP (output argument).
 * 
 * Returns : 
 *   0 : success.
 *   1 : failure.
 */

us16 
llc_sap_open (primitive_call_t nw_indicate, primitive_call_t nw_confirm,
                                                us8 local_sap, us32 * sap)
{
	us16 rc;
	sap_t *this_sap;

	if (llc_check_init() == NO) {
		return (1);
	}

	/* verify this SAP is not already open; if so, return error */
	rc = llc_sap_find (local_sap, &this_sap);
	if (rc) {
		/* sap requested does not yet exist */
		rc = llc_sap_get (&this_sap);
		if (!rc) {
			/* allocated a SAP; initialize it and 
						clear out its memory pool */

			this_sap->local_dl_addr.lsap = local_sap;
			this_sap->request = sap_request;
			this_sap->response = sap_response;
			this_sap->indicate = nw_indicate;
			this_sap->confirm = nw_confirm;
			station_get((station_t **)&this_sap->parent_station);
			mm_clr_pool (this_sap->mp_handle, YES);

			/* initialized SAP; add it to list of SAPs this station manages */
			rc = llc_sap_save (this_sap);
			if (!rc) {
				*sap = (us32) this_sap;     

				      /* introducing SAP to kernel */
				P8022_register_sap(local_sap,mac_indicate);
			}
		} else{
			FDBG_ERR_MSG(("\n too many saps,new sap is unavailable\n"));
			rc = 1;
		}
	} else {
		FDBG_ERR_MSG(("\n SAP already has opened...\n"));
		rc = 1;                 /* SAP already exists */
	}
       
	return (rc);
}

/*
 * Function : llc_sap_close  
 * 
 * Description : 
 *  this function is a interface funtion to upper layer. each one who wants to
 *  close an open SAP (for example NetBEUI) should call this function.
 * 
 * Parameters :
 *   us32 sap_ptr : pointer to free SAP.
 * 
 * Returns : 
 *   0 : success.
 *   1 : failure.
 */

us16 
llc_sap_close (us32 sap_ptr)
{
	us16 rc;
	sap_t *sap = (sap_t *) sap_ptr;

	if (llc_check_init() == NO) {
		return (1);
	}

	rc = llc_rtn_sap (sap);
	P8022_unregister_sap(sap->local_dl_addr.lsap);
	return (rc);
}

/*
 * Function : sap_request  
 * 
 * Description : 
 *  this function is a interface funtion to upper layer. each one who wants to
 *  request a service from LLC, must call this function. details of requested
 *  service is defined in input argument(prim).
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */

static int 
sap_request (prim_if_block_t *prim)
{
	int rc = 1;

   /* 
    * receive REQUEST primitive from network layer; call the appropriate
    * primitive handler which then packages it up as an event and sends
    * it to the SAP or CONNECTION event handler
    */
	if (prim->primitive < NBR_PRIMITIVES) {
	       /* valid primitive; call the function to handle it */
		rc = Request_primitives [prim->primitive] (prim);
	}

	return (rc);
}

/*
 * Function : unitdata_req_handler  
 * 
 * Description : 
 *  this function calls when upper layer wants to send data using connection-less
 *  mode communication (UI pdu).
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */

static int 
unitdata_req_handler (prim_if_block_t *prim)
{
	us16 rc;
	sap_t *sap;
	sap_state_event_t *event;

   /* 
    * accept data frame from network layer to be sent using connection-
    * less mode communication; timeout/retries handled by network layer;
    * package primitive as an event and send to SAP event handler
    */
	rc = llc_sap_find (prim->data->udata.source_addr.lsap, &sap);
	if (!rc) {
		rc = sap_get_event (sap, (void **) &event);
		if (!rc) {
			event->type = SAP_EV_TYPE_PRIM;
			event->data.prim.prim = DATAUNIT_PRIM;
			event->data.prim.type = PRIM_TYPE_REQUEST;
			event->data.prim.data = (void *) prim;
	
			rc = sap_send_event (sap, (void *) event);
		}
	}

	return (rc);
}


/*
 * Function : test_req_handler  
 * 
 * Description : 
 *  this function calls when upper layer wants to send a TEST pdu.
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */



static int
test_req_handler (prim_if_block_t * prim)
{
	us16 rc;
	sap_t *sap;
	sap_state_event_t *event;

   /*
    * package primitive as an event and send to SAP event handler
    */
	rc = llc_sap_find (prim->data->udata.source_addr.lsap, &sap);
	if (!rc) {
		rc = sap_get_event (sap, (void **) &event);
		if (!rc) {
			event->type = SAP_EV_TYPE_PRIM;
			event->data.prim.prim = TEST_PRIM;
			event->data.prim.type = PRIM_TYPE_REQUEST;
			event->data.prim.data = (void *) prim;

			rc = sap_send_event (sap, (void *) event);
		}
	}

	return (rc);
}

/*
 * Function : xid_req_handler  
 * 
 * Description : 
 *  this function calls when upper layer wants to send a XID pdu.
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */
static int 
xid_req_handler (prim_if_block_t * prim)
{
	us16 rc;
	sap_t *sap;
	sap_state_event_t *event;

   /* 
    * package primitive as an event and send to SAP event handler
    */
	rc = llc_sap_find (prim->data->udata.source_addr.lsap, &sap);
	if (!rc) {
		rc = sap_get_event (sap, (void **) &event);
		if (!rc) {
			event->type = SAP_EV_TYPE_PRIM;
			event->data.prim.prim = XID_PRIM;
			event->data.prim.type = PRIM_TYPE_REQUEST;
			event->data.prim.data = (void *) prim;
	
			rc = sap_send_event (sap, (void *) event);
		}
	}

	return (rc);
}

/*
 * Function : data_req_handler  
 * 
 * Description : 
 *  this function calls when upper layer wants to send data using connection-
 *  oriented communication mode. during sendig data, connection will be locked
 *  and recieved frames and expired timers will be queued in log_q filed of
 *  connection. they will processed after sending data at end of this function
 *  by calling process_rxframes_events function.
 *  after sending data. 
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : internal failure.
 *  -ECONNABORTED : connection already closed.
 *  -EBUSY : sending data is not permitted in this state or LLC has send an I
 *           pdu with p bit set to 1 and is waiting for it's response.
 */
static int 
data_req_handler (prim_if_block_t * prim)
{
	int rc=0;
	connection_t *conn;
	conn_state_event_t *event;

   /* 
    * accept data frame from network layer to be sent using connection
    * mode communication; timeout/retries handled by this layer;
    * package primitive as an event and send to connection event handler
    */
	conn = (connection_t *) prim->data->data.connect_handle;
	inc_conn_busy(conn);   /* locking connection */ 
	conn->data_ret_val = 0;  
	if ( conn->state == CONN_STATE_ADM ){
		conn->data_ret_val = -ECONNABORTED;  
	} else if ( data_accept_state(conn->state) != 0 ){
		/* data_conn_refuse */ 
		conn->failed_data_req = YES;  
		conn->data_ret_val = -EBUSY;
	} else if ( conn->p_flag ){
		conn->failed_data_req = YES;
		conn->data_ret_val = -EBUSY;
	} else {
		rc = conn_get_event (conn, (void **) &event);
		if (!rc) {
			event->type = CONN_EV_TYPE_PRIM;
			event->data.prim.prim = DATA_PRIM;
			event->data.prim.type = PRIM_TYPE_REQUEST;
			event->data.prim.data = (void *) prim;
			rc = conn_send_event (conn, (void *) event);
		}
		conn->data_ret_val = rc;
	} 
  
	start_bh_atomic(); 
  
	process_rxframes_events(conn);
  
	rc = conn->data_ret_val;
	conn->data_ret_val = 0;
	dec_conn_busy(conn);  /* opening connection lock */

	save_flags(prim->data->data.flags);
	cli();
  
	end_bh_atomic(); 

	return (rc);
}

/*
 * Function : confirm_impossible  
 * 
 * Description : 
 *  this function informs upper layer about failing in connection establishment.
 *  this function calls by connect_req_handler.
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains confimation 
 *  data.
 * 
 * Returns : 
 *  none
 */
static void 
confirm_impossible(prim_if_block_t *prim)
{
	prim->data->conn.status = IMPOSSIBLE;
	((sap_t *)prim->sap)->confirm(prim);
}

/*
 * Function : connect_req_handler  
 * 
 * Description : 
 *  this function calls when upper layer wants to establish an LLC connection 
 *  with a remote machine. this function packages a proper event and sends it
 *  connection component state machine. 
 *  success or failure of connection establishment will inform to upper layer
 *  via calling it's confirm function and passing proper information. 
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0
 */
static int 
connect_req_handler (prim_if_block_t * prim)
{
	us16 rc;
	sap_t *sap = (sap_t *) prim->sap;
	connection_t *conn;
	conn_state_event_t *event;


   /*
    * network layer supplies addressing required to establish
    * connection; package as an event and send it to the connection
    * event handler
    */
	if ( find_conn(sap,(address_t *)&prim->data->conn.dest_addr,
							(void **)&conn)==0 ){
		FDBG_ERR_MSG((KERN_ERR "conn already in use\n"));
		confirm_impossible(prim); 
		return 0;
	}
	rc = llc_connection_get (&conn);
	if (rc){
		FDBG_ERR_MSG((KERN_ERR "free conn not available\n"));
		confirm_impossible(prim);
		return 0;
	}

            /* assign new connection to it's SAP */

	rc = sap_assign_conn (sap, (void *) conn);
	if (!rc) {
		memcpy (&conn->remote_dl_addr, &prim->data->conn.dest_addr,
                                                       sizeof (address_t));
		memcpy (&conn->local_dl_addr, &prim->data->conn.source_addr,
                                                       sizeof (address_t));
		conn->local_dev = prim->data->conn.device;
		conn->link_no = prim->data->conn.link_no;
		rc = conn_get_event (conn, (void **) &event);
		if (!rc) {
			event->type = CONN_EV_TYPE_PRIM;
			event->data.prim.prim = CONNECT_PRIM;
			event->data.prim.type = PRIM_TYPE_REQUEST;
			event->data.prim.data = (void *) prim;
			rc = conn_send_event (conn, (void *) event);
			if (!rc) {
				prim->data->conn.connect_handle = (us32) conn;
			}
		}

	}		
	if (rc) {
		FDBG_ERR_MSG(("connect_req_handler failed\n"));
		sap_unassign_conn (sap, (void *) conn);
		llc_connection_rtn (conn);
		confirm_impossible(prim);
	}
	prim->data->conn.connect_handle = (us32)conn;
	return (0);
}

/*
 * Function : disconnect_req_handler  
 * 
 * Description : 
 *  this function calls when upper layer wants to close an established LLC
 *  connection with a remote machine. this function packages a proper event and 
 *  sends it to connection component state machine. 
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */

static int 
disconnect_req_handler (prim_if_block_t * prim)
{
	us16 rc;
	connection_t *conn;
	conn_state_event_t *event;

	conn = (connection_t *) prim->data->disc.connect_handle;
	if (conn->state == CONN_STATE_ADM || conn->state == CONN_OUT_OF_SVC ){
		FDBG_ERR_MSG(("\ndisconnect_req_handler: conn is not open"));
		return (1);
	}

   /*
    * postpone unassigning the connection from its SAP and returning the
    * connection until all ACTIONs have been completely executed
    */   

	rc = conn_get_event (conn, (void **) &event);
	if (!rc) {
		event->type = CONN_EV_TYPE_PRIM;
		event->data.prim.prim = DISCONNECT_PRIM;
		event->data.prim.type = PRIM_TYPE_REQUEST;
		event->data.prim.data = (void *) prim;
		rc = conn_send_event (conn, (void *) event);
	}
	return (rc);
}

/*
 * Function : reset_req_handler  
 * 
 * Description : 
 *  this function calls when upper layer wants to reset an established LLC
 *  connection with a remote machine. this function packages a proper event and 
 *  sends it to connection component state machine. 
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */

static int 
reset_req_handler (prim_if_block_t * prim)
{
	us16 rc;
	connection_t *conn;
	conn_state_event_t *event;

	conn = (connection_t *) prim->data->res.connect_handle;
	rc = conn_get_event (conn, (void **) &event);
	if (!rc) {
		event->type = CONN_EV_TYPE_PRIM;
		event->data.prim.prim = RESET_PRIM;
		event->data.prim.type = PRIM_TYPE_REQUEST;
		event->data.prim.data = (void *) prim;

		rc = conn_send_event (conn, (void *) event);
	}
   
	return (rc);
}

/*
   We don't support flow control.
 */

static int 
flowcontrol_req_handler (prim_if_block_t * prim)
{
	us16 rc;
	connection_t *conn;
	conn_state_event_t *event;


   /*
    * network layer supplies connection handle; map it to a connection;
    * package as event and send it to connection event handler
    */
	conn = (connection_t *) prim->data->fc.connect_handle;
	rc = conn_get_event (conn, (void **) &event);
	if (!rc) {
		event->type = CONN_EV_TYPE_PRIM;
		event->data.prim.prim = FLOWCONTROL_PRIM;
		event->data.prim.type = PRIM_TYPE_REQUEST;
		event->data.prim.data = (void *) prim;

		rc = conn_send_event (conn, (void *) event);
	}
   
	return (rc);
}

/*
 * Function : sap_response  
 * 
 * Description : 
 *  this function is a interface funtion to upper layer. each one who wants to
 *  response to an indicate can call this function via calling sap_response with
 *  proper service parameters.
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains service 
 *  parameters.
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */

static int
sap_response (prim_if_block_t * prim)
{
	us16 rc = 1;

   /* 
    * network layer RESPONSE primitive received; package primitive
    * as an event and send it to the connection event handler
    */

	if (prim->primitive < NBR_PRIMITIVES) {
	       /* valid primitive; call the function to handle it */
		rc = Response_primitives [prim->primitive] (prim);
	}

	return (rc);
}

/*
 * Function : connect_rsp_handler  
 * 
 * Description : 
 *  response to connect indication.
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains response info. 
 * 
 * Returns : 
 *  0
 */

static int
connect_rsp_handler (prim_if_block_t * prim)
{
	connection_t *conn;

	conn = (connection_t *) prim->data->conn.connect_handle;
	conn->link_no = prim->data->conn.link_no;

	return 0;
}

/*
 * Function : reset_rsp_handler  
 * 
 * Description : 
 *  response to RESET indication.
 * 
 * Parameters :
 *  prim_if_block_t *prim : pointer to structure that contains response info. 
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */

static int 
reset_rsp_handler (prim_if_block_t *prim)
{
	us16 rc;
	connection_t *conn;
	conn_state_event_t *event;


   /*
    * network layer supplies connection handle; map it to a connection;
    * package as event and send it to connection event handler
    */

	conn = (connection_t *) prim->data->res.connect_handle;
	rc = conn_get_event (conn, (void **) &event);
	if (!rc) {
		event->type = CONN_EV_TYPE_PRIM;
		event->data.prim.prim = RESET_PRIM;
		event->data.prim.type = PRIM_TYPE_RESPONSE;
		event->data.prim.data = (void *) prim;

		rc = conn_send_event (conn, (void *) event);
	}
   
	return (rc);
}



static int 
no_rsp_handler (prim_if_block_t * prim)
{  
	return (0);
}

/*
 * Function : process_rxframes_events  
 * 
 * Description : 
 *  this function processes frames that has recieved and timers that has expired
 *  during sending an I pdu (refer to data_req_handler).  
 *  frames queue by mac_indicate function (llc_mac.c) and timers queue by timer
 *  callback functions(llc_c_ac.c).
 * 
 * Parameters :
 *  connection_t *conn : pointer to connection. 
 * 
 * Returns : 
 *  0 : success.
 *  1 : failure.
 */


us16 
process_rxframes_events(connection_t *conn)
{
	us16 rc=0;
	rx_object_t *rx_object;   
	us16 size;
   
	if (conn->busy > 1){  /* avoiding reentrancy */
		return (0);
	}   

	size = dll_query(&conn->log_q);
	if (size == 0){
		return (0);
	}
	do{
		rc = dll_remove (&conn->log_q, (void **) &rx_object, 
								DLL_WHERE_HEAD);
		if (!rc){
			switch(rx_object->desc) {
			case FRAME:
				if (conn->state > 1) { /* not closed */
					llc_pdu_router((sap_t *)conn->parent_sap, 
							conn, rx_object->obj.frame, 
									TYPE_2);
				} else {
					frame_skb_free(rx_object->obj.frame);
					FDBG_ERR_MSG(("\n recieving frame on 
							closed connection\n"));
				}
				break;
			case EVENT :     /* timer expiration event */
				if (conn->state > 1) { /* not closed */
					conn_send_event(conn, 
							rx_object->obj.event);
				}
				else{
					conn_return_event(conn, 
							rx_object->obj.event);
				}
				break;
			default :	
				break;
			}
			kfree(rx_object);
		}
	} while (rc==0);

	return (rc);
}
