#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/rtnetlink.h> 
#include <linux/smp_lock.h>
#include <rtl_sync.h>
#include <rtl_core.h>

#include <ip.h>
#include <nic.h>
#include <rqueue.h>


/* This will be the nic for the linux virtual driver */

static char eth_mac []= {0x00, 0x53, 0x4e, 0x55, 0x4c, 0x30};
static char virt_packet [1600];
static int sended_data = 0;

static struct rqueue packetqueue;

void nic_reset (struct nic *nic) {
  return;
}

spinlock_t sending_spin;

int nic_poll (struct nic *nic) {
  int aux = sended_data;
  spin_lock(&sending_spin);
  sended_data = 0;
  spin_unlock(&sending_spin);
  return aux;
}

void virt_net_send (unsigned char *buf, int len);
static int virt_net_irq;

static void virt_net_handler(int irq,void *ignore,struct pt_regs *ignoreregs) {
  unsigned char *buf = NULL;
  rtl_irqstate_t flags;
  int size;
 
  while (!is_empty (&packetqueue)) {
    rtl_no_interrupts (flags);
    get_ptr_head (&packetqueue, &size, &buf); 
    rtl_restore_interrupts (flags);
    virt_net_send ((unsigned char *) buf, size);
    rtl_no_interrupts (flags); 
    extract_element (&packetqueue, NULL, NULL);
    rtl_restore_interrupts (flags);
      
  }

  return;
}

void nic_transmit (struct nic *nic, const char *d,
		   unsigned int t, unsigned int s, const char *p) {
  rtl_irqstate_t flags;
  rtl_no_interrupts (flags);
  insert_element (&packetqueue, s, (char *) p, t);
  rtl_restore_interrupts (flags);
  rtl_global_pend_irq (virt_net_irq);
  return;
}

void nic_disable (struct nic *nic) {
  return ;
}

/* With RTL_UDP is easy to do new device drivers with
   the nic struct is enought */

static struct nic virt_nic = {
  reset: nic_reset,
  poll: nic_poll,
  transmit: nic_transmit,
  disable: nic_disable,
  flags: 0,
  node_addr: eth_mac,
  packet: virt_packet,
  packetlen: 0,
  priv_data: NULL
};

static char *ip_address = NULL;
static char *ip_submask = NULL;
MODULE_PARM(ip_address, "s");
MODULE_PARM(ip_submask, "s");

struct virt_net_priv {
    struct net_device_stats stats;
    int status;
    int rx_packetlen;
    u8 *rx_packetdata;
    int tx_packetlen;
    u8 *tx_packetdata;
    struct sk_buff *skb;
    spinlock_t lock;
};

static struct net_device *eth_dev = NULL;

int virt_net_open(struct net_device *dev) {
  MOD_INC_USE_COUNT;
  
  /* request_region(), request_irq(), .... (like fops->open) */
  
  /* 
   * Assign the hardware address of the board: use "\0SNULx", where
   * x is 0 or 1. The first byte is '\0' to avoid being a multicast
   * address (the first byte of multicast addrs is odd).
   */
  memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
  //    dev->dev_addr[ETH_ALEN-1] += (int)(dev); /* the number */
  
  netif_start_queue(dev);
  eth_dev = dev;

  return 0;
}

int virt_net_release(struct net_device *dev) {
  /* release ports, irq and such -- like fops->close */
  
  netif_stop_queue(dev); /* can't transmit any more */
  MOD_DEC_USE_COUNT;
  return 0;
}

extern int handle_rx (struct nic *nic);

/* The own linux kernel will send the messages */
int virt_net_tx(struct sk_buff *skb, struct net_device *dev)
{
  struct virt_net_priv *priv = (struct virt_net_priv *) dev->priv;

  virt_nic.packetlen = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;
  memcpy ((char *) virt_packet, (char *)skb->data, virt_nic.packetlen);
  dev->trans_start = jiffies; /* save the timestamp */
  
  /* Remember the skb, so we can free it at interrupt time */
  priv->skb = skb;
  
  /* actual delivery of data is device specific, and not shown here */
  eth_dev = dev;
  spin_lock(&sending_spin);
  sended_data = 1;
  spin_unlock(&sending_spin);
  priv->stats.tx_packets++;
  priv->stats.tx_bytes += virt_nic.packetlen;
  dev_kfree_skb(priv->skb);
  handle_rx (&virt_nic);

  return 0; /* Our simple device cannot fail */
}
 
