/*
 *	Routines having to do with the 'sk_buff' memory handlers.
 *
 *	Authors:	Alan Cox <iialan@iifeak.swan.ac.uk>
 *			Florian La Roche <rzsfl@rz.uni-sb.de>
 *
 *	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.
 *
 *	Fixes:
 *		Alan Cox	:	Fixed the worst of the load balancer bugs.
 */

/*
 *	Note: There are a load of cli()/sti() pairs protecting the net_memory type
 *	variables. Without them for some reason the ++/-- operators do not come out
 *	atomic. Also with gcc 2.4.5 these counts can come out wrong anyway - use 2.5.8!!
 */

#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include "ip.h"
#include "protocol.h"
#include <linux/string.h>
#include "route.h"
#include "tcp.h"
#include "udp.h"
#include <linux/skbuff.h>
#include "sock.h"

#define SKB_FORWARDING_SLACK	32	/* Plenty for all current jobs bar AX.25 */

/*
 *	Resource tracking variables
 */

volatile unsigned long net_memory = 0;
volatile unsigned long net_skbcount = 0;
volatile unsigned long net_locked = 0;
volatile unsigned long net_allocs = 0;
volatile unsigned long net_fails  = 0;
volatile unsigned long net_free_locked = 0;

void show_net_buffers(void)
{
	printk("Networking buffers in use          : %lu\n",net_skbcount);
	printk("Memory committed to network buffers: %lu\n",net_memory);
	printk("Network buffers locked by drivers  : %lu\n",net_locked);
	printk("Total network buffer allocations   : %lu\n",net_allocs);
	printk("Total failed network buffer allocs : %lu\n",net_fails);
	printk("Total free while locked events     : %lu\n",net_free_locked);
}

#if CONFIG_SKB_CHECK

/*
 *	Debugging paranoia. Can go later when this crud stack works
 */

int __skb_check(sk_buff *skb, int head, int line, char *file)
{
 
	if (head) {	
		if (skb->magic_debug_cookie != SK_HEAD_SKB) {
			printk("File: %s Line %d, found a bad skb-head (%x)\n",
				file,line, skb->magic_debug_cookie);
			return -1;
		}
		if (!skb->next || !skb->prev) {
			printk("skb_check: head without next or prev\n");
			return -1;
		}
		if (skb->next->magic_debug_cookie != SK_HEAD_SKB
			&& skb->next->magic_debug_cookie != SK_GOOD_SKB) {
			printk("File: %s Line %d, bad next head-skb member\n",
				file,line);
			return -1;
		}
		if (skb->prev->magic_debug_cookie != SK_HEAD_SKB
			&& skb->prev->magic_debug_cookie != SK_GOOD_SKB) {
			printk("File: %s Line %d, bad prev head-skb member\n",
				file,line);
			return -1;
		}
#if 0
		{
		sk_buff *skb2 = skb->next;
		int i = 0;
		while (skb2 != skb && i < 5) {
			if (skb_check(skb2, 0, line, file) < 0) {
				printk("bad queue element in whole queue\n");
				return -1;
			}
			i++;
			skb2 = skb2->next;
		}
		}
#endif
		return 0;
	}
	if (skb->next != NULL && skb->next->magic_debug_cookie != SK_HEAD_SKB
		&& skb->next->magic_debug_cookie != SK_GOOD_SKB) {
		printk("File: %s Line %d, bad next skb member (%p,%x)\n",
			file,line, skb->next, skb->next->magic_debug_cookie);
		return -1;
	}
	if (skb->prev != NULL && skb->prev->magic_debug_cookie != SK_HEAD_SKB
		&& skb->prev->magic_debug_cookie != SK_GOOD_SKB) {
		printk("File: %s Line %d, bad prev skb member\n",
			file,line);
		return -1;
	}


	if(skb->magic_debug_cookie==SK_FREED_SKB)
	{
		printk("File: %s Line %d, found a freed skb lurking in the undergrowth!\n",
			file,line);
		printk("skb=%p, claimed size=%ld, free=%d\n",
			skb,skb->mem_len,skb->free);
		return -1;
	}
	if(skb->magic_debug_cookie!=SK_GOOD_SKB)
	{
		printk("File: %s Line %d, passed a non skb!\n", file,line);
		printk("skb=%p, claimed size=%ld, free=%d\n",
			skb,skb->mem_len,skb->free);
		return -1;
	}
	if(skb->start < (unsigned char *)(skb+1) || skb->head>skb->tail || skb->start > skb->head || skb->tail > skb->end)
	{
		printk("File: %s Line %d, order fault.\n", file,line);
		printk("skb=%p, start=%p head=%p tail=%p end=%p\n",
			skb,skb->start,skb->head,skb->tail,skb->end);
	}
	if(skb->len != skb->tail-skb->head)
	{
		printk("File: %s Line %d, bad length %ld (should be %d).\n", file,line, skb->len, skb->tail-skb->head);
		printk("skb=%p, start=%p head=%p tail=%p end=%p\n",
			skb,skb->start,skb->head,skb->tail,skb->end);
	}
	/* Guess it might be acceptable then */
	return 0;
}

