/*
 * NET3:	Token ring device handling subroutines
 * 
 *		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.
 *
 *              May'99 D.J Barrow barrow_dj@yahoo.com 
 *              added code cleanups, added smp safe locking 
 *              removed broken sti/cli stuff, fixed
 *              race conditions & added limiting to the size of the
 *              rif_table.
 *              put rif caches in device structure where they should be 
 *              to avoid smp lock collisions if a few token ring devices
 *              are going concurrently.
 *              Added hint field to sock struct & now using 3 linked lists
 *              to minimise linked list walking see if_tr.h for info.
 *              Added correct MTU fixups to transmitted rifs so that
 *              Changing the MTU size is supported better.
 *
 * Fixes:       3 Feb 97 Paul Norton <pnorton@cts.com> Minor routing fixes.
 *              Added rif table to /proc/net/tr_rif and rif timeout to
 *              /proc/sys/net/token-ring/rif_timeout.
 *              22 Jun 98 Paul Norton <p.norton@computer.org> Rearranged
 *              tr_header and tr_type_trans to handle passing IPX SNAP and
 *              802.2 through the correct layers. Eliminated tr_reformat.
 *        
 */

#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/trdevice.h>
#include <linux/skbuff.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/net.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <net/arp.h>
#include <asm/atomic.h>
#include <asm/bitops.h>
#include <linux/if_tr.h>
#include <net/sock.h>





#define TR_SR_DEBUG  0
#define TR_SR_SANITY 0
#define TR_DEBUG_TIME 0
#define TR_SR_BROADCAST_DEBUG 0
#define TR_SR_LISTPERF 0 /* Compile with this on if dubious about my claims DJB */
#define TR_RIF_BUFF_SIZE  64
#define TR_RIF_CACHE_LIST_SIZE         64
#define TR_RIF_LINKED_LIST_WIDTH       8
#if 0
#define TR_RIF_CACHE_LIST_SIZE       4
#define TR_RIF_LINKED_LIST_WIDTH     4
#endif
#if TR_DEBUG_LOCKS

char lockbuff[256];

int tr_lock2(struct net_device *dev,char *file,int line)
{      
	if(test_and_set_bit(1,&dev->tr_lock)==0) {
		sprintf(lockbuff,"lockbuff %s %d\n",file,line);
		return(TRUE);
	}
	return(FALSE);
}
#endif

int sysctl_tr_rif_linked_list_width=TR_RIF_LINKED_LIST_WIDTH;

/*
 *	Put the headers on a token ring packet. Token ring source routing
 *	makes this a little more exciting than on ethernet.
 */

static hdr_8022 hdr_8022list[] = 
{
	{
		EXTENDED_SAP,     /* FOR ETH_P_IP & ARP */
		EXTENDED_SAP,     
		UI_CMD,
		{0,0,0},
	},
};

void *tr_alloc(size_t size)
{
	void *retval=kmalloc(size,GFP_ATOMIC);

	if(retval)
		memset(retval,0,size);
	return(retval);
}

/*   N.B. this hash will still work with a source routed address */
/*   as the and operation masks this out. */
__inline__  tr_rif_cache_lists *tr_hash(struct net_device *dev,
					__u8 *macaddr)
{
	__u16 hash=(*((__u16 *)&macaddr[0])^*((__u16 *)&macaddr[2])
		  ^*((__u16 *)&macaddr[4]))&(TR_RIF_CACHE_LIST_SIZE-1);
	return(&dev->rif_cache_lists[hash]);
}


/*
 * Sorry this is so complex, if you have the misfortune of having to debug this
 * try preprocessing it first ( gcc -E ) DJB.
 * If I didn't do it using macros I'd end up installing the same bug in several places
 * P.S. gcc isn't stupid the stuff compiles good.
 */
