#include "brg_shortcut.h"
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/compiler.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/crc32.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/proc_fs.h>
#include <linux/module.h>

#ifdef CONFIG_RTL_MULTI_ETH_WAN
#include <linux/if_smux.h>
#endif
#ifdef CONFIG_RTL8676_Dynamic_ACL
#include <net/rtl/rtl867x_hwnat_api.h>
#endif

//#include <linux/imq.h>
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
#include <linux/if_vlan.h>
#endif

//#define CONFIG_RTL8672_ETHSKB_CONTROL_POOL
//#define CONFIG_RTK_ETHUP 

#ifdef CONFIG_RTL8672_ETHSKB_CONTROL_POOL
extern int net_smallpkt_heavytraffic;
#endif

#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE

/** 
* the value of MAX_BRG_SC_ENTRY_NUM is  BRG_SC_ENTRY_POWER power of 2
*     BRG_SC_ENTRY_POWER       {0, 1, 2, 3,  4  ,5,  6,  7,   8,    9,    10 } 
*     MAX_BRG_SC_ENTRY_NUM   {1, 2, 4, 8, 16, 32,64,128,256,512,1024}
*  e.g. if BRG_SC_ENTRY_POWER is 6, then MAX_BRG_SC_ENTRY_NUM is 64 
**/
#define BRG_SC_ENTRY_POWER 6 //change this value to set fast bridge table size

// table size should be the power of 2
#define MAX_BRG_SC_ENTRY_NUM	 (1<<BRG_SC_ENTRY_POWER)
// HASH_BUCKET_SIZE is now same as MAX_BRG_SC_ENTRY_NUM
#define HASH_BUCKET_SIZE (1<<BRG_SC_ENTRY_POWER)

//use max bucket depth to make sure the time of hash searching is no longer than logN
#define HASH_BUCKET_MAX_DEPTH BRG_SC_ENTRY_POWER 

#define BRG_ENTRY_MIN_DELETE_TIME 10
#else
#define MAX_BRG_SC_ENTRY_NUM	8
#endif


int BRG_ENTRY_AGING_TIME =BRG_ENTRY_AGING_TIME_NORMAL;
//int BRG_ENTRY_FORCE_TIMEOUT=BRG_ENTRY_FORCE_TIMEOUT_NORMAL;



typedef struct brg_shortcut_entry {
	unsigned char enable;
	unsigned short macPair[6];
	struct net_device *srcDev;
	struct net_device *dstDev;
	unsigned int mark;//for IP QoS
	unsigned long tick;
	unsigned int dev_vlanid;
#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE	
	struct brg_shortcut_entry * next; 
	//link the entries with the same hashkey generated by src mac
	//use double linked list to remove a entry quickly
	struct list_head 	BrgSrcMacHashList;
#endif
}BRG_SHORTCUT_ENTRY;


#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE
#ifdef CONFIG_RTL8672
__DRAM
#endif 
/**for a stream whose mac pair is (mac1, mac2) 
**                     srcmac     destmac
**  upstream       mac1         mac2
**  downstream     mac2         mac1
**  current hash function can convert these two mac pairs (mac1,mac2) and (mac2,mac1) 
**  into different hash value, so we can use one table to store both entries
**/
static BRG_SHORTCUT_ENTRY fbTbl[MAX_BRG_SC_ENTRY_NUM];
//the hash key is generated by mac pair (dest,src) 
static BRG_SHORTCUT_ENTRY * fbTblHash[HASH_BUCKET_SIZE];
// this hash table is only used for brgScFind() and the hash key is only generated by src mac
static struct list_head fbTblSrcHash[HASH_BUCKET_SIZE];
// all free entries are linked to a free list and fbTblFreeHead points to the head of this list
static BRG_SHORTCUT_ENTRY * fbTblFreeHead;

#else
#ifdef CONFIG_RTL8672
__DRAM
#endif 
static BRG_SHORTCUT_ENTRY fbTbl[MAX_BRG_SC_ENTRY_NUM]; // for downstream

#ifdef CONFIG_RTL8672
__DRAM
#endif 
static BRG_SHORTCUT_ENTRY fbTblup[MAX_BRG_SC_ENTRY_NUM]; //for upstream
#endif

static unsigned int brg_shortcut_enable = 1;
static unsigned int brg_real_shortcut =1;
static DEFINE_SPINLOCK(lock);

#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE
static inline unsigned long djb2hash(unsigned short * destMac,unsigned short * srcMac)
{
    unsigned long hash = 5381;
    int c,i;
	if(destMac != NULL)
	{
		for( i= 0; i< 3; i++)
	    {
			c = destMac[i];
	        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
	    }
	}
    for( i= 0; i< 3; i++)
    {
		c = srcMac[i];
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
    }
	hash = hash ^ (hash >> 16); 
    return hash;
}


static inline int hashKeyfromMacPair(unsigned short * destMac,unsigned short * srcMac)
{ 
    return djb2hash(destMac, srcMac) & (MAX_BRG_SC_ENTRY_NUM -1);
}

static inline int hashKeyfromSrcMac(unsigned short * srcMac)
{
    return djb2hash(NULL, srcMac)  & (MAX_BRG_SC_ENTRY_NUM -1);
}

static void  brgRemoveFromMacPairHash(BRG_SHORTCUT_ENTRY * preEntry,BRG_SHORTCUT_ENTRY * pEntry, int hashKey)
{
	if(preEntry != NULL)
	{
		preEntry->next = pEntry->next;
	}
	else 
	{   //pEntry is the first entry in this bucket and preEntry should be NULL at this time
		fbTblHash[hashKey] = pEntry->next;
	}
}
static void  brgReturnToFreeList(BRG_SHORTCUT_ENTRY * pEntry)
{
	if(fbTblFreeHead != NULL)
	{
		pEntry->next = fbTblFreeHead;
		fbTblFreeHead = pEntry;
	}
	else
	{
		fbTblFreeHead = pEntry;
		pEntry->next = NULL;
	}

}