int skb_check(sk_buff *skb, int head, int line, char *file)
{
	if(__skb_check(skb,head,line,file)==-1)
	{
		volatile unsigned long l;
		for(l=0;l<100000;l++);
		return -1;
	}
	return 0;
}

#endif


void skb_queue_head_init(sk_buff_head *list)
{
	list->prev = (sk_buff *)list;
	list->next = (sk_buff *)list;
#if CONFIG_SKB_CHECK
	list->magic_debug_cookie = SK_HEAD_SKB;
#endif
	IS_SKB_HEAD(list);
}


/*
 *	Insert an sk_buff at the start of a list.
 */

void skb_queue_head(sk_buff_head *list_,sk_buff *newsk)
{
	unsigned long flags;
	sk_buff *list = (sk_buff *)list_;

	save_flags(flags);
	cli();

#if CONFIG_SKB_CHECK
	IS_SKB(newsk);
	IS_SKB_HEAD(list);
	if (newsk->next || newsk->prev)
		printk("Suspicious queue head: sk_buff on list!\n");
#endif

	newsk->next = list->next;
	newsk->prev = list;

	newsk->next->prev = newsk;
	newsk->prev->next = newsk;

	IS_SKB(newsk);	
	restore_flags(flags);
}

/*
 *	Insert an sk_buff at the end of a list.
 */

void skb_queue_tail(sk_buff_head *list_, sk_buff *newsk)
{
	unsigned long flags;
	sk_buff *list = (sk_buff *)list_;

	save_flags(flags);
	cli();

#if CONFIG_SKB_CHECK
	if (newsk->next || newsk->prev)
		printk("Suspicious queue tail: sk_buff on list!\n");
	IS_SKB(newsk);
	IS_SKB_HEAD(list);
#endif

	newsk->next = list;
	newsk->prev = list->prev;

	newsk->next->prev = newsk;
	newsk->prev->next = newsk;

	IS_SKB(newsk);
	IS_SKB_HEAD(list);
	
	restore_flags(flags);
}

/*
 *	Remove an sk_buff from a list. This routine is also interrupt safe
 *	so you can grab read and free buffers as another process adds them.
 */

sk_buff *skb_dequeue(sk_buff_head *list_)
{
	long flags;
	sk_buff *result;
	sk_buff *list = (sk_buff *)list_;

	save_flags(flags);
	cli();

	IS_SKB_HEAD(list);

	result = list->next;
	if (result == list) {
		restore_flags(flags);
		return NULL;
	}

	result->next->prev = list;
	list->next = result->next;

	result->next = NULL;
	result->prev = NULL;

	restore_flags(flags);

	IS_SKB(result);	
	IS_SKB_HEAD(list);
	return result;
}

/*
 *	Insert a packet before another one in a list.
 */

