#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/etherdevice.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;

/***************************************************************************
  Ethernet / FDDI 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 + 16;  // skb_reserve(skb,16);
  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 )
{
  return dev_alloc_skb( ETH_BUFFER_SIZE );
}


/*
 *	Receive a packet from a device driver and queue it for the upper
 *	(protocol) levels.  It always succeeds. 
 */

void NP_netif_rx(struct sk_buff *skb)
{

  int chan;
  unsigned long flags;

#if 0
if( np->x[ABC].tous_in % 64 == 0 )
  {
    int i;
    for(i=0;i<FIFO_SIZE;i++)
	  {
	      printk("%d = %p %p\n",i,np->x[ABC].tous_skb[i],np->x[ABC].frus_skb[i] );
	  }  
  }
#endif

#if 0
  printk("NPROBE: skb %p : %p (%d) tous %d,%d \n",
	 skb,skb->data,skb->len,np->x[ABC].tous_in, np->x[ABC].tous_out);
#endif



  //skb->stamp = xtime;	// XXX use RPCC for greater accuracy
  do_gettimeofday(&skb->stamp);

  chan = _chan_hash( skb ); 

#if 0
  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


  /* the irqsave seems to be necessary even on uniprocessor
systems. It must be something to do with each card generating an
interrupt at the same time.

  I don't understand why the spinlock doesn't work in this instance. I
  can't imagine they both take the lock, so I think one must get stuck
  in the interrupt routine or something...

  But how can they both be in the interrupt routine at the same time
  on a uni?

  without these locks at all the machine hangs even on a uni. this implies that it can be executing two interrupt handlers at the same time!  
  One irq for one card prempting another???? This would explain the hang...

  

  */

  //spin_lock( &np->x[chan].lock ); // XXX why irsave reqd?
  spin_lock_irqsave( &np->x[chan].lock, flags ); // XXX why irsave reqd?

  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( &np->x[chan].lock );
  spin_unlock_irqrestore( &np->x[chan].lock, flags );

  hint = chan;          // set hint

}


/* 
 * This is called when network card shuts down.
 * Let's do nothing, and wait for reset_fifo to `rediscover' them.  */

void NP_dev_kfree_skb( struct sk_buff * skb )
{
  return;
}

// assumes lock held
static inline struct sk_buff * get_skb( int chan )
{
  struct sk_buff *skb = NULL;

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


  if( np->x[chan].frus_in - np->x[chan].frus_out > 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 ); 	
    }  


  return skb;
}

struct sk_buff * NP_dev_alloc_skb(unsigned int length)
{
  struct sk_buff *skb = NULL;
  int i, start;
  unsigned long flags;

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

  i = start;

  do
    {

      //spin_lock( &np->x[i].lock ); // XXXX why irqsave ????
      spin_lock_irqsave( &np->x[i].lock, flags ); // XXXX why irqsave ????

      skb = get_skb(i);

      //spin_unlock( &np->x[i].lock );
      spin_unlock_irqrestore( &np->x[i].lock, flags );

      if (skb) return skb;

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

  //      printk("NPROBE: dev_alloc - nuke packet\n");
  // ifconfig stats will tell us about drops

  np->alloc_failed_cnt++;

  return NULL;
}


struct sk_buff *  NP_alloc_and_pass_up(struct sk_buff * recv_skb,
				       unsigned int length )
{
  struct sk_buff * new_skb;


  int chan;

#if 0
  printk("NPROBE: skb %p : %p (%d) tous %d,%d \n",
	 skb,skb->data,skb->len,np->x[ABC].tous_in, np->x[ABC].tous_out);
#endif


  //recv_skb->stamp = xtime;	// XXX use RPCC for greater accuracy
  do_gettimeofday(&recv_skb->stamp);

  chan = _chan_hash( recv_skb ); 

#if 1
  if(np->x[chan].tous_in % (8192*8) == 0 )
    printk("NPROBE: [%d] alloc_and_up %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( &np->x[chan].lock );

  new_skb = get_skb(chan);

  if( ! new_skb )
    {
      int i;
      
      for(i=(chan+1) % US_CHANNELS; i!=chan; i = (i+1) % US_CHANNELS )
	{
	  spin_lock( &np->x[i].lock );
	  new_skb = get_skb(i);
	  spin_unlock( &np->x[i].lock );	  

	  if ( new_skb ) goto got_one;
	}

      // if we got here, we couldn't get a new skb
      // refresh the old one and return

      np->alloc_failed_cnt++;

      _reset_skb(recv_skb);

      spin_unlock( &np->x[chan].lock );      
      return recv_skb;
    }

got_one:
  // we got a new skb, pass the old one up

  if( np->x[chan].tous_in - np->x[chan].tous_out < FIFO_SIZE )
    {
      np->tous_skb[chan][ np->x[chan].tous_in % FIFO_SIZE ] = recv_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.
      // We're probably going to go down. hard.

      // kfree_skb(skb);

    }

  spin_unlock( &np->x[chan].lock );
  return new_skb;
}



















