/*
    Copyright 2000 (c) by David Schweikert,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
	
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Net/old/Ethernet/NetEthernet.c,v $
    Author(s):             
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2936 $
    Creation Date:         
    Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $      by: $Author: baderma $
 
 
    $Log: NetEthernet.c,v $
    Revision 1.2  2000/11/15 10:12:14  ruf
    Repository Reorganisation
 
    Revision 1.1  2000/03/31 17:50:36  gfa
    Merged with /Net from several term projects
 
 
*/
#include <Topsy.h>
#include <Messages.h>
#include <Syscall.h>

#include <NetAttr.h>
#include <NetBuf.h>
#include <NetDebug.h>
#include <NetConfig.h>
#include <NetHAL.h>
#include <NetIface.h>
#include <NetModules.h>

#include <Ethernet/NetEthernet.h>

void netethRead(ThreadId id, unsigned long int length);
static void netethMain(ThreadArg arg);
static void netethUp(NetBuf buf, NetAttrs attrs);
static void netethDown(NetBuf buf, NetAttrs attrs);

/* cache of hw address of ethernet interfaces */
unsigned short neteth_hwaddr_cache[NETIF_IFCOUNT][3];
unsigned char  neteth_hwaddr_cache_set[NETIF_IFCOUNT];

void netethInit()
{
  ThreadId netethId;
  int i = 0;

  for(i=0; i<NETIF_IFCOUNT; i++)
    neteth_hwaddr_cache_set[i]=0;

  /* Start netethMain thread */
  if (tmStart(&netethId, netethMain,
              (ThreadArg)0, "neteth") !=  TM_STARTOK)
  {
    netdbgDisplay(NETDEBUG_GENERIC,
                  "Start: couldn't start ethernet thread\n");
    return;
  }
}

static void netethMain(ThreadArg arg)
{
  ThreadId myThreadId, parentThreadId;
  NetModMsg msg;

  tmGetInfo(SELF, &myThreadId, &parentThreadId);

  // Add to list of modules
  netmodAdd(NETMODULE_ETHERNET, myThreadId);

  netdbgPrintf(NETDEBUG_MODULES,
               "Ethernet module started (moduleId=%d, threadId=%d).\n",
               NETMODULE_ETHERNET, myThreadId);

  while(1)
  {
    if(!netmodMsgRecv(&msg))
      return;

    switch (msg.id)
    {
    case NETMOD_SENDUP:
      netethUp(msg.buf, msg.attrs);
      break;
    case NETMOD_SENDDOWN:
      netdbgPrintf(NETDEBUG_ETHERNET, "neteth: got NETMOD_SEND.\n");
      netethDown(msg.buf, msg.attrs);
      break;
    default:
      netdbgPrintf(NETDEBUG_GENERIC,
                   "*** ERROR: Ethernet: Unknown message (%d)!\n",
                   msg.id);
      break;
    }
  }
}

static inline int netethCacheAddr(int iface)
{
  if(!neteth_hwaddr_cache_set[iface])
  {
    /* ... if not fill the cache */
    if(!netifGetAttr(iface, NETATTR_ETH_FROM_0, neteth_hwaddr_cache[iface]))
      return 0;
    if(!netifGetAttr(iface, NETATTR_ETH_FROM_1, neteth_hwaddr_cache[iface]+1))
      return 0;
    if(!netifGetAttr(iface, NETATTR_ETH_FROM_2, neteth_hwaddr_cache[iface]+2))
      return 0;
    neteth_hwaddr_cache_set[iface]=1;

    netdbgPrintf(NETDEBUG_ETHERNET, "neteth: neteth_hwaddr_cache[iface][0]=%d\n",
                 neteth_hwaddr_cache[iface][0]);
    netdbgPrintf(NETDEBUG_ETHERNET, "neteth: neteth_hwaddr_cache[iface][1]=%d\n",
                 neteth_hwaddr_cache[iface][1]);
    netdbgPrintf(NETDEBUG_ETHERNET, "neteth: neteth_hwaddr_cache[iface][2]=%d\n",
                 neteth_hwaddr_cache[iface][2]);
  }

  return 1;
}