void skb_insert(sk_buff *old, sk_buff *newsk)
{
	unsigned long flags;

#if CONFIG_SKB_CHECK
	IS_SKB(old);
	IS_SKB(newsk);

	if(!old->next || !old->prev)
		printk("insert before unlisted item!\n");
	if(newsk->next || newsk->prev)
		printk("inserted item is already on a list.\n");
#endif

	save_flags(flags);
	cli();
	newsk->next = old;
	newsk->prev = old->prev;
	old->prev = newsk;
	newsk->prev->next = newsk;
	
	IS_SKB(old);
	IS_SKB(newsk);
	restore_flags(flags);
}

/*
 *	Place a packet after a given packet in a list.
 */
 
void skb_append(sk_buff *old, sk_buff *newsk)
{
	unsigned long flags;

#if CONFIG_SKB_CHECK
	IS_SKB(old);
	IS_SKB(newsk);

	if(!old->next || !old->prev)
		printk("append before unlisted item!\n");
	if(newsk->next || newsk->prev)
		printk("append item is already on a list.\n");
#endif

	save_flags(flags);
	cli();

	newsk->prev = old;
	newsk->next = old->next;
	newsk->next->prev = newsk;
	old->next = newsk;

	IS_SKB(newsk);
	IS_SKB(old);
	
	restore_flags(flags);
}

/*
 *	Remove an sk_buff from its list. Works even without knowing the list it
 *	is sitting on, which can be handy at times. It also means that THE LIST
 *	MUST EXIST when you unlink. Thus a list must have its contents unlinked
 *	_FIRST_.
 */
 
void skb_unlink(sk_buff *skb)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	IS_SKB(skb);

	if(skb->prev && skb->next)
	{
		skb->next->prev = skb->prev;
		skb->prev->next = skb->next;
		skb->next = NULL;
		skb->prev = NULL;
	}
#ifdef PARANOID_BUGHUNT_MODE	/* This is legal but we sometimes want to watch it */
	else
		printk("skb_unlink: not a linked element\n");
#endif
	restore_flags(flags);
}

/*
 *	Free an sk_buff. This still knows about things it should
 *	not need to like protocols and sockets.
 */

void kfree_skb(sk_buff *skb, int rw)
{
	if (skb == NULL)
	{
		printk("kfree_skb: skb = NULL (from %p)\n",
			__builtin_return_address(0));
		return;
  	}
	IS_SKB(skb);
	if (skb->lock)
	{
		skb->free = 3;    /* Free when unlocked */
		net_free_locked++;
		return;
  	}
  	if (skb->free == 2)
		printk("Warning: kfree_skb passed an skb that nobody set the free flag on! (from %p)\n",
			__builtin_return_address(0));
	if (skb->next)
	 	printk("Warning: kfree_skb passed an skb still on a list (from %p).\n",
			__builtin_return_address(0));
	if (skb->sk)
	{
		if (rw)
			skb->sk->rmem_alloc-=skb->mem_len;
		else
			skb->sk->wmem_alloc-=skb->mem_len;
		if(!skb->sk->dead)
			skb->sk->write_space(skb->sk);
		kfree_skbmem(skb,skb->mem_len);
	}
	else
		kfree_skbmem(skb, skb->mem_len);
}