static void brgResetTable(void)
{
	int i;
	fbTblFreeHead = &fbTbl[0];
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if(i < (MAX_BRG_SC_ENTRY_NUM-1))
		{
			fbTbl[i].next = &fbTbl[i+1];
		}
		else
		{
			fbTbl[i].next = NULL;
		}
		fbTbl[i].enable = 0;		
	}
		
	for (i=0; i<HASH_BUCKET_SIZE; i++)
	{
		fbTblHash[i] = NULL;
		INIT_LIST_HEAD(&fbTblSrcHash[i]);
	}

}

static void brgClearTable(void)
{
	brgResetTable();
	BRG_ENTRY_AGING_TIME =BRG_ENTRY_AGING_TIME_NORMAL;
}


static void brgClearTableByDev(struct net_device *dev)
{
	BRG_SHORTCUT_ENTRY * pEntry, * preEntry, * pTempEntry;
	int i;
	
	for (i=0; i<HASH_BUCKET_SIZE; i++)
	{
		preEntry = NULL;
		pEntry = fbTblHash[i];
		while(pEntry != NULL)
		{ 
			if ((pEntry->srcDev->name[0]==dev->name[0]) || (pEntry->dstDev->name[0]==dev->name[0]))
			{
				pTempEntry = pEntry->next;
				pEntry->enable=0;
				brgRemoveFromMacPairHash(preEntry, pEntry, i);
				list_del(&pEntry->BrgSrcMacHashList);
				brgReturnToFreeList(pEntry);		
				pEntry = pTempEntry;
			}
			else
			{
				preEntry = pEntry;
				pEntry = preEntry->next;
			}
		}
    }
	
}

/*when src mac learned by a new interface, all related brgSC entry should be deleted*/
int brgScDelete(unsigned char *mac)
{
	BRG_SHORTCUT_ENTRY * pEntry, * preEntry, * pTempEntry;	
	unsigned short *pmac = (unsigned short *)mac;
	unsigned short *smac, *dmac;
	int i;
	
	for (i=0; i<HASH_BUCKET_SIZE; i++)
	{
		preEntry = NULL;
		pEntry = fbTblHash[i];
		while(pEntry != NULL)
		{ 
			smac = &(pEntry->macPair[3]);
			dmac = &(pEntry->macPair[0]);
			if (!((pmac[0]^smac[0])|(pmac[1]^smac[1])|(pmac[2]^smac[2])) ||
				!((pmac[0]^dmac[0])|(pmac[1]^dmac[1])|(pmac[2]^dmac[2])))
			{
				pTempEntry = pEntry->next;
				pEntry->enable = 0;
				brgRemoveFromMacPairHash(preEntry, pEntry, i);
				list_del(&pEntry->BrgSrcMacHashList);
				brgReturnToFreeList(pEntry);
				pEntry = pTempEntry;
			}
			else
			{
				preEntry = pEntry;
				pEntry = preEntry->next;
			}
		}
	}
	
	return 0;
}
EXPORT_SYMBOL(brgScDelete);


/* Kevin, when the skb pass through br_flood, it cannot record rest. if , so delete the entry */
void brgScEntryDelete(unsigned short *s_mac,unsigned short *d_mac,int dir)
{
	BRG_SHORTCUT_ENTRY * pEntry, * preEntry, * pTempEntry;
    unsigned short *smac, *dmac;
	int hashKey;

    hashKey = hashKeyfromMacPair(d_mac, s_mac);
	pEntry = fbTblHash[hashKey];
	preEntry =NULL;
	while(pEntry != NULL)
	{
		smac = &(pEntry->macPair[3]);
		dmac = &(pEntry->macPair[0]);
		if (!((s_mac[0]^smac[0])|(s_mac[1]^smac[1])|(s_mac[2]^smac[2])) &&
			!((d_mac[0]^dmac[0])|(d_mac[1]^dmac[1])|(d_mac[2]^dmac[2])))
		{		
			pTempEntry = pEntry->next;
			pEntry->enable = 0;
			
			brgRemoveFromMacPairHash(preEntry, pEntry, hashKey);
			list_del(&pEntry->BrgSrcMacHashList);
			brgReturnToFreeList(pEntry);
			
			pEntry = pTempEntry;
		}
		else
		{
			preEntry = pEntry;
			pEntry = preEntry->next;
		}
		
	}

}
EXPORT_SYMBOL(brgScEntryDelete);


/*
 * dst: source device
 * mac: source mac
 */
int brgScFind(struct net_device *dst, unsigned char *mac, unsigned long *tick)
{
	BRG_SHORTCUT_ENTRY * pEntry, *preEntry, * pNextEntry;
	unsigned short *pmac = (unsigned short *)mac;
	unsigned short *smac;
	unsigned long tick_tmp=0;
	int hashKey,srcHashKey;
	
	srcHashKey = hashKeyfromSrcMac(pmac);

	list_for_each_entry_safe(pEntry, pNextEntry, &fbTblSrcHash[srcHashKey],BrgSrcMacHashList)
	{
		if (pEntry->srcDev->name[0] == dst->name[0]) {
			smac = &(pEntry->macPair[3]);
			if (!((pmac[0]^smac[0])|(pmac[1]^smac[1])|(pmac[2]^smac[2]))){
				if ((jiffies - pEntry->tick) > BRG_ENTRY_AGING_TIME) 
				{
					pEntry->enable = 0;
					//remove it from source mac hash table
					list_del(&pEntry->BrgSrcMacHashList);				
					//remove it from mac pair hash table
					hashKey = hashKeyfromMacPair(&(pEntry->macPair[0]),&(pEntry->macPair[3]));
					if(pEntry == fbTblHash[hashKey])
						fbTblHash[hashKey] = pEntry->next;
					else
					{
						preEntry = fbTblHash[hashKey];
						while(pEntry !=preEntry->next)
							preEntry = preEntry->next;
						preEntry->next = pEntry->next;
					}
					//return this new free entry to the free list
					brgReturnToFreeList(pEntry);
					continue;
				}

				if (tick_tmp == 0)
					tick_tmp = pEntry->tick;
				else
					tick_tmp = (tick_tmp >= pEntry->tick)?tick_tmp:pEntry->tick;
			}
		}
	}

	if (tick_tmp) {
		*tick = tick_tmp;
				return 1;
	}
	
	return 0;
}
EXPORT_SYMBOL(brgScFind);

