#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/ip.h>
#include <linux/inet.h>
#include <net/checksum.h>

#include <asm/processor.h>
#include <asm/uaccess.h>
#include <asm/system.h>

#include <net/profile.h>

#ifdef CONFIG_NET_PROFILE

atomic_t net_profile_active;
struct timeval net_profile_adjust;

NET_PROFILE_DEFINE(total);

struct net_profile_slot *net_profile_chain = &net_prof_total;

#ifdef __alpha__
__u32 alpha_lo;
long alpha_hi;

static void alpha_tick(unsigned long);

static struct timer_list alpha_timer = TIMER_INITIALIZER(alpha_tick, 0, 0);

void alpha_tick(unsigned long dummy)
{
	struct timeval dummy_stamp;
	net_profile_stamp(&dummy_stamp);
	alpha_timer.expires = jiffies + 4*HZ;
	add_timer(&alpha_timer);
}

#endif

void net_profile_irq_adjust(struct timeval *entered, struct timeval* leaved)
{
	struct net_profile_slot *s;

	net_profile_sub(entered, leaved);
	for (s = net_profile_chain; s; s = s->next) {
		if (s->active)
			net_profile_add(leaved, &s->irq);
	}
}


#ifdef CONFIG_PROC_FS
static int profile_read_proc(char *buffer, char **start, off_t offset,
			     int length, int *eof, void *data)
{
	off_t pos=0;
	off_t begin=0;
	int len=0;
	struct net_profile_slot *s;

	len+= sprintf(buffer, "Slot            Hits       Hi         Lo         OnIrqHi    OnIrqLo    Ufl\n");

	if (offset == 0) {
		cli();
		net_prof_total.active = 1;
		atomic_inc(&net_profile_active);
		NET_PROFILE_LEAVE(total);
		sti();
	}
	for (s = net_profile_chain; s; s = s->next) {
		struct net_profile_slot tmp;

		cli();
		tmp = *s;

		/* Wrong, but pretty close to truth */

		s->accumulator.tv_sec = 0;
		s->accumulator.tv_usec = 0;
		s->irq.tv_sec = 0;
		s->irq.tv_usec = 0;
		s->hits = 0;
		s->underflow = 0;
		/* Repair active count, it is possible, only if code has a bug */
		if (s->active) {
			s->active = 0;
			atomic_dec(&net_profile_active);
		}
		sti();

		net_profile_sub(&tmp.irq, &tmp.accumulator);

		len += sprintf(buffer+len,"%-15s %-10d %-10ld %-10lu %-10lu %-10lu %d/%d",
			       tmp.id,
			       tmp.hits,
			       tmp.accumulator.tv_sec,
			       tmp.accumulator.tv_usec,
			       tmp.irq.tv_sec,
			       tmp.irq.tv_usec,
			       tmp.underflow, tmp.active);

			buffer[len++]='\n';
		
			pos=begin+len;
			if(pos<offset) {
				len=0;
				begin=pos;
			}
			if(pos>offset+length)
				goto done;
	}
	*eof = 1;

done:
	*start=buffer+(offset-begin);
	len-=(offset-begin);
	if(len>length)
		len=length;
	if (len < 0)
		len = 0;
	if (offset == 0) {
		cli();
		net_prof_total.active = 0;
		net_prof_total.hits = 0;
		net_profile_stamp(&net_prof_total.entered);
		sti();
	}
	return len;
}
#endif

struct iphdr whitehole_iph;
int whitehole_count;

static int whitehole_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct net_device_stats *stats;

	stats = (struct net_device_stats *)dev->priv;
	stats->tx_packets++;
	stats->tx_bytes+=skb->len;

	dev_kfree_skb(skb);
	return 0;
}

static void whitehole_inject(unsigned long);
int whitehole_init(struct net_device *dev);

static struct timer_list whitehole_timer =
		TIMER_INITIALIZER(whitehole_inject, 0, 0);

static struct net_device whitehole_dev = {
	.name	= "whitehole",
	.init	= whitehole_init,
};

