#define __NO_VERSION__   // keep linker happy

#include <linux/config.h>
#include <linux/module.h>

#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/bitops.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/tty.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/atm.h>
#include <linux/atmdev.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/if_arp.h>
#include <linux/if_slip.h>
#include <linux/init.h>

#include "probe.h"

volatile int hint;

//XXXXXXXXXXXX

//#define spin_lock(a) spin_lock_irq(a)
//#define spin_unlock(a) spin_unlock_irq(a)


/***************************************************************************
  ATM specific functions 
****************************************************************************/

inline static void _reset_skb( struct sk_buff * skb )
{
  // set skb fields as though it had just been allocated
  skb->tail = skb->data = skb->head;
  skb->end = skb->head + skb->truesize;
  skb->len = 0;             
}


inline static int _chan_hash( struct sk_buff * skb )
{
  u32 ports;

  //return (skb->data[23]&1);  // good for debug with flood ping stream

  // simple hash function that will probably work pretty well:
  // xor together the lsbs of the tcp/udp port info.

  // skip the IP header and look at tcp/udp port info
  // NB potential for unaligned access!

  ports = *(((unsigned long*)skb->data) + 5 ) ;

  // assume a Littel Endian machine

  return ( (ports>>8) ^ (ports>>24) ) % US_CHANNELS ;

}


struct sk_buff *	probe_alloc_skb( void )
{
  struct sk_buff *skb = alloc_skb( ATM_BUFFER_SIZE, GFP_KERNEL ); // XXXX GFP_ATOMIC

  if( skb ) *((unsigned long *)skb->head) = 0xdeadbeef;
  
  return skb;
}


// assumes lock already held
static inline struct sk_buff * get_skb( int chan )
{
  struct sk_buff *skb = NULL;
  unsigned long num = np->x[chan].frus_in - np->x[chan].frus_out;

  // check the return fifo. If nothing available then fail.

  if( num > 0 )
    {
      skb = np->frus_skb[chan][ np->x[chan].frus_out % FIFO_SIZE ];
      
#if 0
      if ( ( ((unsigned long)skb) & 0xf0000000 ) != 0xc0000000 )
	{
	  printk("NPROBE: READ frus %p\n",skb);
	  return NULL;
	}
      
      np->frus_skb[chan][ np->x[chan].frus_out % FIFO_SIZE ] =
	(struct sk_buff *) 0x0a5a5a5a;

#endif

      np->x[chan].frus_out++;   // XXX should really ensure ordering!!!!
	
      _reset_skb( skb ); 	

      if( num < np->alloc_low_water ) np->alloc_low_water = num;
    }  
  else
    {
      np->alloc_failed_cnt++;
      np->alloc_low_water = FIFO_SIZE;
    }


  return skb;
}

struct sk_buff * safety_alloc_skb(unsigned int size, int priority)
{
  struct sk_buff *skb;
  char *head=NULL;
  int len=0;

  skb = alloc_skb(size, priority);

  if(skb) head = skb->head;
  if( skb ) len = skb->truesize;

  printk("NPROBE: safety_alloc_skb : %p %p, len %d:%d\n",
	 skb, head, size, len);  


  return skb;
}

void safety_kfree_skb(struct sk_buff *skb)
{
  int j, len = 0;
  int one_of_ours = 0;
  char *head =NULL;


  if( skb ) len = skb->truesize;
  if( skb ) head = skb->head;

  for ( j=0; j<np->num_bufs; j++ )
    {
      if(skb == np->bufs[j])
	{
	  one_of_ours = 1;
	  break;
	}	
    }

  /*  printk("NPROBE: safety_free_skb %p %p, len %d, one_of_ours = %d\n",
	 skb, head, len, one_of_ours );
   */
  

  if( !one_of_ours )
    {
      kfree_skb( skb );
    }
  else
    {
      printk("NPROBE: INCORRECT FREE "
	     "on skb %p %p (%d), one_of_ours = %d [%d]\n",
	 skb, head, len, one_of_ours, j );
    }
    
}