//rx will learn source mac and destination mac, and tx will learn related destination interface
/*****************************************************************
** NAME: brgShortcutLearnMac
** PARA: pMac - ethernet header, that is DA/SA
              srcDev - packet receive device.
** RETURN: void
*****************************************************************/
static void  brgShortcutLearnMac(unsigned short *pMac, struct net_device *srcDev, int dir, int hashkey)
{
	int i, hashKey,srcHashKey,index=-1;
	unsigned long maxelapse=0;
	BRG_SHORTCUT_ENTRY * preEntry, * pEntry, * pHashEntry, *preTempEntry;
	unsigned short *pmac;

	hashKey = hashkey >=0? hashkey: hashKeyfromMacPair(&pMac[0],&pMac[3]);
	pHashEntry = fbTblHash[hashKey];
	
	//1. check if there is a entry having the same mac address in a bucket
	i= 0; //save the bucket depth
	while(pHashEntry != NULL)
	{ 
		pmac = pHashEntry->macPair; 
		if (!((pmac[0]^pMac[0])|(pmac[1]^pMac[1])|(pmac[2]^pMac[2])|(pmac[3]^pMac[3])|(pmac[4]^pMac[4])|(pmac[5]^pMac[5])))
		{
			if(pHashEntry->srcDev != srcDev)
			{
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
				// check for vlan tagged interface (ex. eth0.5 vs. eth0.5.10, where eth0.5 should not replace eth0.5.10)
				// vlan device should not be replaced by its real device
				if (is_vlan_dev(pHashEntry->srcDev) && (vlan_dev_real_dev(pHashEntry->srcDev)==srcDev)) {
					//printk("%s is real dev of %s\n", srcDev->name, pHashEntry->srcDev->name);
					return;
				}
#endif
				pHashEntry->srcDev = srcDev;
				pHashEntry->dstDev = NULL;
				pHashEntry->tick = jiffies;
			}
			return;
		}	
		i++;
		pHashEntry = pHashEntry->next;
	}
	srcHashKey = hashKeyfromSrcMac(&pMac[3]);
	//2. get a free entry if exists
	if(fbTblFreeHead != NULL)
	{
		pEntry = fbTblFreeHead;
		fbTblFreeHead = fbTblFreeHead->next;
		pEntry->next = NULL;
	}
	else if(i <= HASH_BUCKET_MAX_DEPTH)
	{
	//3. if there is no free entry, find an entry with the smallest tick in the whole hash table.
	//    Remove the selected entry in the old bucket.
		for (i=0; i<HASH_BUCKET_SIZE; i++)
		{
			preEntry = NULL;
			pHashEntry = fbTblHash[i];
			while(pHashEntry != NULL)
			{ 
				if ((maxelapse==0) || (time_after_eq(maxelapse, pHashEntry->tick))){
					maxelapse = pHashEntry->tick;
					pEntry = pHashEntry;
					preTempEntry = preEntry;
					index = i;
				}
				preEntry = pHashEntry;
				pHashEntry = preEntry->next;
			}
		}
		//if the time of the longest existing entry is less than the setting value, just return
		if(((jiffies- pEntry->tick) < BRG_ENTRY_MIN_DELETE_TIME) || (index < 0))
		{
			return;
		}
		else
		{
			//the entry needs to be removed is not in the same bucket the new entry belongs to
			if(index != hashKey)
			{
				//remove the selected entry, the first entry must exist
				brgRemoveFromMacPairHash(preTempEntry, pEntry, index);
			}
			
			list_del(&pEntry->BrgSrcMacHashList);		
		}
		
	}
	else
	{
		//if the entry number of a bucket is already the max bucket depth,just return
		printk("brgShortcutLearnMac, the entry number of the bucket %d is already the max bucket depth.\n",hashKey);
		return;
	}
	//4. insert this entry to the first place in the bucket
	if(index != hashKey)
	{
		if(fbTblHash[hashKey] == NULL)
		{
			fbTblHash[hashKey] = pEntry;
			pEntry->next = NULL;
		}
		else
		{			
			pEntry->next = fbTblHash[hashKey];
			fbTblHash[hashKey] = pEntry;
		}
	}
	
	list_add_tail(&pEntry->BrgSrcMacHashList, &fbTblSrcHash[srcHashKey]);
	
	
	//5. update the entry with new mac address and src dev
	pEntry->macPair[0] = pMac[0];
	pEntry->macPair[1] = pMac[1];
	pEntry->macPair[2] = pMac[2];
	pEntry->macPair[3] = pMac[3];
	pEntry->macPair[4] = pMac[4];
	pEntry->macPair[5] = pMac[5];
	pEntry->srcDev = srcDev;
	pEntry->dstDev = NULL;
	pEntry->tick = jiffies;
	pEntry->enable = 1;
		
}