static int whitehole_open(struct net_device *dev)
{
	whitehole_count = 100000;
	whitehole_timer.expires = jiffies + 5*HZ;
	add_timer(&whitehole_timer);
	return 0;
}

static int whitehole_close(struct net_device *dev)
{
	del_timer(&whitehole_timer);
	return 0;
}

static void whitehole_inject(unsigned long dummy)
{
	struct net_device_stats *stats = (struct net_device_stats *)whitehole_dev.priv;
	extern int netdev_dropping;

	do {
		struct iphdr *iph;
		struct sk_buff *skb = alloc_skb(128, GFP_ATOMIC);
		if (!skb)
			break;
		skb_reserve(skb, 32);
		iph = (struct iphdr*)skb_put(skb, sizeof(*iph));
		skb->mac.raw = ((u8*)iph) - 14;
		memcpy(iph, &whitehole_iph, sizeof(*iph));
		skb->protocol = __constant_htons(ETH_P_IP);
		skb->dev = &whitehole_dev;
		skb->pkt_type = PACKET_HOST;
		stats->rx_packets++;
		stats->rx_bytes += skb->len;
		netif_rx(skb);
		whitehole_count--;
	} while (netdev_dropping == 0 && whitehole_count>0);
	if (whitehole_count > 0) {
		whitehole_timer.expires = jiffies + 1;
		add_timer(&whitehole_timer);
	}
}

static struct net_device_stats *whitehole_get_stats(struct net_device *dev)
{
	struct net_device_stats *stats = (struct net_device_stats *) dev->priv;
	return stats;
}

int __init whitehole_init(struct net_device *dev)
{
	dev->priv = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL);
	if (dev->priv == NULL)
		return -ENOBUFS;
	memset(dev->priv, 0, sizeof(struct net_device_stats));
	dev->get_stats	= whitehole_get_stats;
	dev->hard_start_xmit = whitehole_xmit;
	dev->open = whitehole_open;
	dev->stop = whitehole_close;
	ether_setup(dev);
	dev->tx_queue_len = 0;
	dev->flags |= IFF_NOARP;
	dev->flags &= ~(IFF_BROADCAST|IFF_MULTICAST);
	dev->iflink = 0;
	whitehole_iph.ihl = 5;
	whitehole_iph.version = 4;
	whitehole_iph.ttl = 2;
	whitehole_iph.saddr = in_aton("193.233.7.21");
	whitehole_iph.daddr = in_aton("193.233.7.10");
	whitehole_iph.tot_len = htons(20);
	whitehole_iph.check = ip_compute_csum((void *)&whitehole_iph, 20);
	return 0;
}

int net_profile_register(struct net_profile_slot *slot)
{
	cli();
	slot->next = net_profile_chain;
	net_profile_chain = slot;
	sti();
	return 0;
}

int net_profile_unregister(struct net_profile_slot *slot)
{
	struct net_profile_slot **sp, *s;

	for (sp = &net_profile_chain; (s = *sp) != NULL; sp = &s->next) {
		if (s == slot) {
			cli();
			*sp = s->next;
			sti();
			return 0;
		}
	}
	return -ESRCH;
}


int __init net_profile_init(void)
{
	int i;

#ifdef CONFIG_PROC_FS
	create_proc_read_entry("net/profile", 0, 0, profile_read_proc, NULL);
#endif

	register_netdevice(&whitehole_dev);

	printk("Evaluating net profiler cost ...");
#ifdef __alpha__
	alpha_tick(0);
#endif
	for (i=0; i<1024; i++) {
		NET_PROFILE_ENTER(total);
		NET_PROFILE_LEAVE(total);
	}
	if (net_prof_total.accumulator.tv_sec) {
		printk(" too high!\n");
	} else {
		net_profile_adjust.tv_usec = net_prof_total.accumulator.tv_usec>>10;
		printk("%ld units\n", net_profile_adjust.tv_usec);
	}
	net_prof_total.hits = 0;
	net_profile_stamp(&net_prof_total.entered);
	return 0;
}

#endif