#if TR_SR_SANITY
/* My big ball of wax to check the list. */
void tr_sanity_check_list(tr_rif_cache_lists *list,int index,char *name,int delta)
{
	tr_rif_cache_entry *entry,*prev_entry=NULL,*next_entry,*entry2;
	tr_rif_dlist       *dlist=&list->dlist[index];
	tr_rif_dentry      *dentry,*next_dentry,*prev_dentry;
	int                entrycnt,num_entries=list->num_entries; 
	long               now=jiffies;
	int                diff;
	tr_rif_cache_entry *entry_stack[TR_RIF_LINKED_LIST_WIDTH+16];

	if(delta==0) {
		if(num_entries==0) {
			if(list->dlist[0].head||list->dlist[0].tail||
			   list->dlist[1].head||list->dlist[1].tail)
				panic("%s num_entries=0 & list not empty list=%p\n",name,list);
		}
		if(num_entries==1) {
			if(list->dlist[0].head==NULL||
			   list->dlist[0].head!=list->dlist[0].tail||
			   list->dlist[0].tail!=list->dlist[1].head||
			   list->dlist[1].head!=list->dlist[1].tail)
				panic("%s num_entries=1 & list inconsistent list=%p\n",name,list);
		}
	}
	for(entry=dlist->head,entrycnt=0;entry&&
		    entrycnt<=(num_entries+3)
		    ;entry=next_entry,entrycnt++) {
		entry_stack[entrycnt]=entry;
		dentry=&entry->dentry[index];
		next_entry=dentry->next;
		if(next_entry) {
			next_dentry=&next_entry->dentry[index];
			if(next_dentry->prev!=entry)
				panic("%s index=%d doubly linked list inconsistency forward traversal dlist=%p entry=%p\n",name,index,dlist,entry);
			if((now-dentry->timestamp)>((now-next_dentry->timestamp)))
				panic("%s index=%d timestamp inconsistency dlist=%p\n",name,index,dlist);
		}
		prev_entry=entry;
	}
/* Have to allow for adding deleting entries */
	diff=entrycnt-num_entries;
	if(diff<-delta||diff>delta)
		panic("%s index=%d entrycnt %d expected %d dlist=%p forward traversal\n",
		      name,index,entrycnt,num_entries,dlist);
	next_entry=NULL;
	for(entry=dlist->tail;entry&&
		    entrycnt<=(num_entries+1)
		    ;entry=prev_entry) {
		if(entry!=entry_stack[--entrycnt])
			panic("%s entrystack detected a fault dlist=%p entrycnt=%d"
			      "entry=%p entrystack[entrycnt]=%p\n",
			      name,dlist,entrycnt,entry,entry_stack[entrycnt]);
		dentry=&entry->dentry[index];
		prev_entry=dentry->prev;
		if(prev_entry) {
			prev_dentry=&prev_entry->dentry[index];
			if(prev_dentry->next!=entry)
				panic("%s index=%d doubly linked list inconsistency reverse traversal dlist=%p entry=%p\n",name,index,dlist,entry);
		}
		next_entry=entry;
	}
	for(entry=dlist->head;entry;entry=entry->dentry[index].next)
		for(entry2=dlist->head;entry2;entry2=entry2->dentry[index].next)
			if(entry!=entry2&&memcmp(entry->addr,entry2->addr,TR_ALEN)==0)
				panic("%s index=%d duplicate mac addrs dlist=%p\n"
				      "entry1=%p entry2=%p %02X:%02X:%02X:%02X:%02X:%02X\n"
				      ,name,index,dlist,
				      entry,entry2,
				      entry->addr[0],entry->addr[1],
				      entry->addr[2],entry->addr[3],
				      entry->addr[4],entry->addr[5]);
}
#endif

#define TR_FIX_PREV_ENTRY_COMMON(dnext)              \
prev_entry=dentry->prev;                             \
if(prev_entry) {                                     \
       prev_dentry=&prev_entry->dentry[index];       \
       prev_dentry->next=(dnext);                    \
} 

#define TR_REMOVE_DENTRY_COMMON                      \
TR_FIX_PREV_ENTRY_COMMON(dentry->next)               \
next_entry=dentry->next;                             \
if(next_entry) {                                     \
       next_dentry=&next_entry->dentry[index];       \
       next_dentry->prev=prev_entry;                 \
}

#define TR_ADD_TO_HEAD_COMMON                        \
next_entry=dlist->head;                              \
if(next_entry) {                                     \
       next_dentry=&next_entry->dentry[index];       \
       next_dentry->prev=entry;                      \
}                                                    \
else                                                 \
       dlist->tail=entry;                            \
dentry->next=next_entry;                             \
dentry->prev=NULL;                                   \
dlist->head=entry;

__inline__ void tr_make_list_head(tr_rif_cache_lists *list,tr_rif_cache_entry *entry,int index)
{
	tr_rif_cache_entry *prev_entry,*next_entry;
	tr_rif_dlist       *dlist=&list->dlist[index];
	tr_rif_dentry      *dentry=&entry->dentry[index],*next_dentry,*prev_dentry;
   
	if(dlist->head!=entry) {
		TR_REMOVE_DENTRY_COMMON
		if(dlist->tail==entry)
		        dlist->tail=prev_entry;
		TR_ADD_TO_HEAD_COMMON
	}
#if TR_SR_SANITY
	tr_sanity_check_list(list,index,"tr_make_list_head",0);
#endif
}