/*****************************************************************
** NAME: brgShortcutGetEntry
** PARA: pMacPair - ethernet header, that is DA/SA
              srcDev - packet receive device.
** RETURN: bridge shortcut entry
*****************************************************************/
__IRAM
static BRG_SHORTCUT_ENTRY * brgShortcutGetEntry(unsigned short *pMacPair, struct net_device *srcDev, int dir,int hashkey)
{
	unsigned short *pmac;
	BRG_SHORTCUT_ENTRY * pFoundEntry = NULL;
	BRG_SHORTCUT_ENTRY * pEntry,* preEntry, * pTempEntry;
	int hashKey;

	hashKey = hashkey >=0? hashkey: hashKeyfromMacPair(&pMacPair[0],&pMacPair[3]);
	preEntry = NULL;
	pEntry = fbTblHash[hashKey];
	
	while(pEntry != NULL)
	{ 
		if(pEntry->enable == 0)
		{
REMOVE:		pTempEntry = pEntry->next;
			brgRemoveFromMacPairHash(preEntry, pEntry, hashKey);
			list_del(&pEntry->BrgSrcMacHashList);
			brgReturnToFreeList(pEntry);	
			pEntry = pTempEntry;
			continue;
		}
		if (pEntry->enable && pEntry->dstDev && pEntry->srcDev == srcDev)
		{
			{
#ifdef CONFIG_RTL8672_ETHSKB_CONTROL_POOL			
				if(!net_smallpkt_heavytraffic)
#endif					
				{
					if((jiffies- pEntry->tick) > BRG_ENTRY_AGING_TIME){
						goto REMOVE;
					}
				}	
			}
			pmac = pEntry->macPair;
			if ( !((pmac[0]^pMacPair[0])|(pmac[1]^pMacPair[1])|(pmac[2]^pMacPair[2])|(pmac[3]^pMacPair[3])|(pmac[4]^pMacPair[4])|(pmac[5]^pMacPair[5])) 
				/*&& !strcmp(pfbTbl[i].srcDev->name,srcDev->name) */)
			{
				//get entry, update age time.	
				pEntry->tick = jiffies;
				pFoundEntry = pEntry;
				break;
			}
		}
		
		preEntry = pEntry;
		pEntry = preEntry->next;
	}
	
	return pFoundEntry;
}



int debugBridge;
/*****************************************************************
** NAME: brgScLearnDestItf
** PARA: skb - transmit packet
              dstDev -packet transmit device.
** RETURN: 
*****************************************************************/
void brgScLearnDestItf(struct sk_buff *skb, struct net_device *dstDev)
{
	BRG_SHORTCUT_ENTRY * pEntry;
	unsigned short *pMacPair,*pmac;
	int hashKey;
	if(brg_shortcut_enable){
		pmac = (unsigned short *)skb->data;

		if((*(unsigned char *)pmac) & 0x1)	/*not support multicast*/
			return;
		
		hashKey = hashKeyfromMacPair(&pmac[0],&pmac[3]);
		pEntry = fbTblHash[hashKey];
		while(pEntry != NULL)
		{ 			
			pMacPair = pEntry->macPair;			
			if (!((pmac[0]^pMacPair[0])|(pmac[1]^pMacPair[1])|(pmac[2]^pMacPair[2])|(pmac[3]^pMacPair[3])|(pmac[4]^pMacPair[4])|(pmac[5]^pMacPair[5])))
			{ 
				pEntry->tick = jiffies;
				pEntry->dstDev = dstDev;	

				#if defined(CONFIG_RTL8676_Dynamic_ACL)
				{
					unsigned char *input_mac = (unsigned char *)(pEntry->macPair);					
					#if defined(CONFIG_RTL_FLOW_BASE_HWNAT)
					rtl8676_add_L2Unicast_hwacc(&input_mac[6],&input_mac[0],pEntry->srcDev->name,pEntry->dstDev->name); 
					#endif
				}
				#endif
					
					return;
			}			
			pEntry = pEntry->next;
		}

	}
}
EXPORT_SYMBOL(brgScLearnDestItf);

#else

static void brgClearTableByDev(struct net_device *dev)
{
	BRG_SHORTCUT_ENTRY *pfbTbl;
	int i;
	
	pfbTbl=fbTblup;
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if ((pfbTbl[i].srcDev->name[0]==dev->name[0]) || (pfbTbl[i].dstDev->name[0]==dev->name[0]))
			pfbTbl[i].enable=0;
	}
	pfbTbl=fbTbl;
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if ((pfbTbl[i].srcDev->name[0]==dev->name[0]) || (pfbTbl[i].dstDev->name[0]==dev->name[0]))
			pfbTbl[i].enable=0;
	}
}

/*when src mac learned by a new interface, all related brgSC entry should be deleted*/
int brgScDelete(unsigned char *mac)
{
	BRG_SHORTCUT_ENTRY *pfbTbl;
	unsigned short *pmac = (unsigned short *)mac;
	unsigned short *smac, *dmac;
	//unsigned long tick_tmp=0;
	int i;

	pfbTbl = fbTblup;
DEL:
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if (!pfbTbl[i].enable)
			continue;

			smac = &pfbTbl[i].macPair[3];
			dmac = &pfbTbl[i].macPair[0];
			if (!((pmac[0]^smac[0])|(pmac[1]^smac[1])|(pmac[2]^smac[2])) ||
				!((pmac[0]^dmac[0])|(pmac[1]^dmac[1])|(pmac[2]^dmac[2]))){
				pfbTbl[i].enable = 0;
		}
	}

	if (pfbTbl == fbTblup) {
		pfbTbl = fbTbl;
		goto DEL;
	}

	return 0;
}
EXPORT_SYMBOL(brgScDelete);

/* Kevin, when the skb pass through br_flood, it cannot record rest. if , so delete the entry */