/* In general, we don't free things when the driver asks us to. We
simply wait for aprobe to recover them later.  Since we need to do
something with the buffer, we'll just pass it to the user who will hopefully ignore the zero length packet */

void             AP_kfree_skb(struct sk_buff *skb)
{
  int j;
  int one_of_ours = 0;

  for ( j=0; j<np->num_bufs; j++ )
    {
      if(skb == np->bufs[j])
	{
	  one_of_ours = 1;
	  break;
	}
	
    }

  if( one_of_ours)
    printk("NPROBE: skb_free called on skb %p, one_of_ours = %d\n",
	   skb, one_of_ours );
  else
        printk("NPROBE: INCORRECT skb_free on skb %p, one_of_ours = %d\n",
	   skb, one_of_ours );

  skb->len = 0;
  AP_push( NULL, skb );
}



struct sk_buff *	AP_alloc_skb(unsigned int size, int priority)
{
  struct sk_buff *skb = NULL;
  int i, start;
  unsigned long flags;

  if ( size != ATM_BUFFER_SIZE )
    printk("NPROBE: skb_alloc requested size %d. I have %d\n",
	 size, ATM_BUFFER_SIZE );


  start = hint;      // look at hint for where to try first

  i = start;

  do
    {
      spin_lock_irqsave( &np->x[i].lock, flags );
      skb = get_skb(i);
      spin_unlock_irqrestore( &np->x[i].lock, flags );

      if (skb)
	{
	  //  *((unsigned long *)skb->head) = 0x01020304;

#ifdef PARANOID
	  // check to see whether this looks like a valid skb
          // VERY SLOW!!!
	      {
		  int j;
		  for ( j=0; j<np->num_bufs; j++ )
		  {
		      if(skb == np->bufs[j])
			{
			  return skb;
			}
		  }

		  printk("NPROBE: USER SPACE returned a duff skb! %p\n",
			 skb);
		  
		  return NULL; 
	      }

#endif

	  return skb;
	}

      i = (i+1) % US_CHANNELS ;    
    }
  while( i != start );

#if 0
  for(i=0;i<US_CHANNELS;i++)
  {
      printk("NPROBE: NO BUFFERS [%d] frus %d,%d\n",
	     i,
	     np->x[i].frus_in, np->x[i].frus_out );
  }
#endif

  // ifconfig stats will tell us about drops
  return NULL;
}


void AP_push( struct atm_vcc *vcc, struct sk_buff *skb)
{

  int chan;
  unsigned long flags;

// skb->stamp = xtime;	// XXX use RPCC for greater accuracy

// skb->stamp is already written by nicstar. We overwrite it with
// greater precision here. Warning : this does irqsave/restore !

  do_gettimeofday(&skb->stamp);

  chan = _chan_hash( skb ); 

#if 1
  if(np->x[chan].tous_in % (8192*8) == 0 )
    printk("NPROBE: [%d] dev_alloc %p tous %d,%d : frus %d,%d\n",
	   chan,
	   np->frus_skb[chan][ np->x[chan].frus_out % FIFO_SIZE ],
	   np->x[chan].tous_in, np->x[chan].tous_out, 
	   np->x[chan].frus_in, np->x[chan].frus_out );
#endif

  spin_lock_irqsave( &np->x[chan].lock, flags );

  if( np->x[chan].tous_in - np->x[chan].tous_out < FIFO_SIZE )
    {
      np->tous_skb[chan][ np->x[chan].tous_in % FIFO_SIZE ] = skb;
      np->x[chan].tous_in++;   // XXX should really ensure ordering!!!!
    }
  else
    {
      printk(
	     "NPROBE: ToUs FIFO full! - tous %d,%d : frus %d,%d \n",
	     np->x[chan].tous_in, np->x[chan].tous_out, np->x[chan].frus_in, np->x[chan].frus_out );

      // this _really_ shouldn't happen. Let's free the skbuff for the
      // hell of it, but the reality is that we're probably going to
      // go down. hard.

      kfree_skb(skb);

    }

  spin_unlock_irqrestore( &np->x[chan].lock, flags );

  hint = chan;          // set hint

}

