/* **********************************************************
 * Copyright (C) 1998-2000 VMware, Inc.
 * All Rights Reserved
 * **********************************************************/

#include "driver-config.h"

#ifdef KERNEL_2_1
#  define EXPORT_SYMTAB
#  ifndef __KERNEL__
#    define __KERNEL__
#  endif
#endif

#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/sched.h>
#include <linux/malloc.h>
#ifdef KERNEL_2_1
#include <linux/poll.h>
#include <asm/uaccess.h>
#endif

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/mm.h>
#include <linux/skbuff.h>
#include <linux/if_ether.h>
#include <linux/sockios.h>
#include <net/sock.h>

#define __KERNEL_SYSCALLS__
#include <asm/io.h>

#include <linux/proc_fs.h>
#include <linux/file.h>

#include "vm_oui.h"
#include "vnetInt.h"

#include "utils.h"

typedef struct VNetHubStats {
   unsigned      tx;
} VNetHubStats;

typedef struct VNetHub {
   int           num;
   VNetJack      jack[NUM_JACKS_PER_HUB];
   VNetHubStats  stats[NUM_JACKS_PER_HUB];
   int           totalPorts;
   int           myGeneration;
} VNetHub;

static void VNetHubFree(VNetJack *this);
static void VNetHubReceive(VNetJack *this, struct sk_buff *skb);
static Bool VNetHubCycleDetect(VNetJack *this, int generation);
static void VNetHubPortsChanged(VNetJack *this);
static int  VNetHubIsBridged(VNetJack *this);

#ifdef KERNEL_2_1
static int VNetHubProcRead(char *page, char **start, off_t off,
                           int count, int *eof, void *data);
#endif

static VNetHub *vnetHub[VNET_NUM_HUBS];

/*
 *----------------------------------------------------------------------
 *
 * VNetHubInit --
 *
 *      Initialize the hub module.
 * 
 * Results: 
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

void
VNetHub_Init(void)
{
   int i;
   
   for (i=0; i<VNET_NUM_HUBS; i++) {
      vnetHub[i] = NULL;
   }
}


/*
 *----------------------------------------------------------------------
 *
 * VNetHub_Alloc --
 *
 *      Allocate a jack on this hub.
 * 
 * Results: 
 *      The jack to connect to, NULL on error.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

VNetJack *
VNetHub_Alloc(int hubNum)    // IN: the hub to alloc on
{
   VNetHub *hub = vnetHub[hubNum];
   VNetJack *jack;
   int i;
   int retval;
   
   if (!hub) {
      LOG(1, (KERN_DEBUG "/dev/vmnet: hub %d does not exist, allocating memory.\n",
              hubNum));
      
      hub = (struct VNetHub *)kmalloc(sizeof(struct VNetHub), GFP_KERNEL);

      for (i=0; i<NUM_JACKS_PER_HUB; i++) {
         jack = &hub->jack[i];
         
         /*
          * The private field indicates if this jack is allocated.
          * Null means free, otherwise the jack is allocated and it
          * should point back to the hub.
          */

         jack->peer = NULL;
         jack->numPorts = 0;
         sprintf(jack->name, "hub%d.%d", hubNum, i);
         jack->private = NULL;
         jack->index = i;
         jack->procEntry = NULL;
         jack->free = VNetHubFree;
         jack->rcv = VNetHubReceive;
         jack->cycleDetect = VNetHubCycleDetect;
         jack->portsChanged = VNetHubPortsChanged;
         jack->isBridged = VNetHubIsBridged;

         memset(&hub->stats[i], 0, sizeof(VNetHubStats));
      }

      hub->num = hubNum;
      hub->totalPorts = 0;
      hub->myGeneration = 0;
      
      vnetHub[hubNum] = hub;
   }
   
   for (i=0; i<NUM_JACKS_PER_HUB; i++) {
      jack = &hub->jack[i];
      if (!jack->private) {

         /*
          * Make proc entry for this jack.
          */
         
         retval = VNetProc_MakeEntry(NULL, jack->name, S_IFREG, &jack->procEntry);
         if (retval) {
            if (retval == -ENXIO) {
               jack->procEntry = NULL;
            } else {
               return NULL;
            }
         } else {
#ifdef KERNEL_2_1
            jack->procEntry->read_proc = VNetHubProcRead;
            jack->procEntry->data = jack;
#else
            jack->procEntry = NULL;
#endif
         }

         /*
          *  OK, now allocate this jack.
          */

         jack->numPorts = hub->totalPorts;
         jack->private = hub;
         jack->peer = NULL;

         return jack;
      }
   }
   
   return NULL;
}