__inline__ tr_rif_cache_entry *tr_remove_tail(tr_rif_cache_lists *list,int index)
{
	tr_rif_dlist       *dlist=&list->dlist[index];
	tr_rif_dentry      *dentry,*prev_dentry;
	tr_rif_cache_entry *entry=dlist->tail,*prev_entry;
#if TR_SR_SANITY
	tr_sanity_check_list(list,index,"tr_remove_tail before",1);
#endif   
	if(entry) {
		dentry=&entry->dentry[index];
		TR_FIX_PREV_ENTRY_COMMON(NULL)
		dlist->tail=prev_entry;
		if(dlist->head==entry)
			dlist->head=NULL;
	}

#if TR_SR_SANITY
	tr_sanity_check_list(list,index,"tr_remove_tail after",1);
#endif
	return(entry);
} 
__inline__ void tr_remove_entry(tr_rif_cache_lists *list,tr_rif_cache_entry *entry,int index)
{
	tr_rif_cache_entry *prev_entry,*next_entry;
	tr_rif_dlist       *dlist=&list->dlist[index];
	tr_rif_dentry      *dentry=&entry->dentry[index],*prev_dentry,*next_dentry;

	TR_REMOVE_DENTRY_COMMON
	if(dlist->head==entry)
	        dlist->head=next_entry;
	if(dlist->tail==entry)
		dlist->tail=prev_entry;
#if TR_SR_SANITY
	tr_sanity_check_list(list,index,"tr_remove_entry",1);
#endif
}

__inline__ void tr_add_to_head(tr_rif_cache_lists *list,tr_rif_cache_entry *entry,int index,long now)
{
	tr_rif_dlist       *dlist=&list->dlist[index];
	tr_rif_dentry      *dentry=&entry->dentry[index],*next_dentry;
	tr_rif_cache_entry *next_entry;
 
	TR_ADD_TO_HEAD_COMMON
	if(dlist->tail==NULL)
	        dlist->tail=entry;
	dentry->timestamp=now;
#if TR_SR_SANITY
	tr_sanity_check_list(list,index,"tr_add_to_head",1);
#endif
}

__inline__ void tr_add_to_tail(tr_rif_cache_lists *list,tr_rif_cache_entry *entry,int index,long now)
{
	tr_rif_dlist       *dlist=&list->dlist[index];
	tr_rif_dentry      *dentry=&entry->dentry[index],*prev_dentry;
	tr_rif_cache_entry *prev_entry;

	prev_entry=dlist->tail;
	if(prev_entry) {               
		prev_dentry=&prev_entry->dentry[index];
		prev_dentry->next=entry;
		dentry->timestamp=prev_dentry->timestamp;
	}
	else {
		dlist->head=entry;
		dentry->timestamp=now;
	}
	dentry->prev=prev_entry;
	dentry->next=NULL;       
	dlist->tail=entry;
#if TR_SR_SANITY
	tr_sanity_check_list(list,index,"tr_add_to_tail",1);
#endif                           
}
/*
  From RFC 1042
  0                   1
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |  B  |   LTH   |D|  LF |   r   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LF - Largest Frame: 3 bits

The LF bits specify the maximum MTU supported by all
bridges along a specific route.  All multi-ring broadcast
frames should be transmitted with a value at least as
large as the supported MTU.  The values used are:

LF (binary)   MAC MTU      IP MTU

   000          552         508
   001         1064        1020
   010         2088        2044
   011         4136        4092
   100         8232        8188

Even though the netdevice.h header file specifies
dev->mtu as the interface's mtu it sets ethernet's mtu
as 1500 as opposed to 1516 the size of an ethernet frame.
Therefore I'm taking MTU as being IP MTU.

I added this code as I saw on mailing lists people changing
MTU sizes.
*/

__inline__ __u16 tr_set_rcf_framesize(struct net_device *dev)
{
	unsigned		mtu=dev->mtu;

	if(mtu>=8188)
		return(0x40);
	if(mtu>=4092)
		return(0x30);
	if(mtu>=/*2044*/2000) /* to keep compatible with our current implementation */
		return(0x20);
	if(mtu>=1020)
		return(0x10);
	return(0x0);
}

/*

  *FIXME* I added this code in the hope that someone
  would add code such that each source routed destination
  can have a different MTU as recommended by rfc1042.
  Currently we are ramming 2K packets down ethernet drivers throats
  across token ring bridges,
  I presume 802.2 compatible ethernet drivers are designed to put up
  with this but we will break some of them if we start
  ramming 8k packets down their throats.
  Hopefully some nice thoughtful person will figure this one out
  include/net/dst.h has a pmtu field
  include/linux/route.h has a rt_mtu field
  maybe it would be a starting point to see what these do.
 */
__inline__ unsigned tr_get_rcf_mtu(__u16 rcf)
{
	rcf=ntohs(rcf)&TR_RCF_LF_MASK;

	switch(rcf) {
	case 0x40:
		return(8188);
	case 0x30:
		return(4092);
	case 0x20:
		return(/*2044*/2000); /* to keep compatible with our current implementation */
	case 0x10:
		return(1020);
	default:
		return(508); /* Play it safe */
	}
}

/*
 *	We try to do  source routing... 
 */