/*
 *	Allocate a new skbuff. We do this ourselves so we can fill in a few 'private'
 *	fields and also do memory statistics to find all the [BEEP] leaks.
 */
 
 sk_buff *alloc_skb(unsigned int size,int priority)
 {
 	sk_buff *skb;
 	unsigned long flags;
 	int memsize=size+sizeof(sk_buff)+4;
 	
 	size+=4;	/* Hack for lance.c for now.. soon to go */
 		
 	if (intr_count && priority!=GFP_ATOMIC) {
		static int count = 0;
		if (++count < 5) {
			printk("alloc_skb called nonatomically from interrupt %p\n",
				__builtin_return_address(0));
			priority = GFP_ATOMIC;
		}
 	}
 	
 	skb=(sk_buff *)kmalloc(memsize,priority);
 	if (skb == NULL)
 	{
 		net_fails++;
 		return NULL;
 	}
#ifdef PARANOID_BUGHUNT_MODE
	if(skb->magic_debug_cookie == SK_GOOD_SKB)
		printk("Kernel kmalloc handed us an existing skb (%p)\n",skb);
#endif		 	

	net_allocs++;
	
 	skb->free = 2;	/* Invalid so we pick up forgetful users */
	skb->lock = 0;
	skb->pkt_type = PACKET_HOST;	/* Default type */
 	skb->mem_len = memsize;
#ifdef CONFIG_SLAVE_BALANCING 	
 	skb->in_dev_queue = 0;
#endif 	
	skb->prev = skb->next = NULL;
	skb->link3 = NULL;
	skb->sk = NULL;
	skb->dev = NULL;
	skb->localroute=0;
	skb->stamp.tv_sec=0;	/* No idea about time */
	skb->localroute = 0;
	skb->len=0;
	skb->start=(unsigned char *)(skb+1);
	skb->end=skb->start+size;
	skb->head=skb->start;
	skb->tail=skb->start;
	skb->deferred=0;
	skb->protnum=0;
	skb->priority=SOPRI_NORMAL;
	skb->lower_head=skb->head;
	save_flags(flags);
	cli();
 	net_memory += memsize;
 	net_skbcount++;
 	restore_flags(flags);
#if CONFIG_SKB_CHECK
	skb->magic_debug_cookie = SK_GOOD_SKB;
#endif
 	skb->users = 0;
 	
 	IS_SKB(skb);
 	
 	return skb;
}

/*
 *	Free an skbuff by memory
 */ 	

void kfree_skbmem(sk_buff *skb,unsigned size)
{
	unsigned long flags;
#ifdef CONFIG_SLAVE_BALANCING
	save_flags(flags);
	cli();
	if(skb->in_dev_queue && skb->dev!=NULL)
		skb->dev->pkt_queue--;
	restore_flags(flags);
#endif	
	IS_SKB(skb);
	if(size!=skb->mem_len)
		printk("kfree_skbmem: size mismatch.\n");
		
	if(skb->magic_debug_cookie == SK_GOOD_SKB)
	{
		save_flags(flags);
		cli();
		IS_SKB(skb);
		skb->magic_debug_cookie = SK_FREED_SKB;
		kfree_s((void *)skb,size);
		net_skbcount--;
		net_memory -= size;
		restore_flags(flags);
	}
	else
		printk("kfree_skbmem: bad magic cookie\n");
}

/*
 *	Duplicate an sk_buff. The new one is not owned by a socket or locked
 *	and will be freed on deletion.
 */

sk_buff *skb_clone(sk_buff *skb, int priority)
{
	sk_buff *n;
	unsigned long offset;
	
	IS_SKB(skb);
	
	n=alloc_skb(skb->mem_len-sizeof(sk_buff),priority);
	if(n==NULL)
		return NULL;
		
	offset=((char *)n)-((char *)skb);
		
	n->head=skb->head+offset;
	n->tail=skb->tail+offset;
	n->lower_head=skb->lower_head+offset;
	memcpy(n->start,skb->start,skb->mem_len-sizeof(sk_buff));
	n->len=skb->len;
	n->link3=NULL;
	n->sk=NULL;
	n->when=skb->when;
	n->dev=skb->dev;
	n->h.raw=skb->h.raw+offset;
	n->ip_hdr=(struct iphdr *)(((char *)skb->ip_hdr)+offset);
	n->saddr=skb->saddr;
	n->daddr=skb->daddr;
	n->raddr=skb->raddr;
	n->acked=skb->acked;
	n->used=skb->used;
	n->free=1;
	n->tries=0;
	n->lock=0;
	n->users=0;
	n->deferred=skb->deferred;
	n->pkt_type=skb->pkt_type;	
	IS_SKB(n);
	IS_SKB(skb);
	return n;
}


/*
 *     Skbuff device locking
 */