/*
 *----------------------------------------------------------------------
 *
 * VNetHubFree --
 *
 *      Free the jack on this hub.
 * 
 * Results: 
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

void
VNetHubFree(VNetJack *this)
{
   VNetHub *hub = (VNetHub*)this->private;
   
   if (this != &hub->jack[this->index]) {
      LOG(1, (KERN_DEBUG "/dev/vmnet: bad free of hub jack\n"));
      return;
   }
   
   if (this->procEntry) {
      VNetProc_RemoveEntry(this->procEntry, NULL);
      this->procEntry = NULL;
   }
      
   this->private = NULL;
}


/*
 *----------------------------------------------------------------------
 *
 * VNetHubReceive --
 *
 *      This jack is receiving a packet. Take appropriate action.
 * 
 * Results: 
 *      None.
 *
 * Side effects:
 *      Frees skb.
 *
 *----------------------------------------------------------------------
 */

void
VNetHubReceive(VNetJack       *this,
               struct sk_buff *skb)
{
   VNetHub *hub = (VNetHub*)this->private;
   VNetJack *jack;
   struct sk_buff *clone;
   int i;
#ifdef PACIA_TRACE   
	char * fname = "VNetHubReceive";
	char msg_buf[256];
	struct timeval tv;
	struct timeval now_tv;
	int print = 0; // whether or not to print information
	do_gettimeofday(&tv);

#ifdef VERBOSE_PACIA_TRACE
	sprintf (msg_buf, "%s called on hub %d", fname, hub->num);
	print_string (msg_buf);
#endif // VERBOSE_PACIA_TRACE
#endif
// XXX 736 stuff
#ifdef PACIA_TRACE   
	{
		__u16 src = 0, dst = 0;
		int retval;
	
		/* Set print to 1 iff this is a packet from the server to
		 * the client. */
		retval = getTcpPorts (skb, &src, &dst);
		if (retval != 0
			&& (src == DEFAULT_SERVER_PORT
				|| src == DEFAULT_CLIENT_PORT)
			&& (dst == DEFAULT_CLIENT_PORT
				|| dst == DEFAULT_SERVER_PORT)
			)
		{
			print = 1;
		}
		if (print != 0)
		{
			sprintf (msg_buf, "%s skb:", fname);
			print_string (msg_buf);

			// print the skb
			print_skb (skb);
		}
	}
#endif // PACIA_TRACE   

   hub->stats[this->index].tx++;
   
   for (i=0; i<NUM_JACKS_PER_HUB; i++) {
      jack = &hub->jack[i];
      if (jack->private         /* allocated */
          && jack->peer         /* and connected */
          && (jack != this)) {  /* and not a loop */
         clone = skb_clone(skb, GFP_ATOMIC);
         if (clone) {
            VNetSend(jack, clone);
         }
      }
   }