int  tr_add_rif(struct sk_buff *skb,__u8 *daddr,struct net_device *dev) 
{
	int rcflen=-1;
	tr_rif_cache_lists *rif_cache_listtop;
	tr_rif_cache_entry *entry;
	struct sock	*sk=skb->sk;
	tr_rif_info  *rif;
#if TR_SR_LISTPERF
	int perfcnt;
#endif
	if(tr_lock(dev)) {
		rif_cache_listtop=tr_hash(dev,daddr);
		entry=((sk&&rif_cache_listtop->hint_idx==sk->hint_idx) ? sk->hint:NULL);  
		if(entry==NULL||memcmp(entry->addr,daddr,TR_ALEN)) {
			/*
			 *	Walk the hash table and look for an entry
			 */
			for(
#if TR_SR_LISTPERF
			    perfcnt=0,
#endif
			    entry=rif_cache_listtop->dlist[TR_USED].head;entry &&
			    memcmp(entry->addr,daddr,TR_ALEN);entry=entry->dentry[TR_USED].next
#if TR_SR_LISTPERF
			    ,perfcnt++
#endif
			    );
			if(entry) {
				if(sk) {
					sk->hint=entry;
					sk->hint_idx=rif_cache_listtop->hint_idx;
				}
			}
#if TR_SR_LISTPERF
			printk("tr_rif num entries walked=%d num entries=%d\n",perfcnt,
			       rif_cache_listtop->num_entries);
#endif   
		}
#if TR_SR_DEBUG
		else
			printk("Hint worked horray\n");
#endif
		/*
		 *	If we found an entry we can route the frame.
		 */
		if(entry) {
			/* Dont't use information if it's an arp packet */
			if( skb->protocol != __constant_htons(0x0806) ) {

#if TR_SR_DEBUG
				printk("source routing for %02X:%02X:%02X:%02X:%02X:%02X\n",daddr[0],
				       daddr[1],daddr[2],daddr[3],daddr[4],daddr[5]);
#endif
				rcflen=(ntohs(entry->rcf) & TR_RCF_LEN_MASK) >> 8;
				if(rcflen) {
					rif=(tr_rif_info  *)skb_push(skb,rcflen);
					memcpy(rif,&entry->rcf,rcflen);
					rif->rcf=htons((rif->rcf & __constant_htons(TR_RCF_DIR_BIT) ? 0:TR_RCF_DIR_BIT)|
						       tr_set_rcf_framesize(dev))|(rif->rcf & __constant_htons(TR_RCF_LEN_MASK));
#if TR_SR_DEBUG
					printk("entry found with rcf %04x\n", entry->rcf);
#endif
				}
				/* Are we the most recently used ??, if not fixup. */
				tr_make_list_head(rif_cache_listtop,entry,TR_USED);
				entry->dentry[TR_USED].timestamp=jiffies;
			}
#if TR_SR_DEBUG
			else
				printk("skipped source-routing (arp) for %02X:%02X:%02X:%02X:%02X:%02X\n",daddr[0],daddr[1],daddr[2],daddr[3],daddr[4],daddr[5]);
#endif
		}
		tr_unlock(dev);
	}
	return(rcflen);
}


int sprintfbridgeinfo(char *buffer,tr_rif_info *rifinfo)
{
	int      len=0;
	int      bridgecnt,curr_bridgeno;
	int      segment,bridge_type;
   
	len=sprintf(buffer,"%04X", ntohs(rifinfo->rcf));
	bridgecnt=((ntohs(rifinfo->rcf) & TR_RCF_LEN_MASK) >> 9)-2;
	for(curr_bridgeno = 0; curr_bridgeno <= bridgecnt; curr_bridgeno++) { 
		if(curr_bridgeno==0) {                
			segment=RD_TO_RINGNUM(rifinfo->rseg[curr_bridgeno]);
			len+=sprintf(buffer+len,"  %03X",segment);
		}
		else {
			segment=RD_TO_RINGNUM(rifinfo->rseg[curr_bridgeno]);
			bridge_type=RD_TO_BRIDGE_TYPE(rifinfo->rseg[curr_bridgeno-1]);
			len+=sprintf(buffer+len,"-%01X-%03X",bridge_type,segment);
		}
	}
	return(len);
}

/*
 *	We have learned some new RIF information for our source
 *	routing.
 */
 