void skb_device_lock(sk_buff *skb)
{
	IS_SKB(skb);
	if(skb->lock)
		printk("double lock on device queue!\n");
	else
		net_locked++;
	skb->lock++;
}

void skb_device_unlock(sk_buff *skb)
{
	IS_SKB(skb);
	if(skb->lock==0)
		printk("double unlock on device queue!\n");
	skb->lock--;
	if(skb->lock==0)
		net_locked--;
}

/*
 *	Free a device send buffer
 */

void dev_kfree_skb(sk_buff *skb, int mode)
{
	unsigned long flags;

	IS_SKB(skb);
	save_flags(flags);
	cli();
	if(skb->lock==1)
		net_locked--;

	if (!--skb->lock && (skb->free == 1 || skb->free == 3))
	{
		restore_flags(flags);
		kfree_skb(skb,mode);
	}
	else
		restore_flags(flags);
}

/*
 *	Allocate a device receive buffer.
 */
 
sk_buff *dev_alloc_skb(int len)
{
	sk_buff *skb=alloc_skb(len+SKB_FORWARDING_SLACK, GFP_ATOMIC);
	if(skb==NULL)
		return NULL;
	skb_reserve(skb, SKB_FORWARDING_SLACK);
	IS_SKB(skb);
	return skb;
}


/*
 *	Check if an sk_buff is still locked.
 */
 
int skb_device_locked(sk_buff *skb)
{
	IS_SKB(skb);
	return skb->lock? 1 : 0;
}


/*
 *	sk_buff data management functions. These are intentionally simple
 *	because they have to be very fast and they have to be correct. When
 *	I'm 100% happy with things most of these will become inline.
 */
 
/*
 *	Trim a buffer to a chosen length.
 */
 
void skb_trim(sk_buff *skb, int len)
{
	IS_SKB(skb);
	if(skb->len < len)
		return;
	skb->tail-= skb->len-len;
	skb->len=len;
	IS_SKB(skb);
}

/*
 *	Add data to the beginning of an sk_buff
 */
 
unsigned char *skb_push(sk_buff *skb, int len)
{
	IS_SKB(skb);
	if(skb->head - skb->start < len)
	{
		printk("skb_push: underrun.\n");
		printk("skb_push: (%d %d %p %p).\n",
			skb->head-skb->start,len, skb->start,skb->head);
		return NULL;
	}
	skb->head-=len;
	skb->len+=len;
	IS_SKB(skb);
	return skb->head;
}

/*
 *	Add data to the end of an sk_buff
 */
 
unsigned char *skb_put(sk_buff *skb, int len)
{
	unsigned char *t=skb->tail;
	IS_SKB(skb);
	if(t + len >skb->end)
	{
		printk("skb_put: overrun (%d %d %p %p).\n",len,skb->end - t, t+len,skb->end);
		return NULL;
	}
	skb->tail+=len;
	skb->len+=len;
	IS_SKB(skb);
	return t;
}

/*
 *	Pull data from the start of an skbuff.
 */
 
unsigned char *skb_pull(sk_buff *skb, int len, int *size)
{
	char *t=skb->head;
	IS_SKB(skb);
	if(len>skb->len)
		len=skb->len;
	skb->head+=len;
	skb->len-=len;
	if(size)
		*size=len;
	IS_SKB(skb);
	return t;
}

/*
 *	Get the data pointer into an skbuff but don't move
 */
 
unsigned char *skb_data(sk_buff *skb)
{
	IS_SKB(skb);
	return skb->head;
}

/*
 *	Reserve header space on an sk_buff
 */
 
int skb_reserve(sk_buff *skb, int len)
{
	IS_SKB(skb);
	if(skb->len)
	{
		printk("skb_reserve called for skb with data!\n");
		return -1;
	}
	if(skb->head+len > skb->end)
	{
		printk("skb_reserve: overrun.\n");
		return -1;
	}
	skb->head+=len;
	skb->tail+=len;
	IS_SKB(skb);
	return 0;
}

