/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe 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.                                       *
*                                                                             *
*   Nprobe is distributed in the hope that it will be useful,                 *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
*   GNU General Public License for more details.                              *
*                                                                             *
*   You should have received a copy of the GNU General Public License         *
*   along with Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/


#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"

static volatile int hint;

/* SOS22: Playing with this gives interesting performance
   effects... */
/* This macro is grossly misnamed: it gets called after every update
   to a tous or frus index, and is passed the address of the index,
   and is supposed to make the update more visible to other
   processors, but it really isn't a cache flush. */
#if 1
#define flush_cache_line(x)                     \
asm volatile ("sfence\n")
#else
#define flush_cache_line(x)
#endif

/***************************************************************************
  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. 
 *
 *      Called (mostly) from the receive ISR.
 */

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[0].tous_in, np->x[0].tous_out);
#endif



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

  chan = _chan_hash( skb ); 


  // XXX IAP rem this out because serial console output is polled when in IRQ!!
#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...

  SOS22: What it suggests to me is that someone calls us with interrupts
  still enabled, and if we then get an interrutp firing while we
  hold the lock we deadlock with interrupts off.
  */

  //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;
      mb();
      np->x[chan].tous_in++;
      flush_cache_line(&np->x[chan].tous_in);
    }
  else
    {
      printk(KERN_CRIT
	     "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 (!spin_is_locked(&np->x[chan].lock)) {
    printk(KERN_EMERG "get_skb called without lock.\n");
    BUG();
  }

  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

      mb();
      np->x[chan].frus_out++;
      flush_cache_line(&np->x[chan].frus_out);

      _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(KERN_ERR "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 0
  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

  /* XXX SOS22: Why is this not irqsave? */
  spin_lock( &np->x[chan].lock );

  new_skb = get_skb(chan);

  if( ! new_skb )
    {
      int i;
      
      /* XXX SOS22: Is there a dining philosophers problem here? */
      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;
      mb();
      np->x[chan].tous_in++;
      flush_cache_line(&np->x[chan].tous_in);
    }
  else
    {
      printk(KERN_CRIT
	     "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;
}


EXPORT_SYMBOL(NP_netif_rx);
EXPORT_SYMBOL(NP_dev_kfree_skb);
EXPORT_SYMBOL(NP_dev_alloc_skb);