void tr_add_rif_info(struct trh_hdr *trh, struct net_device *dev)
{

	tr_rif_cache_lists *rif_cache_listtop=NULL;
	tr_rif_cache_entry *entry=NULL,*delete_entry;
	long now;
	__u8          trh_saddr0;
	int rcflen=0;
	int          fixupdated=FALSE;
	__u16 old_ring, new_ring;
	int entry_rcflen;
#if TR_SR_LISTPERF
	int   perfcnt;
#endif

#if TR_SR_DEBUG
	char *debugstr;
#endif
#if TR_SR_BROADCAST_DEBUG
	char broadcastbuff[TR_RIF_BUFF_SIZE];
#endif
	if(tr_lock(dev)) {
		/* is something else playing with the list */
		if(trh->saddr[0] & TR_RII) {
			rcflen=((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8);
#if TR_SR_BROADCAST_DEBUG
			/* As can be seen from this debug code
			 * it is perfectly valid to allow broadcast
			 * frames into the routing table as they have
			 * perfectly valid rifs. 
                         */
			if(ntohs(trh->rcf)&TR_RCF_BROADCAST_MASK) {
				sprintfbridgeinfo(broadcastbuff,(tr_rif_info *)&trh->rcf);
				printk("broadcast received from %02X:%02X:%02X:%02X:%02X:%02X rif=%s\n",
				       trh->saddr[0],trh->saddr[1],trh->saddr[2],
				       trh->saddr[3],trh->saddr[4],trh->saddr[5],broadcastbuff);
			}
#endif
		}
		if(dev->rif_cache_lists==NULL) {
			if(rcflen<6)
				goto done;
			else {
				dev->rif_cache_lists=tr_alloc(TR_RIF_CACHE_LIST_SIZE*sizeof(tr_rif_cache_lists));
				if(dev->rif_cache_lists==NULL)
					goto fail;
			}
		}
		now=jiffies;
		trh_saddr0=trh->saddr[0]&(~TR_RII); /* remove routing info  */
		/*
		 *	Firstly see if the entry exists
		 */
		rif_cache_listtop=tr_hash(dev,trh->saddr);
		if(sysctl_tr_rif_linked_list_width<0)
			sysctl_tr_rif_linked_list_width=0;
		while(rif_cache_listtop->num_entries>sysctl_tr_rif_linked_list_width) {
			delete_entry=tr_remove_tail(rif_cache_listtop,TR_USED);
			if(delete_entry) {
				tr_remove_entry(rif_cache_listtop,delete_entry,TR_UPDATED);
				kfree(delete_entry);
				rif_cache_listtop->num_entries--;
				rif_cache_listtop->hint_idx++;
#if TR_SR_SANITY
				tr_sanity_check_list(rif_cache_listtop,TR_UPDATED,"deleting entries",0);
				tr_sanity_check_list(rif_cache_listtop,TR_USED,"deleting entries",0);
#endif
			}
			else {
				printk("%s linked list bug rif_cache_listtop=%p\n",dev->name,rif_cache_listtop);
				break;
			}
		}
		if(sysctl_tr_rif_linked_list_width==0)
			goto done;
		fixupdated=TRUE;
		for(
#if TR_SR_LISTPERF
			perfcnt=0,
#endif
		        entry=rif_cache_listtop->dlist[TR_UPDATED].head;entry &&
			(trh_saddr0!=entry->addr[0]||memcmp(&entry->addr[1],&trh->saddr[1],TR_ALEN-1))
			;entry=entry->dentry[TR_UPDATED].next
#if TR_SR_LISTPERF
			,perfcnt++
#endif
			);
#if TR_SR_LISTPERF
		printk("tr_add_rif_entry num entries walked=%d num entries=%d\n"
		       ,perfcnt,rif_cache_listtop->num_entries);
#endif 
#if  TR_SR_DEBUG
		debugstr="updating";
#endif

		if(entry) {
#if !TR_DEBUG_TIME
#if !TR_SR_SANITY 
			/* Get ring number of sender and of cached entry */

			if (rcflen >= 6) {
				if (ntohs(trh->rcf) & TR_RCF_DIR_BIT) {
					new_ring = ntohs(trh->rseg[ ((rcflen - 2) >> 1) - 1]) >> 4;
				}
				else {
					new_ring = ntohs(trh->rseg[0]) >> 4;
				}
			}
			else {
				new_ring = 0;
			}
	
			entry_rcflen = (ntohs(entry->rcf) & TR_RCF_LEN_MASK) >> 8;

			if (entry_rcflen >= 6) {
				if (ntohs(entry->rcf) & TR_RCF_DIR_BIT) {
					old_ring = ntohs(entry->rseg[((entry_rcflen - 2) >> 1) - 1]) >> 4;
				}
				else {
					old_ring = ntohs(entry->rseg[0]) >> 4;
				}
			}
			else {
				old_ring = 0;
			}

			if (old_ring != new_ring) {
				printk(KERN_INFO "Machine %02X:%02X:%02X:%02X:%02X:%02X changed ring number from %03X to %03X\n",
					   trh->saddr[0] & 127,
					   trh->saddr[1] & 255,
					   trh->saddr[2] & 255,
					   trh->saddr[3] & 255,
					   trh->saddr[4] & 255,
					   trh->saddr[5] & 255,
					   old_ring,
					   new_ring);
			}

			/* Try to keep good routes */
			if ( (rcflen>((ntohs(entry->rcf) & TR_RCF_LEN_MASK)>>8))
			   &&((now-entry->dentry[TR_UPDATED].timestamp + 120 * HZ) > (jiffies))
			   &&(old_ring == new_ring) ) {
				/* We avoid update the timestamp here 
				 * so sanity timestamps are invalid in reality.
				 */
				goto done;
			}
#endif
#endif
		}
		else {
			if(rif_cache_listtop->num_entries<sysctl_tr_rif_linked_list_width) {
				entry=tr_alloc(sizeof(tr_rif_cache_entry));
				fixupdated=FALSE;
				if(!entry) 
					goto fail;
				tr_add_to_tail(rif_cache_listtop,entry,TR_USED,now);
				tr_add_to_head(rif_cache_listtop,entry,TR_UPDATED,now);
				rif_cache_listtop->num_entries++;
#if TR_SR_SANITY
				tr_sanity_check_list(rif_cache_listtop,TR_USED,"adding entries",0);
				tr_sanity_check_list(rif_cache_listtop,TR_UPDATED,"adding entries",0);
#endif
#if  TR_SR_DEBUG
				debugstr="adding";
#endif	    
			}
			else {
				/* Entry wasn't in cache & therefore hardly
				 * deserves to be here, however if a nw reply
				 * comes down soon it'll stay.
				 * Remember on average hash_width/2 entries
				 * will be thrown out before us.
				 * the other values in the entry are still good
                                 */
				entry=rif_cache_listtop->dlist[TR_UPDATED].tail;
#if  TR_SR_DEBUG
				debugstr="invalidating old entry with";
#endif
			}
		}
		entry->dentry[TR_UPDATED].timestamp=now;
		entry->addr[0]=trh_saddr0;
		memcpy(&entry->addr[1],&trh->saddr[1],TR_ALEN-1);
#if TR_SR_DEBUG
		printk("%s  listtop=%p entry=%p : addr:%02X:%02X:%02X:%02X:%02X:%02X rcf:%04X\n",
		       debugstr,rif_cache_listtop,entry,
		       trh_saddr0,trh->saddr[1],trh->saddr[2],
		       trh->saddr[3],trh->saddr[4],trh->saddr[5],
		       ntohs(trh->rcf));
#endif

		if (rcflen>=6) {
			entry->rcf=trh->rcf&__constant_htons(TR_RCF_CACHE_MASK);
			memcpy(entry->rseg,trh->rseg,rcflen-sizeof(entry->rcf));
		}
		else {
			entry->rcf=0;
		}
		goto done;
	fail:
		printk(KERN_DEBUG "kmalloc failed for tr_add_rif_info!\n");
	done:
		if(fixupdated)
			tr_make_list_head(rif_cache_listtop,entry,TR_UPDATED);
		tr_unlock(dev);
	}
	return;
}



/*
 *	 Create the Ethernet MAC header for an arbitrary protocol layer 
 *
 *	saddr=NULL	means use device source address
 *	daddr=NULL	means leave destination address (eg unresolved arp)
 *
 *      NB as the TR_IG_BIT is set in the ip multicast functional addr
 *      ( C0:00:00:04:00:00 ) we do not have to explicitly check for it DJB. 
 */
int tr_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
              void *daddr, void *saddr, unsigned len) 
{
	hdr_8022 *ptr_8022=NULL,*tr_ptr_8022;
	int   hdr_len=0,rcflen=0;
	tr_fake_header *fake_header; 
	int  broadcast;
	__u16 rcf=TR_NORMAL;
	tr_hw_hdr *trh;
	__u8   fc=0;

	if(daddr==NULL) {
		fake_header=(tr_fake_header *)skb_push(skb,TR_HLEN);
		memcpy(&fake_header->saddr,saddr ? saddr:dev->dev_addr,TR_ALEN);
		fake_header->type=type;
		return -TR_HLEN; /* This is a kludge cos done cos some code might expect this */
	}
   
	switch(type) {
#if 0
		/* raw  ether proto=len
		 * should be callable from p8023.c 
                 */
	case ETH_P_802_3:
		break;
#endif
	case ETH_P_ARP:
	case ETH_P_IP:
		ptr_8022=&hdr_8022list[0];
		break;
	}
	if(ptr_8022) {
		fc=LLC_FRAME;
		tr_ptr_8022=(hdr_8022 *)skb_push(skb,sizeof(*ptr_8022));
		memcpy(tr_ptr_8022,ptr_8022,sizeof(*ptr_8022));
		tr_ptr_8022->ethertype = htons(type);
		hdr_len+=sizeof(*ptr_8022);
	}
	broadcast=(memcmp(daddr,dev->broadcast,TR_ALEN)==0);
	if(ptr_8022)
	{
		if(broadcast)
			rcf=(type==ETH_P_ARP ? TR_RCF_ALL_RINGS_BROADCAST : TR_RCF_LIMITED_BROADCAST);
		else {
			if(((__u8 *)daddr)[0]&TR_IG_BIT)
				rcf=TR_RCF_LIMITED_BROADCAST;
		}
		if(rcf==TR_NORMAL) {
			if(dev->rif_cache_lists)
				rcflen=tr_add_rif(skb,daddr,dev);
			if(dev->rif_cache_lists==NULL||rcflen==-1) {
				rcf=TR_RCF_LIMITED_BROADCAST;
				/* send the mac address to all rings */
			}
		}
		if(rcf!=TR_NO_RCF&&rcf!=TR_NORMAL) {
			*((__u16 *)skb_push(skb,sizeof(__u16)))=
				(htons((sizeof(__u16) << 8)
				       | tr_set_rcf_framesize(dev) | rcf));
			rcflen=sizeof(__u16);
		}
	}
	trh=(tr_hw_hdr *)skb_push(skb,sizeof(*trh));
	memcpy(trh->daddr,broadcast ? dev->broadcast:daddr,TR_ALEN);
	/* We can be called from rebuild header so we might overwrite saddr
	 * so we need memmove here instead of memcpy
         */
	memmove(trh->saddr,saddr ? saddr:dev->dev_addr,TR_ALEN); 
	if(rcflen)
		trh->saddr[0]|=TR_RII;
	trh->fc=fc;
	trh->ac=AC;
	hdr_len+=sizeof(tr_hw_hdr);
	return(hdr_len+rcflen);
}