void virt_net_rx(struct net_device *dev, int len, unsigned char *buf)
{
    struct sk_buff *skb;
    struct virt_net_priv *priv = (struct virt_net_priv *) dev->priv;

    /*
     * The packet has been retrieved from the transmission
     * medium. Build an skb around it, so upper layers can handle it
     */
    skb = dev_alloc_skb (len+2);
    if (!skb) {
        printk("virt_net rx: low on mem - packet dropped\n");
        priv->stats.rx_dropped++;
        return;
    }
    memcpy(skb_put(skb, len), buf, len);

    /* Write metadata, and then pass to the receive level */
    skb->dev = dev;
    skb->protocol = eth_type_trans(skb, dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += len;
    netif_rx(skb);
    return;
}


void virt_net_send (unsigned char *buf, int len) {
  struct virt_net_priv *priv;
  if(eth_dev == NULL) return; 
  priv = eth_dev -> priv;
  spin_lock (&priv ->lock);
  virt_net_rx (eth_dev, len, buf);
  netif_wake_queue (eth_dev);
  spin_unlock (&priv->lock);
}

int virt_net_header(struct sk_buff *skb, struct net_device *dev,
                unsigned short type, void *daddr, void *saddr,
                unsigned int len)
{
  struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);

  eth->h_proto = htons(type);
  memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
  memcpy(eth->h_dest,   daddr ? daddr : dev->dev_addr, dev->addr_len);
  eth->h_dest[ETH_ALEN-1]   ^= 0x01;   /* dest is us xor 1 */
  return (dev->hard_header_len);
}

struct net_device_stats *virt_net_stats(struct net_device *dev)
{
    struct virt_net_priv *priv = (struct virt_net_priv *) dev->priv;
    return &priv->stats;
}

int virt_net_init(struct net_device *dev) {

  ether_setup(dev); /* assign some of the fields */
  
  dev->open            = virt_net_open;
  dev->stop            = virt_net_release;
  dev->hard_start_xmit = virt_net_tx;
  dev->get_stats       = virt_net_stats;
  dev->hard_header     = virt_net_header;

  /* keep the default flags, just add NOARP */
  dev->flags           |= IFF_NOARP;
  dev->hard_header_cache = NULL;      /* Disable caching */
  SET_MODULE_OWNER(dev);

  dev->priv = kmalloc(sizeof(struct virt_net_priv), GFP_KERNEL);

  if (dev->priv == NULL)
    return -ENOMEM;
  memset(dev->priv, 0, sizeof(struct virt_net_priv));
  spin_lock_init(& ((struct virt_net_priv *) dev->priv)->lock);
  eth_dev = dev;
  return 0;
}

static struct net_device virt_net_dev = {
  init: virt_net_init  /* init, nothing more */
};

MODULE_AUTHOR("Miguel Masmano Tello <mmasmano@disca.upv.es>");
MODULE_DESCRIPTION("Virtual Linux net driver");

MODULE_LICENSE("GPL");

static int v_nic_pos = 0;

int init_module (void) {
  int result;
  char ip_address2 [] = "192.168.4.2";
  char ip_submask2 [] = "255.255.255.0";
  char eth_mac2 []= {0x00, 0x53, 0x4e, 0x55, 0x4c, 0x31};
  
  init_queue (&packetqueue, eth_mac, eth_mac2);
  spin_lock_init (&sended_spin);
  
  strcpy (virt_net_dev.name, "eth%d");
  
  if (ip_address == NULL) ip_address = ip_address2;
  if (ip_submask == NULL) ip_submask = ip_submask2;

  if ((result = register_netdev(&virt_net_dev))) {
    printk("virt-net: error [%d] registering device \"%s\"\n",
	   result, virt_net_dev.name);
    return -1;
  }
  
  printk("virt-net: device \"%s\" has been registered successfully\n",
	virt_net_dev.name);

  virt_net_irq = rtl_get_soft_irq(virt_net_handler,"Virtual Net IRQ");
  if (virt_net_irq < 0) {
    printk("couldn't get a soft irq for virt net\n");
    return -1;
  }
  v_nic_pos = rtl_register_net_driver (&virt_nic, 
				       "Virtual linux device driver",
		  		       ip_address, ip_submask, 0);
  if (v_nic_pos == -1) {
    kfree(virt_net_dev.priv);  
    rtnl_lock ();
    unregister_netdevice(&virt_net_dev);
    rtnl_unlock ();
    return -1;
  }

  return 0;
}

void cleanup_module (void) {
  rtl_unregister_net_driver (v_nic_pos);
  if (virt_net_irq)
    rtl_free_soft_irq (virt_net_irq);
  kfree(virt_net_dev.priv);
  rtnl_lock ();
  unregister_netdevice(&virt_net_dev);
  rtnl_unlock ();
  return;
}
