/*
    NetDevQueue.c, Copyright  (c) by Boris Lutz,
    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/NetDevQueue.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $  by: $Author: baderma $
 
*/
#include "Support.h"
#include "Syscall.h"
#include "NetSyscall.h"
#include "Topsy.h"
#include "NetQueue.h"
#include "NetDevQueue.h"
#include "NetDevice.h"
#include "NetInQueueMngr.h"
#include "NetDebug.h"

void netDevQueue(ThreadArg arg)
{
  NetQueue			myQueue;
  NetDevice			device = (NetDevice) arg;
  Address				sndBuf,
  rcvBuf;
  Message				myMsg,
  msgToInQueue;
  NetBuf				data,
  ext,
  rcvData;
  NetAttrs			rcvMeta;
  ThreadId			from,
  devThreadId,
  selfId,
  parentId;
  unsigned int	wrtLen,
  rcvLen;
  short					writeIsPending, devType;

  if (tmGetInfo(SELF, &selfId, &parentId) != TM_INFOOK)
  {
    printf ("DevQueue: PANIC failed to get thread info\n");
    tmExit();
  }
  /* get the device ThreadId and set the DevQueue ThreadId to our ID */
  devThreadId = device->device;
  device->queue = selfId;
  if (ioSubscribe(devThreadId, selfId) != IO_SUBSCRIBEOK)
  {
    printf ("DevQueue: PANIC failed to subscribe to IO-Device\n");
    tmExit();
  }

  if ( (myQueue = netQueueNew(TYPE_DGRAM)) == NULL )
  {
    printf ("DevQueue: PANIC failed to allocate queue\n");
    tmExit();
  }

  writeIsPending	= FALSE;
  /* XXX this is not the omptimal way of doing it. Normaly
   * XXX the NetBuf should be filled and emptied directly and
   * XXX not via a linear buffer. But to achive this iovec are needed.
   * XXX CJ-2001 */
  if (netAlloc(&rcvBuf, MRU_SIZE) != VM_ALLOCOK)
  {		// XXX
    printf ("DevQueue: PANIC failed to allocate memory\n");
    tmExit();
  }
  if (netAlloc(&sndBuf, MTU_SIZE) != VM_ALLOCOK)
  {		// XXX
    printf ("DevQueue: PANIC failed to allocate memory\n");
    tmExit();
  }
  if ( (devType = netdevGetType(device->type)) == NETATTR_TYPE_UNKOWN )
  {
    printf ("DevQueue: PANIC unknown device type\n");
    tmExit();
  }

  netdbgPrintf(NETDEBUG_DEVICES, "Outqueue for %s started\n", device->name);
  while (1)
  {
    from = ANY;
    if (tmMsgRecv(&from, ANYMSGTYPE, &myMsg, INFINITY) != TM_MSGRECVOK)
    {
      printf ("DevQueue: failed to receive\n");
      continue;
    }
    data = myMsg.msg.netqMsg.netData;
    ext = myMsg.msg.netqMsg.netMeta;
    switch (myMsg.id)
    {
    case NET_QUEUE_DATA:
      netdbgPrintf(NETDEBUG_DEVICES, "%s: got NET_QUEUE_DATA\n",
                   device->name);
      if (writeIsPending)
      {
        if (netQueueAdd (myQueue, data, ext) != NETQ_OK)
        {
          printf ("DevQueue: failed queue up packet\n");
          continue;
        }
        netdbgPrintf(NETDEBUG_DEVICES, "%s: data queued\n",
                     device->name);
      }
      else
      {
        /*					netdbgPrintf(NETDEBUG_DEVICES,
        							"%s Queue: recieved data: (buf %p Attr %p)\n", 
        							device->name, data, ext);
        					netdbgDumpBuf(NETDEBUG_DEVICES, data);
        					netdbgPrintf(NETDEBUG_DEVICES, "**********************\n");
        */
        wrtLen = netbufLen(data);
        if (wrtLen > MTU_SIZE )
        {		// XXX
          printf ("DevQueue: datalength is longer than MTU\n");
          continue;
        }
        if (netbufRead(&data, sndBuf, &wrtLen, 0, SEEK_SET) != 1)
        {
          printf ("DevQueue: failed to read from netBuf\n");
          continue;
        }
        netbufFree(data);
        netbufFree(ext);
        netdbgPrintf(NETDEBUG_DEVICES, "%s: data written len %i\n",
                     device->name, wrtLen);
        netAsyncIOWrite(devThreadId, sndBuf, wrtLen);
        writeIsPending = TRUE;
      }
      break;
    case IO_WRITEREPLY:
      netdbgPrintf(NETDEBUG_DEVICES, "%s: got IO_WRITEREPLY\n",
                   device->name);
      writeIsPending = FALSE;
      if ( myMsg.msg.ioWriteReply.errorCode != IO_WRITEOK ||
           myMsg.msg.ioWriteReply.bytesWritten != wrtLen )
      {
        printf ("DevQueue: io-write call returned error\n");
        continue;
      }
      if (netQueueIsEmpty (myQueue) )
      {
        continue;
      }
      else
      {
        if (netQueueGet(myQueue, &data, &ext) != NETQ_OK)
        {
          printf ("DevQueue: failed to get queued data\n");
          continue;
        }
        wrtLen = netbufLen(data);
        if (wrtLen > MTU_SIZE )
        {		// XXX
          printf ("DevQueue: datalength is longer than MTU\n");
          continue;
        }
        if (netbufRead(&data, sndBuf, &wrtLen, 0, SEEK_SET) != 1)
        {
          printf ("DevQueue: failed to read from netBuf\n");
          continue;
        }
        netbufFree(data);
        netbufFree(ext);
        netdbgPrintf(NETDEBUG_DEVICES, "%s Queue: data written len %i\n",
                     device->name, wrtLen);
        netAsyncIOWrite(devThreadId, sndBuf, wrtLen);
        writeIsPending = TRUE;
      }
      break;
    case IO_NOTIFY:
      netdbgPrintf(NETDEBUG_DEVICES, "%s Queue: got IO_NOTIFY\n",
                   device->name);
      /* XXX the ioNotify message contains the available
         amount of data. This can be used to allocate a correct 
         sized NetBuf and then via a readv ... dreams I know */
      rcvLen = MRU_SIZE;	// XXX MRU_SIZE XXX
      if (ioRead(devThreadId, rcvBuf, &rcvLen) != IO_READOK)
      {
        printf ("DevQueue: failed to read from device\n");
        continue;
      }

      rcvData = NULL;
      if (netbufWrite(&rcvData, rcvBuf, rcvLen, 0, SEEK_SET) != 1 )
      {
        printf ("DevQueue: failed to write to netBuf\n");
        continue;
      }
      msgToInQueue.id = NET_QUEUE_DATA;
      msgToInQueue.msg.netqMsg.netData = rcvData;
      if (! netattrNew(&rcvMeta ) )
      {
        printf ("DevQueue: failed to get new Attributes\n");
        netbufFree(rcvData);
      }
      msgToInQueue.msg.netqMsg.netMeta = rcvMeta;
      if (!(	netattrSet(rcvMeta, NETATTR_IN_TYPE, devType)
             && 	netattrSet(rcvMeta, NETATTR_IF_FROM, device->index)
           ))
      {
        printf ("DevQueue: failed to set device type. Ignored\n");
      }

      netdbgPrintf(NETDEBUG_DEVICES, "%s Queue: sending to InQueue %i\n", device->name, netInqueueThread);

      if (tmMsgSend (netInqueueThread, &msgToInQueue) != TM_MSGSENDOK)
      {
        printf ("DevQueue: failed to send to InQueue\n");
        continue;
      }
      break;
    default:
      printf ("DevQueue: wrong message type\n");
      continue;
    }
  }
}


void netAsyncIOWrite(ThreadId ioDev, void* data, long len)
{
  Message		ioMsg;


  ioMsg.id = IO_WRITE;
  ioMsg.msg.ioWrite.buffer = data;
  ioMsg.msg.ioWrite.size = len;

  if (tmMsgSend(ioDev, &ioMsg) != TM_MSGSENDOK)
  {
    printf ("DevQueue: failed to send data to device\n");
  }
}
