/*
 * (llc_mac.c)- Manages interface between LLC and MAC
 *
 * 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_MAC_C

#include <asm/byteorder.h>
#include <asm/types.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/if_tr.h>
#include <linux/if_ether.h>
#include <linux/interrupt.h>
#include <net/sock.h>
#include <net/cm_types.h>
#include <net/cm_mm.h>
#include <net/cm_frame.h>
#include <net/cm_dll.h>
#include <net/llc_if.h>
#include <net/llc_mac.h>
#include <net/lan_hdrs.h>
#include <net/llc_pdu.h>
#include <net/os_if.h>
#include <net/llc_glob.h>
#include <net/llc_sap.h>
#include <net/llc_conn.h>
#include <net/llc_if.h>
#include <net/llc_main.h>
#include <net/llc_pdu.h>
#include <net/llc_rout.h>
#include <net/decode.h>
#include <net/llc_dbg.h>

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

/* 
 * function prototypes 
 */

int mac_indicate (struct sk_buff *skb, struct device *dev,
						struct packet_type *pt);
static us16 fix_up_incoming_frame (frame_t *frame);

/* 
 * global data 
 */

static int Module_init = NO;

/* 
 * hook into device callback interface to receive 802.2 PDUs from
 * either Ethernet or Token-Ring networks
 */

static struct packet_type P8022_packet_type = 
{
	0,
	NULL,
	mac_indicate,
	NULL,
	NULL,
};


static struct packet_type P8022_tr_packet_type = 
{
	0,      
	NULL,
	mac_indicate,
	NULL,
	NULL,
};
 