   DEV_KFREE_SKB(skb, FREE_WRITE);
#ifdef PACIA_TRACE   
	if (print != 0)
	{
		unsigned int foo_us;
		do_gettimeofday(&now_tv);
	  	foo_us = abs (1000000*(now_tv.tv_sec  - tv.tv_sec));
		foo_us += abs (now_tv.tv_usec - tv.tv_usec);
		sprintf (msg_buf, "%s: call took %u us", fname, foo_us);
		print_string (msg_buf);
	}
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * VNetHubCycleDetect --
 *
 *      Cycle detection algorithm.
 * 
 * Results: 
 *      TRUE if a cycle was detected, FALSE otherwise.
 *
 * Side effects:
 *      Will generate other cycleDetect events to other jacks on hub.
 *
 *----------------------------------------------------------------------
 */

Bool
VNetHubCycleDetect(VNetJack *this,
                   int       generation)
{
   VNetHub *hub = (VNetHub*)this->private;
   Bool foundCycle;
   int i;
   
   if (hub->myGeneration == generation) {
      return TRUE;
   }

   hub->myGeneration = generation;
      
   for (i=0; i<NUM_JACKS_PER_HUB; i++) {
      if (hub->jack[i].private && (i != this->index)) {
         foundCycle = VNetCycleDetect(hub->jack[i].peer, generation);
         if (foundCycle) {
            return TRUE;
         }
      }
   }

   return FALSE;
}


/*
 *----------------------------------------------------------------------
 *
 * VNetHubPortsChanged --
 *
 *      The number of ports connected to this jack has change, react
 *      accordingly.
 * 
 * Results: 
 *      None.
 *
 * Side effects:
 *      May generate other portsChanged events to other jacks on hub.
 *
 *----------------------------------------------------------------------
 */

void
VNetHubPortsChanged(VNetJack *this)
{
   VNetHub *hub = (VNetHub*)this->private;
   int num, new;
   int i;
   
   hub->totalPorts = 0;
   
   for (i=0; i<NUM_JACKS_PER_HUB; i++) {
      if (hub->jack[i].private) {
         hub->totalPorts += VNetGetAttachedPorts(&hub->jack[i]);
      }
   }
   
   for (i=0; i<NUM_JACKS_PER_HUB; i++) {
      if (hub->jack[i].private) {
         num = VNetGetAttachedPorts(&hub->jack[i]);
         new = hub->totalPorts - num;
         if (i == this->index) {
            if (new != hub->jack[i].numPorts) {
               /* basically an assert failure */
               LOG(0, (KERN_DEBUG "/dev/vmnet: numPorts mismatch.\n"));
            }
         } else {
            hub->jack[i].numPorts = new;
            VNetPortsChanged(hub->jack[i].peer);
         }
      }
   }
}


/*
 *----------------------------------------------------------------------
 *
 * VNetHubIsBridged --
 *
 *      Check whether we are bridged.
 *
 * Results:
 *      0 - not bridged
 *      1 - we are bridged but the interface is not up
 *      2 - we are bridged and the interface is up
 *      3 - some bridges are down
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

int
VNetHubIsBridged(VNetJack *this)
{
   VNetHub *hub = (VNetHub*)this->private;
   int ret = 0;
   int num;
   int i;
   
   for (i=0; i<NUM_JACKS_PER_HUB; i++) {
      if ((hub->jack[i].private) && (i != this->index)) {
         num = VNetIsBridged(&hub->jack[i]);
         ret = MAX(ret, num);
         if ((num == 1) && (ret == 2)) {
            ret = 3;
         }
      }
   }   
   
   return ret;
}


/*
 *----------------------------------------------------------------------
 *
 * VNetHubProcRead --
 *
 *      Callback for read operation on hub entry in vnets proc fs.
 *
 * Results: 
 *      Length of read operation.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */
#ifdef KERNEL_2_1
int
VNetHubProcRead(char    *page,   // IN/OUT: buffer to write into
                char   **start,  // OUT: 0 if file < 4k, else offset into page
                off_t    off,    // IN: offset of read into the file
                int      count,  // IN: maximum number of bytes to read
                int     *eof,    // OUT: TRUE if there is nothing more to read
                void    *data)   // IN: client data - not used
{
   VNetJack *jack = (VNetJack*)data;
   VNetHub *hub = (VNetHub*)jack->private;
   int len = 0;

   if (!jack || !jack->private) {
      return len;
   }

   len += VNetPrintJack(jack, page+len);

   len += sprintf(page+len, "tx %u ", hub->stats[jack->index].tx);
   
   len += sprintf(page+len, "\n");
   
   *start = 0;
   *eof   = 1;
   return len;
}
#endif
