/*******************************************************************
 * This file is part of the Emulex Linux Device Driver for         *
 * Enterprise Fibre Channel Host Bus Adapters.                     *
 * Refer to the README file included with this package for         *
 * driver version and adapter support.                             *
 * Copyright (C) 2003 Emulex Corporation.                          *
 * www.emulex.com                                                  *
 *                                                                 *
 * This program 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.          *
 *                                                                 *
 * This program 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, a copy of which    *
 * can be found in the file COPYING included with this package.    *
 *******************************************************************/

#include <linux/version.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/in.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,4)
#include <linux/slab.h>
#else
#include <linux/malloc.h>
#endif
#include <linux/string.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/blk.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/unistd.h>
#include <linux/timex.h>
#include <linux/timer.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <asm/irq.h>
#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s))
#if LINUX_VERSION_CODE >= LinuxVersionCode(2,3,17)
#include <linux/spinlock.h>
#else
#include <asm/spinlock.h>
#endif
#include <linux/smp.h>
#include <asm/byteorder.h>
#include <asm/uaccess.h>

#ifdef MODULE
#include <linux/module.h>
//#include "lpfc.ver"
#else
extern int lpfn_probe(void);
#endif /* MODULE */

/* fcLINUXlan.c IP interface network driver */
#include "fc_os.h"
#include "fc_hw.h"
#include "fc.h"

static int lpfn_xmit(struct sk_buff *skb, NETDEVICE *dev);
static struct enet_statistics *lpfn_get_stats(NETDEVICE *dev);

extern int arp_find(unsigned char *haddr, struct sk_buff *skb);
extern int lpfc_xmit(fc_dev_ctl_t *p_dev_ctl, struct sk_buff *skb);
extern int lpfc_ioctl(int cmd, void *s);

/******************************************************************************
* Function name : lpfn_open
*
* Description   : 
* 
******************************************************************************/
static int lpfn_open(NETDEVICE *dev)
{
   fc_dev_ctl_t *p_dev_ctl;
   FC_BRD_INFO  * binfo;

   if((p_dev_ctl = (fc_dev_ctl_t *)(dev->priv)) == 0)
      return(-ENODEV);
   binfo = &BINFO;
   p_dev_ctl->device_state = OPENED;
   binfo->fc_open_count |= FC_LAN_OPEN;

   netdevice_start(dev);
   netif_start_queue(dev);
#ifdef MODULE
   MOD_INC_USE_COUNT;
#endif /* MODULE */
   return 0;
}

/******************************************************************************
* Function name : lpfn_close
*
* Description   : 
* 
******************************************************************************/
static int lpfn_close(NETDEVICE *dev)
{
   fc_dev_ctl_t *p_dev_ctl;
   FC_BRD_INFO  * binfo;

   if((p_dev_ctl = (fc_dev_ctl_t *)(dev->priv)) == 0)
      return(-ENODEV);
   binfo = &BINFO;
   p_dev_ctl->device_state = 0;
   binfo->fc_open_count &= ~FC_LAN_OPEN;

   netdevice_stop(dev);
   netif_stop_queue(dev);
#ifdef MODULE
   MOD_DEC_USE_COUNT;
#endif /* MODULE */
   return 0;
}

/******************************************************************************
* Function name : lpfn_change_mtu
*
* Description   : 
* 
******************************************************************************/
int lpfn_change_mtu(NETDEVICE *dev, 
                    int        new_mtu)
{
   fc_dev_ctl_t *p_dev_ctl;

   if((p_dev_ctl = (fc_dev_ctl_t *)(dev->priv)) == 0)
      return(-ENODEV);
   if ((new_mtu < FC_MIN_MTU) || (new_mtu > p_dev_ctl->ihs.lpfn_mtu))
      return -EINVAL;
   dev->mtu = new_mtu;
   return(0);
}


/******************************************************************************
* Function name : lpfn_header
*
* Description   : Create the FC MAC/LLC/SNAP header for an arbitrary protocol 
*                 layer
*              saddr=NULL   means use device source address
*              daddr=NULL   means leave destination address (eg unresolved arp)
* 
******************************************************************************/
int lpfn_header(struct sk_buff *skb, 
                NETDEVICE      *dev, 
                unsigned short  type,
            void           *daddr, 
                void           *saddr, 
                unsigned        len)
{
   struct fc_nethdr *fchdr;
   int rc;
   
   if (type == ETH_P_IP || type == ETH_P_ARP) {
      fchdr = (struct fc_nethdr *)skb_push(skb, sizeof(struct fc_nethdr));

      fchdr->llc.dsap = FC_LLC_DSAP;  /* DSAP                      */
      fchdr->llc.ssap = FC_LLC_SSAP;  /* SSAP                      */
      fchdr->llc.ctrl = FC_LLC_CTRL;  /* control field             */
      fchdr->llc.prot_id[0] = 0;    /* protocol id             */
      fchdr->llc.prot_id[1] = 0;    /* protocol id             */
      fchdr->llc.prot_id[2] = 0;    /* protocol id             */
      fchdr->llc.type = htons(type);  /* type field                */
      rc = sizeof(struct fc_nethdr);
   }
   else {
printk("lpfn_header:Not IP or ARP: %x\n",type);

      fchdr = (struct fc_nethdr *)skb_push(skb, sizeof(struct fc_nethdr));
      rc = sizeof(struct fc_nethdr);
   }

   /* Set the source and destination hardware addresses */
   if (saddr != NULL)
      memcpy(fchdr->fcnet.fc_srcname.IEEE, saddr, dev->addr_len);
   else
      memcpy(fchdr->fcnet.fc_srcname.IEEE, dev->dev_addr, dev->addr_len);

   fchdr->fcnet.fc_srcname.nameType = NAME_IEEE;       /* IEEE name */
   fchdr->fcnet.fc_srcname.IEEEextMsn = 0;
   fchdr->fcnet.fc_srcname.IEEEextLsb = 0;


   if (daddr != NULL)
   {
    memcpy(fchdr->fcnet.fc_destname.IEEE, daddr, dev->addr_len);
        fchdr->fcnet.fc_destname.nameType = NAME_IEEE;       /* IEEE name */
        fchdr->fcnet.fc_destname.IEEEextMsn = 0;
        fchdr->fcnet.fc_destname.IEEEextLsb = 0;
    return(rc);
   }

   return(-rc);
}