/*
 *	A neighbour discovery of some species (eg arp) has completed. We
 *	can now send the packet.
 */
 
int tr_rebuild_header(struct sk_buff *skb) 
{
	tr_fake_header *fake_header=(tr_fake_header *)skb->data;
	__u8  daddr[TR_ALEN];
   
	skb_pull(skb,TR_HLEN);
#ifdef CONFIG_INET
	if(arp_find(daddr, skb)) {
		return 1;
	}
	else 
#endif  
	{	
		tr_header(skb,skb->dev,fake_header->type,&daddr,fake_header->saddr,0); 
		return 0;
	}
}
	
/*
 *	Some of this is a bit hackish. We intercept RIF information
 *	used for source routing. We also grab IP directly and don't feed
 *	it via SNAP.
 */
 
unsigned short tr_type_trans(struct sk_buff *skb, struct net_device *dev) 
{

	struct trh_hdr *trh=(struct trh_hdr *)skb->data;
	hdr_8022 *ptr_8022;
	int tr_hdr_size=sizeof(tr_hw_hdr);
	
	skb->mac.raw = skb->data;
	
       	if(trh->saddr[0] & TR_RII)
		tr_hdr_size+=(ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
	skb_pull(skb,tr_hdr_size);
	ptr_8022 = (hdr_8022 *)skb->data;
	
	if(*trh->daddr & TR_IG_BIT) {
		if(!memcmp(trh->daddr,dev->broadcast,TR_ALEN)) 	
			skb->pkt_type=PACKET_BROADCAST;
		else
			skb->pkt_type=PACKET_MULTICAST;
	}
	else if ( (trh->daddr[0] & 0x01) && (trh->daddr[1] & 0x00) &&
		  (trh->daddr[2] & 0x5E))
	{
		skb->pkt_type=PACKET_MULTICAST;
	}
	else if(dev->flags & IFF_PROMISC) {
		if(memcmp(trh->daddr, dev->dev_addr, TR_ALEN))
			skb->pkt_type=PACKET_OTHERHOST;
	}

	if ((skb->pkt_type != PACKET_BROADCAST) &&
	    (skb->pkt_type != PACKET_MULTICAST))
		tr_add_rif_info(trh,dev) ; 
	/*
	 * Strip the SNAP header from ARP packets since we don't 
	 * pass them through to the 802.2/SNAP layers.
	 */

	if (ptr_8022->dsap == EXTENDED_SAP &&
	    (ptr_8022->ethertype == ntohs(ETH_P_IP) ||
	     ptr_8022->ethertype == ntohs(ETH_P_ARP))) {
		skb_pull(skb, sizeof(*ptr_8022));
		return ptr_8022->ethertype;
	}

	return ntohs(ETH_P_802_2);
}

/*
 *	Generate the /proc/net information for the token ring RIF
 *	routing.
 */
 
#ifdef CONFIG_PROC_FS
/* for safety put curly brackets around this macro 
 * othewise you may get cunfused elses etc.
 * also make sure that there is a \n on the end of
 * each printf otherwise the pretty printing might not
 * be so pretty if the cache gets modified while printing
 * to the procfs .
 */
#define trproc_printf(exittr,args...)        \
splen=sprintf(spbuff,##args);                \
spoffset+=splen;                             \
if(spoffset>offset) {                        \
       spbuff+=splen;                        \
       currlen+=splen;                       \
}                                            \
if(currlen>=length)                          \
       goto exittr;
#endif

#ifndef CONFIG_PROC_FS
static int tr_rif_proc_info(char *buffer,char **start, off_t offset, int length)  {}
#else
static int tr_rif_proc_info(char *buffer,char **start, off_t offset, int length)
{
	char   *spbuff=*start=buffer;
	off_t  spoffset=0;
	int    currlen=0,splen;
	tr_rif_cache_entry   *entry,*local_entry=NULL;
	tr_rif_cache_lists *rif_cache_lists;
	int       dir;
	int       rifcnt,listidx;
	struct net_device *dev;
	unsigned long now=jiffies;
	char     rifbuff[TR_RIF_BUFF_SIZE],*rifstr;      
	int      lock_acquired=FALSE;
	trproc_printf(done1,"                          last     last\n");
	trproc_printf(done1,"                          used    updated\n");
	trproc_printf(done1,"if     TR address        (secs)    (secs)  rcf  routing segments\n");
	trproc_printf(done1,"=======================================================================\n");
	read_lock(&dev_base_lock);
	for (dev = dev_base; dev != NULL; dev = dev->next) {
		if(strncmp(dev->name, "tr", 2) == 0) {
			lock_acquired=tr_lock(dev);
			if(lock_acquired) {
				rif_cache_lists=dev->rif_cache_lists;
				rifcnt=0;
				if(rif_cache_lists) {
					for(listidx=0;listidx<TR_RIF_CACHE_LIST_SIZE;listidx++) {
						for(entry=rif_cache_lists[listidx].dlist[TR_USED].head;
						    entry;entry=entry->dentry[TR_USED].next) {
							rifcnt++;
							if(entry->rcf==0) {
								rifstr="local";
							}
							else {
								local_entry=entry;
								rifstr=rifbuff;
								sprintfbridgeinfo(rifbuff,(tr_rif_info *)&entry->rcf);
							}
							trproc_printf(done2,"%s %02X:%02X:%02X:%02X:%02X:%02X %8li %8li    %s\n",
								      dev->name,entry->addr[0],entry->addr[1],entry->addr[2],
								      entry->addr[3],entry->addr[4],entry->addr[5],
								      (now-entry->dentry[TR_USED].timestamp)/HZ,
								      (now-entry->dentry[TR_UPDATED].timestamp)/HZ,rifstr);
						}
					}
				}
				else {
					trproc_printf(done2,"%s currently has no rif_entries\n",dev->name);
				}

				trproc_printf(done2,"\nstats for %s\n"
					      "rifcnt=%d rif mem used %ldk\n",
					      dev->name,rifcnt,
					      (long)(rif_cache_lists ? (TR_RIF_CACHE_LIST_SIZE*sizeof(tr_rif_cache_lists)
									+((rifcnt*sizeof(tr_rif_cache_entry))))>>10:0));
				if(local_entry) {
					dir=(ntohs(local_entry->rcf)&TR_RCF_DIR_BIT ? TRUE:FALSE);
					trproc_printf(done2,"local ring number=%03X\n",
						      RD_TO_RINGNUM(local_entry->rseg[dir ? 
										     0:(((ntohs(local_entry->rcf) & TR_RCF_LEN_MASK)>>9)-2)]));
				}
				lock_acquired=FALSE;
				tr_unlock(dev);
			}
			else {
				trproc_printf(done1,"\n%s is busy no info available try later\n",dev->name);
#if TR_DEBUG_LOCKS
				trproc_printf(done1,"%s",lockbuff); 
#endif
			}
		}
	}
 done2:
	if(dev&&lock_acquired)
		tr_unlock(dev);
 done1:
	read_unlock(&dev_base_lock);
	if(currlen>length) {
		/* rewind to previous printf so that we are correctly
		 * aligned if we get called to print another page.
                 */
		currlen-=splen;
	}
	return(currlen);
}
#endif

/*
 *	Called during bootup.  We don't actually have to initialise
 *	too much for this.
 */

static int __init rif_init(void)
{
	proc_net_create("tr_rif",0,tr_rif_proc_info);
	return 0;
}

module_init(rif_init);

void tr_free_rif_cache(struct net_device *dev)
{
	tr_rif_cache_lists *rif_cache_lists=dev->rif_cache_lists;
	tr_rif_cache_entry *entry;
	int                listidx;
	if(rif_cache_lists) {
		if(tr_lock(dev)) {
			for(listidx=0;listidx <TR_RIF_CACHE_LIST_SIZE;listidx++) {
				for(entry=rif_cache_lists[listidx].dlist[TR_USED].head;
				    entry;entry=entry->dentry[TR_USED].next)
					kfree(entry);
			}
			kfree(rif_cache_lists);
			dev->rif_cache_lists=NULL;
			tr_unlock(dev);
		}
		else
			printk("free rif cache failed for %s possibly busy\n",dev->name);
	}
}