void brgScEntryDelete(unsigned short *s_mac,unsigned short *d_mac,int dir)
{
    BRG_SHORTCUT_ENTRY *pfbTbl;
    unsigned short *smac, *dmac;
    int i;

    if (dir == DIR_LAN)
		pfbTbl = fbTblup;
	else
		pfbTbl = fbTbl;


    for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if (!pfbTbl[i].enable)
			continue;

		smac = &pfbTbl[i].macPair[3];
		dmac = &pfbTbl[i].macPair[0];
		if (!((s_mac[0]^smac[0])|(s_mac[1]^smac[1])|(s_mac[2]^smac[2])) &&
			!((d_mac[0]^dmac[0])|(d_mac[1]^dmac[1])|(d_mac[2]^dmac[2])))
			pfbTbl[i].enable = 0;		
	}


}
EXPORT_SYMBOL(brgScEntryDelete);


/*
 * dst: source device
 * mac: source mac
 */
int brgScFind(struct net_device *dst, unsigned char *mac, unsigned long *tick)
{
	BRG_SHORTCUT_ENTRY *pfbTbl;
	unsigned short *pmac = (unsigned short *)mac;
	unsigned short *smac;
	unsigned long tick_tmp=0;
	int i;

	pfbTbl = fbTblup;
FIND:
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if (!pfbTbl[i].enable)
			continue;

		if (pfbTbl[i].srcDev->name[0] == dst->name[0]) {
			smac = &pfbTbl[i].macPair[3];
			if (!((pmac[0]^smac[0])|(pmac[1]^smac[1])|(pmac[2]^smac[2]))){
				if ((jiffies-pfbTbl[i].tick) > BRG_ENTRY_AGING_TIME) {
					pfbTbl[i].enable = 0;
					continue;
		}

				if (tick_tmp == 0)
					tick_tmp = pfbTbl[i].tick;
				else
					tick_tmp = (tick_tmp >= pfbTbl[i].tick)?tick_tmp:pfbTbl[i].tick;
			}
			}
		}

	if (tick_tmp) {
		*tick = tick_tmp;
				return 1;
			}

	if (pfbTbl == fbTblup) {
		pfbTbl = fbTbl;
		goto FIND;
	}

	return 0;
}
EXPORT_SYMBOL(brgScFind);

//rx will learn source mac and destination mac, and tx will learn related destination interface
/*****************************************************************
** NAME: brgShortcutLearnMac
** PARA: pMac - ethernet header, that is DA/SA
              srcDev - packet receive device.
** RETURN: void
*****************************************************************/
static void  brgShortcutLearnMac(unsigned short *pMac, struct net_device *srcDev, int dir)
{
	BRG_SHORTCUT_ENTRY *pfbTbl;
	int i, index=-1, selected=0;
	unsigned long maxelapse=0;

	if (dir == DIR_LAN)
		pfbTbl = fbTblup;
	else
		pfbTbl = fbTbl;
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if (pfbTbl[i].enable)
		{
			unsigned short *pmac = pfbTbl[i].macPair;
			if (!((pmac[0]^pMac[0])|(pmac[1]^pMac[1])|(pmac[2]^pMac[2])|(pmac[3]^pMac[3])|(pmac[4]^pMac[4])|(pmac[5]^pMac[5])))
			{
				if(pfbTbl[i].srcDev != srcDev)
				{
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
					// check for vlan tagged interface (ex. eth0.5 vs. eth0.5.10, where eth0.5 should not replace eth0.5.10)
					// vlan device should not be replaced by its real device
					if (is_vlan_dev(pfbTbl[i].srcDev) && (vlan_dev_real_dev(pfbTbl[i].srcDev)==srcDev)) {
						//printk("%s is real dev of %s\n", srcDev->name, pfbTbl[i].srcDev->name);
						return;
					}
#endif
					pfbTbl[i].srcDev = srcDev;
					pfbTbl[i].dstDev = NULL;
					pfbTbl[i].tick = jiffies;
				}
				return;
			}

			if ((maxelapse==0) || (time_after_eq(maxelapse, pfbTbl[i].tick))){
				maxelapse = pfbTbl[i].tick;
				selected = i;
			}
		}
		else{
			if (index == -1)
				index = i;
		}
	}

	index = index<0?selected:index;
	pfbTbl[index].macPair[0] = pMac[0];
	pfbTbl[index].macPair[1] = pMac[1];
	pfbTbl[index].macPair[2] = pMac[2];
	pfbTbl[index].macPair[3] = pMac[3];
	pfbTbl[index].macPair[4] = pMac[4];
	pfbTbl[index].macPair[5] = pMac[5];
	pfbTbl[index].srcDev = srcDev;
	pfbTbl[index].dstDev = NULL;
	pfbTbl[index].tick = jiffies;
	pfbTbl[index].enable = 1;

	
}

/*****************************************************************
** NAME: brgShortcutGetEntry
** PARA: pMacPair - ethernet header, that is DA/SA
              srcDev - packet receive device.
** RETURN: bridge shortcut entry
*****************************************************************/
__IRAM
static BRG_SHORTCUT_ENTRY * brgShortcutGetEntry(unsigned short *pMacPair, struct net_device *srcDev, int dir)
{
	BRG_SHORTCUT_ENTRY *pfbTbl;
	unsigned short *pmac;
	int i;
	BRG_SHORTCUT_ENTRY * pEntry = NULL;
	
	if (dir == DIR_LAN)
		pfbTbl = fbTblup;
	else
		pfbTbl = fbTbl;
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if (pfbTbl[i].enable && pfbTbl[i].dstDev && pfbTbl[i].srcDev == srcDev)
		{
			{
#ifdef CONFIG_RTL8672_ETHSKB_CONTROL_POOL			
				if(!net_smallpkt_heavytraffic)
#endif					
				{
					if((jiffies-pfbTbl[i].tick) > BRG_ENTRY_AGING_TIME){
						pfbTbl[i].enable = 0;
						continue;
					}
				}	
			}
			pmac = pfbTbl[i].macPair;
			if ( !((pmac[0]^pMacPair[0])|(pmac[1]^pMacPair[1])|(pmac[2]^pMacPair[2])|(pmac[3]^pMacPair[3])|(pmac[4]^pMacPair[4])|(pmac[5]^pMacPair[5])) 
				/*&& !strcmp(pfbTbl[i].srcDev->name,srcDev->name) */)
			{
				//get entry, update age time.				
				pfbTbl[i].tick = jiffies;
				pEntry = pfbTbl+i;
				break;
			}
		}
	}

	return pEntry;
}