/*
 * Function: mac_init
 *
 * Description:
 *  If module already initialized, doesn't do it again, returns success; else 
 *  all incomming packets queued here for eventual servicing. caches source 
 *  route information in here, if supported; registers packet types for 
 *  Ethernet and Token-Ring sources. Sets Module_init to "YES".
 *
 * Parameters:
 *  None
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mac_init ()
{
	us16 rc = 0;

	if (Module_init == YES) {
		return (0);
	}

	P8022_packet_type.type = htons(ETH_P_802_2);
	P8022_tr_packet_type.type = htons(ETH_P_TR_802_2);

	if (!rc) {
		Module_init = YES;
	}

	return (rc);
}


/*
 * Function: mac_exit
 *
 * Description:
 *  If module is not initialized, return success, else sets Module_int to "NO"
 *  unregisters packet types for Ethernet and Token-Ring source.
 *
 * Parameters:
 *  None
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mac_exit ()
{
	us16 rc = 0;

	if (Module_init == NO) {
		return (0);
	}

	Module_init = NO;

	return (rc);
}


/*
 * Function: mac_send_pdu
 *
 * Description:
 *  If module is not initialized then returns failure, else figures out where
 *  to direct this PDU. Sends PDU to specific device, at this point a device
 *  must has been assigned to the PDU; If not, can't transmit the PDU. PDU sent
 *  to MAC layer, is free to re-send at a later time.
 *
 * Parameters:
 *  frame_t *pdu : This argument points to pdu which must be sent.
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mac_send_pdu (frame_t *pdu)
{
	us16 rc = 1;
	struct sk_buff *skb;
	int pri = 0;
	pdu_sn_t *llc_hdr;

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

	if (pdu->dev) {
		skb = (struct sk_buff *) pdu->skb;
		skb->dev = (struct device *) pdu->dev;
		skb->arp = 1;
		if (skb->sk)
			pri = (int) skb->sk->priority;
		llc_hdr = (pdu_sn_t *)pdu->llc_hdr; 
		if (LLC_PDU_TYPE_IS_I(llc_hdr)) {
			frame_free(pdu);
		} else if (is_loopback(pdu->dev)==0) { 
			frame_free(pdu); 
			skb->free = 1;
		} else {                     
			skb->free = 0;
		}
		dev_queue_xmit (skb, skb->dev, pri);
		rc = 0;
	} else {
		FDBG_ERR_MSG((KERN_ERR "mac_send_pdu failed, pdu->dev is 
				NULL\n"));
	}
	
	return (rc);
}


/*
 * Function: mac_indicate
 *
 * Description:
 *  This function is LLC callback for net_bh (linux networking bottom half 
 *  handler). When a 802.2 frame receives to system, net_bh calls this function.
 *  this function checks SAP and connection of recieved pdu and returnes frame
 *  to llc_pdu_router for sending to proper state machine. if frames is related
 *  to a busy connection ( a conection is sending data now ), function queues
 *  this frame in log_q of connection. this frame will be processed after sending 
 *  data at end of data_req_handler in llc_if.c by calling process_rxframe_events. 
 *
 * Parameters:
 *  struct sk_buff *skb : recieved pdu. 
 *  struct device *dev : device that receive pdu. 
 *  struct packet_type *pt : packet type. 
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

int 
mac_indicate (struct sk_buff *skb, struct device *dev, struct packet_type *pt)
{
	us16 rc;
	frame_t *frame;
	sap_t *sap;  
	connection_t *conn;
	pdu_sn_t *pdu;
	rx_object_t *rx_obj;
	address_t source_addr;
	us8 dest;

#ifdef TR_DBG
	printk("mac_indicate called .....\n");
#endif
	IS_SKB(skb);
	rc = frame_allocate (&frame);
	if (rc) {
		FDBG_ERR_MSG((KERN_ERR "\nmac_indicate failed,can't 
				allocate frame\n"));
		return(0);
	}

	skb->free = 1;
	frame->skb = (void *) skb;
	frame->dev = (void *) dev;
	frame->pt = (void *) pt;
	//frame->mac_hdr = skb->data - 2*MAC_ADDR_LEN - 2;
	frame->mac_hdr = skb->mac.raw;
	frame->mac_type = ntohs(skb->protocol);
	frame->llc_hdr = skb->data;
	fix_up_incoming_frame (frame);
#ifdef TR_DBG
	decode_trframe_t(frame);
#endif
	pdu = (pdu_sn_t *)frame->llc_hdr;
	if ( pdu->dsap == 0 ) { /* NULL DSAP, refer to station */
		llc_pdu_router(sap,NULL,frame,0);
	} else {
		rc = llc_sap_find(pdu->dsap,&sap);
		if (rc) { 	/* unknown SAP */ 
			frame_skb_free(frame);
			return (0);
		}
		decode_pdu_type(frame,&dest);
		switch (dest) {

			case DEST_SAP : /* type 1 services */ 
				llc_pdu_router(sap,NULL,frame,TYPE_1);
				break;

			case DEST_CONN : 
				pdu_decode_sa (frame, source_addr.mac);
				pdu_decode_ssap (frame, &source_addr.lsap);
				rc = sap_find_conn (sap, &source_addr,
							 (void **) &conn);
				if (rc) { /* unknown connection */ 
					frame_skb_free(frame);
					return (0);
				} else if (conn->busy > 0) { 
				/* connection is sending I pdu, queue frame */
					rx_obj = (rx_object_t *) kmalloc(sizeof
						(rx_object_t), GFP_ATOMIC);

					if (rx_obj) {
						rx_obj->desc = FRAME; 
						rx_obj->obj.frame = frame; 
						dll_add(&conn->log_q, (void *)
							rx_obj, DLL_WHERE_TAIL);
					} else {
						FDBG_ERR_MSG(("mac_indicate : 
							can't allocate 
							memory"));
					}
				} else {
					llc_pdu_router((sap_t *)
						conn->parent_sap, 
						conn, frame, TYPE_2);
				}
				break;

			default :   /* unknown or not supported pdu */   
	 			frame_skb_free(frame);
				break;
                   
		}    
	}
	
	
	return (0);
}


/*
 * Function: fix_up_incoming_frame
 *
 * Description:
 *  Initializes internal frame pointer to start of network layer by deriving 
 *  length of LLC header; finds length of LLC control field in LLC header by 
 *  looking at the two lowest-order bits of the first control field byte; 
 *  field is either 3 or 4 bytes long.
 *
 * Parameters:
 *  frame_t *frame : This argument points to incoming frame.
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

static us16 
fix_up_incoming_frame (frame_t *frame)
{
	us8 llc_len;
	pdu_sn_t *pdu;
	us16 data_size;

	pdu = (pdu_sn_t *) frame->llc_hdr;

	switch (pdu->ctrl_1 & LLC_PDU_TYPE_MASK) {
		case LLC_PDU_TYPE_U:

			llc_len = 1;   
			break;

		case LLC_PDU_TYPE_S:
		case LLC_PDU_TYPE_I:
		default:

			llc_len = 2;       
			break;
	}

	llc_len += 2;  

	skb_pull((struct sk_buff *)frame->skb,llc_len);
	if (frame->mac_type == ETH_P_802_2){
		data_size = ntohs(((ieee_802_3_mac_hdr_t *)frame->mac_hdr)->lpdu_len) -
								llc_len;
		skb_trim((struct sk_buff *)frame->skb,data_size); 
	}	
	return (0);
}


/*
 * Function: is_loopback  
 *
 * Description:
 *  This routine determines if given device is loopback.
 *
 * Parameters:
 *  struct device *dev : This argument points to given device.
 *
 * Returns:
 *  0 : If device is loopback
 *  1 : If device is not loopback
 */

us16 
is_loopback (struct device *dev)
{
	return ( !(dev->flags & IFF_LOOPBACK) );
}