static void netethUp(NetBuf buf, NetAttrs attrs)
{
  NetEthernetHdr hdr;
  unsigned short iface, to[3], from[3];
  int i;

  hdr = (NetEthernetHdr) NETBUF_DATA(buf);
  netdbgPrintf(NETDEBUG_ETHERNET,
               "neteth: dst = %02x:%02x:%02x:%02x:%02x:%02x",
               hdr->dest[0],hdr->dest[1],hdr->dest[2],
               hdr->dest[3],hdr->dest[4],hdr->dest[5]);
  netdbgPrintf(NETDEBUG_ETHERNET,
               ", src = %02x:%02x:%02x:%02x:%02x:%02x",
               hdr->src[0],hdr->src[1],hdr->src[2],
               hdr->src[3],hdr->src[4],hdr->src[5]);
  netdbgPrintf(NETDEBUG_ETHERNET, ", type = %04x\n", ntohs(hdr->type));

  /* we work with shorts ... */
  to[0]  =hdr->dest[1];
  to[0] <<=8;
  to[0]  |=hdr->dest[0];
  to[1]  =hdr->dest[3];
  to[1] <<=8;
  to[1]  |=hdr->dest[2];
  to[2]  =hdr->dest[5];
  to[2] <<=8;
  to[2]  |=hdr->dest[4];
  from[0]=hdr->src[1];
  from[0]<<=8;
  from[0]|=hdr->src[0];
  from[1]=hdr->src[3];
  from[1]<<=8;
  from[1]|=hdr->src[2];
  from[2]=hdr->src[5];
  from[2]<<=8;
  from[2]|=hdr->src[4];

  /* was a broadcast? */
  for(i=0; i<3; i++)
    if(to[i]!=0xFFFF)
      break;

  if(i<3)
  {
    /* ... no (it isn't a broadcast) */
    /* check address */

    /* Get originating iface */
    netattrGet(attrs, NETATTR_IF_FROM, &iface);

    /* cache hw address of iface */
    if(!netethCacheAddr(iface))
      goto discard;

    netdbgPrintf(NETDEBUG_ETHERNET,
                 "neteth: checking address: to[0]=%d, cache[iface][0]=%d\n",
                 to[0], neteth_hwaddr_cache[iface][0]);
    netdbgPrintf(NETDEBUG_ETHERNET,
                 "neteth: checking address: to[1]=%d, cache[iface][1]=%d\n",
                 to[1], neteth_hwaddr_cache[iface][1]);
    netdbgPrintf(NETDEBUG_ETHERNET,
                 "neteth: checking address: to[2]=%d, cache[iface][2]=%d\n",
                 to[2], neteth_hwaddr_cache[iface][2]);

    /* check address */
    if((to[0] != neteth_hwaddr_cache[iface][0]) ||
        (to[1] != neteth_hwaddr_cache[iface][1]) ||
        (to[2] != neteth_hwaddr_cache[iface][2]))
      goto discard;
  }

  /* Set attributes */
  netattrSet(attrs, NETATTR_ETH_FROM_0, from[0]);
  netattrSet(attrs, NETATTR_ETH_FROM_1, from[1]);
  netattrSet(attrs, NETATTR_ETH_FROM_2, from[2]);
  netattrSet(attrs, NETATTR_ETH_TO_0, to[0]);
  netattrSet(attrs, NETATTR_ETH_TO_1, to[1]);
  netattrSet(attrs, NETATTR_ETH_TO_2, to[2]);
  netattrSet(attrs, NETATTR_ETH_TYPE, ntohs(hdr->type));

  netdbgPrintf(NETDEBUG_ETHERNET, "neteth: Packet type: %04x\n", ntohs(hdr->type));

  /* Remove header */
  buf->start += 14;

  /* Send up packet */
  netdbgDisplay(NETDEBUG_ETHERNET, "neteth: Sending up packet.\n");
  netcfgSendNextUp(NETMODULE_ETHERNET, buf, attrs);
  return;

discard:
  netdbgDisplay(NETDEBUG_ETHERNET, "neteth: Discarding upcoming packet.\n");
  netbufFree(buf);
  NETATTR_FREE(attrs);
}


/* required attributes for a downgoing packet:
 * NETATTR_ETH_TO_0
 * NETATTR_ETH_TO_1
 * NETATTR_ETH_TO_2
 * NETATTR_ETH_TYPE
 * NETATTR_IF_TO
 * */
static void netethDown(NetBuf buf, NetAttrs attrs)
{
  NetEthernetHdr hdr;
  unsigned short data,iface;

  /*netdbgDumpBuf(NETDEBUG_ETHERNET, buf);*/

  /* add ethernet header */
  netbufAddHead(&buf, sizeof(struct NetEthernetHdr_t));

  /* fill in data */
  hdr = (NetEthernetHdr) NETBUF_DATA(buf);

  /* netattrDebug(NETDEBUG_ETHERNET, "netethDown", attrs); */

  /* set destination address */
  /*
  	if(!netattrGet(attrs, NETATTR_ETH_TO_0, &data)) goto error;
  	hdr->dest[0]=data%256;
  	hdr->dest[1]=data>>8;
  	if(!netattrGet(attrs, NETATTR_ETH_TO_1, &data)) goto error;
  	hdr->dest[2]=data%256;
  	hdr->dest[3]=data>>8;
  	if(!netattrGet(attrs, NETATTR_ETH_TO_2, &data)) goto error;
  	hdr->dest[4]=data%256;
  	hdr->dest[5]=data>>8;
  */

  if(!netattrGet(attrs, NETATTR_ETH_TO_0, &data))
    goto error;
  hdr->dest[1]=data%256; 		//swapped RGA
  hdr->dest[0]=data>>8;
  if(!netattrGet(attrs, NETATTR_ETH_TO_1, &data))
    goto error;
  hdr->dest[3]=data%256;
  hdr->dest[2]=data>>8;
  if(!netattrGet(attrs, NETATTR_ETH_TO_2, &data))
    goto error;
  hdr->dest[5]=data%256;
  hdr->dest[4]=data>>8;

  /* set source address */
  if(!netattrGet(attrs, NETATTR_IF_TO, &iface))
    goto error;
  if(!netethCacheAddr(iface))
    goto error;

  hdr->src[0]=neteth_hwaddr_cache[iface][0]%256;
  hdr->src[1]=neteth_hwaddr_cache[iface][0]>>8;
  hdr->src[2]=neteth_hwaddr_cache[iface][1]%256;
  hdr->src[3]=neteth_hwaddr_cache[iface][1]>>8;
  hdr->src[4]=neteth_hwaddr_cache[iface][2]%256;
  hdr->src[5]=neteth_hwaddr_cache[iface][2]>>8;

  /* set type */
  if(!netattrGet(attrs,NETATTR_ETH_TYPE,&data))
    goto error;
  hdr->type=htons(data);

  /* send down */
  netcfgSendNextDown(NETMODULE_ETHERNET, buf, attrs);
  return;

  /* these gotos can be seen as a ugly practice, but I use them like as
     a convenient and clean way of doing exceptions handling */

error:
  netdbgDisplay(NETDEBUG_ETHERNET, "neteth: Discarding downgoing packet.\n");
  netbufFree(buf);
  netbufFree((NetBuf)attrs);
}