#if defined(CONFIG_DUAL_BAND_TX_THROUGHPUT_ENHANCE)
struct net_device *brgScGetDstDev(struct sk_buff *skb)
{
	struct ethhdr *peh;
	BRG_SHORTCUT_ENTRY *pFbEntry = NULL;

	peh = eth_hdr(skb);
	if (is_multicast_ether_addr(peh->h_dest)) 
		return NULL;

	if (!skb->dev)
		return NULL;

	if(brg_shortcut_enable){
		if (skb->dev->priv_flags & IFF_DOMAIN_WAN)//wan
			pFbEntry = brgShortcutGetEntry((unsigned short *)peh, skb->dev, DIR_WAN);
		else
			pFbEntry = brgShortcutGetEntry((unsigned short *)peh, skb->dev, DIR_LAN);
	}

	if (pFbEntry)
		return pFbEntry->dstDev;
	
	return NULL;
}

EXPORT_SYMBOL(brgScGetDstDev);
#endif


int debugBridge;
/*****************************************************************
** NAME: brgScLearnDestItf
** PARA: skb - transmit packet
              dstDev -packet transmit device.
** RETURN: 
*****************************************************************/
void brgScLearnDestItf(struct sk_buff *skb, struct net_device *dstDev)
{
	BRG_SHORTCUT_ENTRY *pfbTbl;
	int i;
	unsigned short *pMacPair,*pmac;

	if(brg_shortcut_enable){
		pmac = (unsigned short *)skb->data;

		if((*(unsigned char *)pmac) & 0x1)	/*not support multicast*/
			return;

		pfbTbl = fbTblup;

FIND:
		for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
		{
			if (pfbTbl[i].enable)
			{			
				pMacPair = pfbTbl[i].macPair;			
				if (!((pmac[0]^pMacPair[0])|(pmac[1]^pMacPair[1])|(pmac[2]^pMacPair[2])|(pmac[3]^pMacPair[3])|(pmac[4]^pMacPair[4])|(pmac[5]^pMacPair[5])))
				{ 

					pfbTbl[i].tick = jiffies;
					pfbTbl[i].dstDev = dstDev;	

					#ifdef CONFIG_RTL_ADV_FAST_PATH					
					pfbTbl[i].mark = skb->mark;
					#endif /* CONFIG_RTL_ADV_FAST_PATH */
									
					#if defined(CONFIG_RTL8676_Dynamic_ACL) 
					{
						int ret;
						unsigned char *input_mac = (unsigned char *)pfbTbl[i].macPair;					
						#if defined(CONFIG_RTL_FLOW_BASE_HWNAT)
						ret = rtl8676_add_L2Unicast_hwacc(&input_mac[6],&input_mac[0],pfbTbl[i].srcDev->name,pfbTbl[i].dstDev->name);
						#endif
					}
					#endif
					
					return;
				}
			}
		}

		if(pfbTbl== fbTblup)
		{
			pfbTbl = fbTbl;
			goto FIND;
		}
	}
}
EXPORT_SYMBOL(brgScLearnDestItf);

static void brgClearTable(void)
{
	BRG_SHORTCUT_ENTRY *pfbTbl;
	int i;
	pfbTbl=fbTblup;
	
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		pfbTbl[i].enable=0;
	}
	pfbTbl=fbTbl;
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		pfbTbl[i].enable=0;
	}
	BRG_ENTRY_AGING_TIME =BRG_ENTRY_AGING_TIME_NORMAL;
 	//BRG_ENTRY_FORCE_TIMEOUT=BRG_ENTRY_FORCE_TIMEOUT_NORMAL;

	
}
#endif


/*****************************************************************
** NAME: brgShortcutProcess
** PARA: skb - transmit packet
		srcDev -packet receive device.
** RETURN: 1 - go through bridge shortcut.
		    0 - transfer to upper layer
*****************************************************************/
__IRAM
int brgScProcess(struct sk_buff *skb, struct net_device *srcDev, int dir)
{
	BRG_SHORTCUT_ENTRY *pFbEntry;
	struct ethhdr *peh;
	unsigned long flags;
#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE
	int hashKey;
#endif
	peh = eth_hdr(skb);
	if (is_multicast_ether_addr(peh->h_dest)) /*not support multicast packet*/
		return 0;
		
	if(brg_shortcut_enable){
		if(ntohs(peh->h_proto) == 0x8100){	/*not support vlan packet*/
			//local_irq_restore(flags);
			return 0;
		}
		spin_lock_irqsave(&lock, flags);
		#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE
		//if in brgShortcutGetEntry() doesn't find the match entry, 
		//the hashkey can be used in brgShortcutLearnMac(), save one time hashkey calculation 
		hashKey = hashKeyfromMacPair((unsigned short *)peh->h_dest,(unsigned short *)peh->h_source);
		pFbEntry = brgShortcutGetEntry((unsigned short *)peh, srcDev, dir,hashKey);
		#else
		pFbEntry = brgShortcutGetEntry((unsigned short *)peh, srcDev, dir);
		#endif
		if (pFbEntry)
		{
			if (unlikely(!brg_real_shortcut)) {
				spin_unlock_irqrestore(&lock, flags);
				return 0;
			}
			
			skb->dev = pFbEntry->dstDev;
			spin_unlock_irqrestore(&lock, flags);
#ifdef CONFIG_RTL_ADV_FAST_PATH
			skb->mark = pFbEntry->mark;
#endif /* CONFIG_RTL_ADV_FAST_PATH */
			skb_push(skb, ETH_HLEN);

			if (skb->dev->priv_flags & IFF_DOMAIN_WAN) {
				int rc;

				rc = dev_queue_xmit(skb);
				if (rc < 0) {
					pFbEntry->enable = 0;
					//skb has been freed in dev_queue_xmit
					//dev_kfree_skb(skb);
					printk("fast bridge tx error!, error no = %d\n", rc);
				}
			} else {
				//skip device queue for non WAN dev to speed up xmit
				if(skb->dev->netdev_ops->ndo_start_xmit(skb,skb->dev)) {
					pFbEntry->enable = 0;
					dev_kfree_skb(skb);
					printk("fast bridge tx error!\n");
				}
			}
			return 1;		
		} else {
#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE
			brgShortcutLearnMac((unsigned short *)peh, srcDev, dir,hashKey);
#else
			brgShortcutLearnMac((unsigned short *)peh, srcDev, dir);
#endif
			spin_unlock_irqrestore(&lock, flags);
		}		
	}

	return 0;
}
EXPORT_SYMBOL(brgScProcess);