/******************************************************************************
* Function name : lpfn_rebuild_header
*
* Description   : Rebuild the FC MAC/LLC/SNAP header. 
*                 This is called after an ARP (or in future other address 
*                 resolution) has completed on this sk_buff.  
*                 We now let ARP fill in the other fields.
******************************************************************************/
int lpfn_rebuild_header(struct sk_buff   *skb)
{
   struct fc_nethdr *fchdr = (struct fc_nethdr *)skb->data;
   NETDEVICE *dev = skb->dev;

   if (fchdr->llc.type == htons(ETH_P_IP)) {
        return arp_find(fchdr->fcnet.fc_destname.IEEE, skb);
   }

   printk("%s: unable to resolve type %X addresses.\n", 
          dev->name, (int)fchdr->llc.type);
    
   memcpy(fchdr->fcnet.fc_srcname.IEEE, dev->dev_addr, dev->addr_len);
   fchdr->fcnet.fc_srcname.nameType = NAME_IEEE;       /* IEEE name */
   fchdr->fcnet.fc_srcname.IEEEextMsn = 0;
   fchdr->fcnet.fc_srcname.IEEEextLsb = 0;

   return 0;
}

/******************************************************************************
* Function name : lpfn_xmit
*
* Description   : 
* 
******************************************************************************/
static int lpfn_xmit(struct sk_buff *skb, 
                     NETDEVICE      *dev)
{
   fc_dev_ctl_t *p_dev_ctl;
   int rc;


   p_dev_ctl = (fc_dev_ctl_t *)dev->priv;
   rc=lpfc_xmit(p_dev_ctl, skb);
   return rc;
}

/******************************************************************************
* Function name : lpfn_receive
*
* Description   : 
* 
******************************************************************************/
_static_ void lpfn_receive(ndd_t          *p_ndd, 
                           struct sk_buff *skb, 
                           void           *p)
{
   fc_dev_ctl_t *p_dev_ctl;
   NETDEVICE *dev;
   struct fc_nethdr *fchdr = (struct fc_nethdr *)skb->data;
   struct ethhdr *eth;
   unsigned short *sp;

   p_dev_ctl = (fc_dev_ctl_t *)p;
   dev = p_dev_ctl->ihs.lpfn_dev;
   skb->dev = dev;
    
   skb->mac.raw=fchdr->fcnet.fc_destname.IEEE;
   sp = (unsigned short *)fchdr->fcnet.fc_srcname.IEEE;
   *(sp - 1) = *sp;
   sp++;
   *(sp - 1) = *sp;
   sp++;
   *(sp - 1) = *sp;

   skb_pull(skb, dev->hard_header_len);
   eth= skb->mac.ethernet;
    
   if(*eth->h_dest&1) {
      if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0)
         skb->pkt_type=PACKET_BROADCAST;
      else
         skb->pkt_type=PACKET_MULTICAST;
   }
    
   else if(dev->flags&(IFF_PROMISC)) {
      if(memcmp(eth->h_dest,dev->dev_addr, ETH_ALEN))
         skb->pkt_type=PACKET_OTHERHOST;
   }
    
   skb->protocol = fchdr->llc.type;

   if (skb->protocol == ntohs(ETH_P_ARP))
      skb->data[1] = 0x06;


   netif_rx(skb);
}

/******************************************************************************
* Function name : lpfn_get_stats
*
* Description   : 
* 
******************************************************************************/
static struct enet_statistics *lpfn_get_stats(NETDEVICE *dev)
{
      fc_dev_ctl_t *p_dev_ctl;
   struct enet_statistics *stats;

   p_dev_ctl = (fc_dev_ctl_t *)dev->priv;
   stats = &NDDSTAT.ndd_enet;
   return stats;
}

#ifdef MODULE
/******************************************************************************
* Function name : init_module
*
* Description   : 
* 
******************************************************************************/
int init_module(void)
#else
/******************************************************************************
* Function name : lpfn_probe
*
* Description   : 
* 
******************************************************************************/
int lpfn_probe(void)
#endif /* MODULE */
{
   struct lpfn_probe lp;

   lp.hard_start_xmit   = &lpfn_xmit;
   lp.receive       = &lpfn_receive;
   lp.get_stats     = &lpfn_get_stats;
   lp.open      = &lpfn_open;
   lp.stop      = &lpfn_close;
   lp.hard_header   = &lpfn_header;
   lp.rebuild_header    = &lpfn_rebuild_header;
   lp.change_mtu    = &lpfn_change_mtu;
   if(lpfc_ioctl(LPFN_PROBE,(void *)&lp) == 0)
      return -ENODEV;

   return 0;
}

#ifdef MODULE
/******************************************************************************
* Function name : cleanup_module
*
* Description   : 
* 
******************************************************************************/
void cleanup_module(void)
{
   lpfc_ioctl(LPFN_DETACH,0);
}
MODULE_LICENSE("GPL");
#endif /* MODULE */