#ifdef CONFIG_FAST_FORWARDING
__IRAM
int brgFastForwarding(struct sk_buff *skb, int dir)
{
	BRG_SHORTCUT_ENTRY *pFbEntry;
	unsigned short *pMacHdr;
	unsigned long flags;

	if(*(unsigned char *)skb->data & 0x1) /*not support multicast packet*/
		return 0;

	spin_lock_irqsave(&lock, flags);
	
	if(brg_shortcut_enable){
		pMacHdr = (unsigned short *)skb->data;
		if(pMacHdr[6] == 0x8100){	/*not support vlan packet*/
			spin_unlock_irqrestore(&lock, flags);
			return 0;
		}
#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE		
		if((pFbEntry = brgShortcutGetEntry(pMacHdr, skb->dev, dir,-1)))
#else
		if((pFbEntry = brgShortcutGetEntry(pMacHdr, skb->dev, dir)))
#endif
		{
			skb->dev = pFbEntry->dstDev;
			
			if ((skb->dev->priv_flags & IFF_DOMAIN_WLAN) || 
				((skb->dev->priv_flags & (IFF_DOMAIN_WAN|IFF_OSMUX)) == (IFF_DOMAIN_WAN)))
			{
				extern void rtl865x_free_eth_priv_buf(struct sk_buff *skb, unsigned flag);
				extern void init_skbhdr(struct sk_buff *skb, unsigned char *data, unsigned int size,
					void (*prealloc_cb)(struct sk_buff *, unsigned));
				init_skbhdr(skb,skb->head, skb->len, rtl865x_free_eth_priv_buf);
				skb->len = skb->tail - skb->data;
			}

			if(pFbEntry->dstDev->hard_start_xmit(skb,pFbEntry->dstDev))
			{
				pFbEntry->enable = 0;
				dev_kfree_skb_any(skb);
			}
			spin_unlock_irqrestore(&lock, flags);
			return 1;
		}
	}

	spin_unlock_irqrestore(&lock, flags);
	return 0;
}
#endif//end of CONFIG_FAST_FORWARDING


static int fastbridge_read_proc(struct seq_file *f, void *data)
{
	int i;
	char srcportbuf[16];
	char dstportbuf[16];
#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE	
	BRG_SHORTCUT_ENTRY * pHashEntry;
#endif

	seq_printf(f, "\nrtk fast bridge is %s, real_fastbridge is %s\n",brg_shortcut_enable?"enabled":"disabled",brg_real_shortcut?"enabled":"disabled");
#ifndef CONFIG_FASTBRIDGE_USES_HASHTABLE	
	seq_printf(f, "************************fast bridge table***************************\n");
	seq_printf(f, "Index   Enabled   TimeOut   DstMac           SrcMac           SrcItf     DstItf    MARK   DIR\n");
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if (fbTbl[i].enable){
			if (fbTbl[i].srcDev)
				sprintf(srcportbuf,"%s",fbTbl[i].srcDev->name);
			else
				sprintf(srcportbuf,"---");
			
			if(fbTbl[i].dstDev){
				sprintf(dstportbuf,"%s",fbTbl[i].dstDev->name);
			}else{
				sprintf(dstportbuf,"---");
			}
			seq_printf(f, "%-5d   %-7d   %-7d   %04x:%04x:%04x   %04x:%04x:%04x   %-10s   %-9s   %-6u   DOWN\n",
				i,fbTbl[i].enable,(jiffies-fbTbl[i].tick)>BRG_ENTRY_AGING_TIME?1:0,fbTbl[i].macPair[0],fbTbl[i].macPair[1],fbTbl[i].macPair[2],
				fbTbl[i].macPair[3],fbTbl[i].macPair[4],fbTbl[i].macPair[5],srcportbuf,dstportbuf, fbTbl[i].mark);
		}
	}
	
	for (i=0; i<MAX_BRG_SC_ENTRY_NUM; i++)
	{
		if (fbTblup[i].enable){
			if (fbTblup[i].srcDev)
				sprintf(srcportbuf,"%s",fbTblup[i].srcDev->name);
			else
				sprintf(srcportbuf,"---");
			
			if(fbTblup[i].dstDev){
				sprintf(dstportbuf,"%s",fbTblup[i].dstDev->name);
			}else{
				sprintf(dstportbuf,"---");
			}
				seq_printf(f, "%-5d   %-7d	 %-7d   %04x:%04x:%04x	 %04x:%04x:%04x   %-10s   %-9s	%10x  UP\n",
							i,fbTblup[i].enable,(jiffies-fbTblup[i].tick)>BRG_ENTRY_AGING_TIME?1:0,fbTblup[i].macPair[0],fbTblup[i].macPair[1],fbTblup[i].macPair[2],
							fbTblup[i].macPair[3],fbTblup[i].macPair[4],fbTblup[i].macPair[5],srcportbuf,dstportbuf, fbTblup[i].mark);
		}
	}
#endif

#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE	
	seq_printf(f, "*******************fast bridge hash table*******************\n");
	seq_printf(f, "Bucket	Enabled   TimeOut	DstMac			 SrcMac 		  SrcItf	 DstItf    MARK\n");
	for (i=0; i<HASH_BUCKET_SIZE; i++)
	{
		pHashEntry = fbTblHash[i];
		while(pHashEntry != NULL)
		{
			if (pHashEntry->srcDev)
				sprintf(srcportbuf,"%s",pHashEntry->srcDev->name);
			else
				sprintf(srcportbuf,"---");
			
			if(pHashEntry->dstDev){
				sprintf(dstportbuf,"%s",pHashEntry->dstDev->name);
			}else{
				sprintf(dstportbuf,"---");
			}
			seq_printf(f, "%-5d   %-7d   %-7d   %04x:%04x:%04x   %04x:%04x:%04x   %-10s   %-9s   %-6u\n",
				i, pHashEntry->enable,(jiffies-pHashEntry->tick)>BRG_ENTRY_AGING_TIME?1:0,pHashEntry->macPair[0],pHashEntry->macPair[1],pHashEntry->macPair[2],
				pHashEntry->macPair[3],pHashEntry->macPair[4],pHashEntry->macPair[5],srcportbuf,dstportbuf, pHashEntry->mark);
			pHashEntry = pHashEntry->next;
		}
	}
	seq_printf(f, "*****************fast bridge source hash table**************\n");
	seq_printf(f, "Bucket	Enabled   TimeOut	DstMac			 SrcMac 		  SrcItf	 DstItf    MARK\n");
	for (i=0; i<HASH_BUCKET_SIZE; i++)
	{
		list_for_each_entry(pHashEntry, &fbTblSrcHash[i],BrgSrcMacHashList)
		{	
			if (pHashEntry->srcDev)
				sprintf(srcportbuf,"%s",pHashEntry->srcDev->name);
			else
				sprintf(srcportbuf,"---");
			
			if(pHashEntry->dstDev){
				sprintf(dstportbuf,"%s",pHashEntry->dstDev->name);
			}else{
				sprintf(dstportbuf,"---");
			}
			seq_printf(f, "%-5d   %-7d   %-7d   %04x:%04x:%04x   %04x:%04x:%04x   %-10s   %-9s   %-6u\n",
				i, pHashEntry->enable,(jiffies-pHashEntry->tick)>BRG_ENTRY_AGING_TIME?1:0,pHashEntry->macPair[0],pHashEntry->macPair[1],pHashEntry->macPair[2],
				pHashEntry->macPair[3],pHashEntry->macPair[4],pHashEntry->macPair[5],srcportbuf,dstportbuf, pHashEntry->mark);
		}
	}
	seq_printf(f, "-----------------------------------------\n");
	pHashEntry = fbTblFreeHead;
	i=0;
	while(pHashEntry!= NULL)
	{
		pHashEntry = pHashEntry->next;
		i++;
	}
	seq_printf(f, "free entry number %d \n",i);
#endif
	return 0;
}


static int fastbridge_write_proc(struct file *file, const char *buffer,	unsigned long count, void *data)
{
	unsigned char flag;

	if (count < 2)
		return -EFAULT;
	
	if (buffer && !copy_from_user(&flag, buffer, 1)) {
		if(flag == '0')
		{
			brg_shortcut_enable = 0;
			brg_real_shortcut = 0;
            printk("rtk fast bridge is disabled\n");
		}
		else if(flag == '1')
		{
			brg_shortcut_enable = 1;
			brg_real_shortcut = 1;
            printk("rtk fast bridge is enabled\n");
		}
		else if(flag == '2')
		{                    
            brgClearTable();
            printk("clean rtk fast bridge table\n");     
		}
		else if (flag == '3')
		{
		  	brg_shortcut_enable = 1;
			brg_real_shortcut = 0;
            		brgClearTable();
			printk("rtk fast bridge is enabled for fast bridge table record, not using real fast bridge\n");
		}
		return count;
	}else
		return -EFAULT;
}

static int read_proc_open_fastbridge(struct inode *inode, struct file *file) {
    return(single_open(file, fastbridge_read_proc, NULL));
}

static ssize_t write_proc_fastbridge(struct file *file, const char __user * userbuf, size_t count, loff_t * off) {
    return fastbridge_write_proc(file, userbuf, count, NULL);
}

static struct file_operations fops_proc_fastbridge = {
    .open     = read_proc_open_fastbridge,
    .read     = seq_read,
    .llseek   = seq_lseek,
    .release  = single_release,
    .write    = write_proc_fastbridge,
};

extern struct proc_dir_entry *realtek_proc;
#define OLD_COMPATIBLE 1
static int __init fastbridge_init(void) 
{
	struct proc_dir_entry *entry=NULL;	
	entry = proc_create_data("fastbridge", 0644, realtek_proc, &fops_proc_fastbridge, NULL);
	if (!entry) {
		printk("Realtek fastbridge, create proc failed!\n");
	}
	#if OLD_COMPATIBLE
	entry = proc_create_data("fastbridge", 0644, NULL, &fops_proc_fastbridge, NULL);
	#endif 
	
#ifdef CONFIG_FASTBRIDGE_USES_HASHTABLE
	brgResetTable();
#endif	
	return 0;
}

static void __exit fastbridge_exit(void) 
{
	remove_proc_entry("fastbridge", realtek_proc);
	#if OLD_COMPATIBLE
	remove_proc_entry("fastbridge", NULL);
	#endif 
}

module_init(fastbridge_init);
module_exit(fastbridge_exit);