/*
  IODevice.c, Copyright  (c) by George Fankhauser,
  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/IO/IODevice.c,v $
 	Author(s):             George Fankhauser
 	Affiliation:           ETH Zuerich, TIK
 	Version:               $Revision: 3042 $
 	Last Date of Change:   $Date: 2007-06-28 01:28:34 +0200 (Don, 28 Jun 2007) $      by: $Author: sryffel $
*/

#include "IODevice.h"
#include "Messages.h"
#include "Syscall.h"
#include "Threads.h"
#include "MMMapping.h"
#include "Limits.h"
#include "HashList.h"
#include "TMThread.h"
#include "vm_extern.h"



/* This function introduces an artificial delay of at least nbLoops (actually
 * almost three times more) to cope with timing problems between cpu and devices
 */
int ioDelayAtLeastCycles( int nbLoops)
{
  int i = 0, k = 0;

  for (i=0; i<nbLoops; i++)
    k++;
  return k;
}

static Boolean ioCheckBufferAddress(ThreadId id, Address address,
                                    unsigned long size)
{
  Address spaceFrom;
  unsigned long spaceSize;
  Thread*		threadPtr;

  if ( (threadPtr = getTHbyId(id)) == NULL )
  {
    return TM_THREADNOTFOUND;
  }
  if (THREAD_PERMISSION(threadPtr) == KERNEL)
    return TRUE;

  /* buffer must be in user space (at least). otherwise users could
   * access kernel memory.
   *
   * an exact test would require the address to be inside a vmRegion,
   * but this would be quite an expensive test
   */
  mmAddressSpaceRange(USER, &spaceFrom, &spaceSize);
  if (address < spaceFrom || (unsigned long)address + size >
      (unsigned long)spaceFrom + spaceSize
      || ULONG_MAX - (unsigned long)address < size)
    return FALSE;
  else
    return TRUE;
}

/* IO device main procedure.
 * Every device driver loops here and passes messages to its 
 * driver implementation.
 * 
 * If a driver handles special messages (other than read/write/close/init)
 * they are forwarded to its handleMsg function.
 */
void ioDeviceMain(IODevice this)
{

  Message msg, reply;
  ThreadId	expectedFrom;
  
  printf("ioDeviceMain started %li:\"%s\"  maj %i  min %i\n", this->threadId, this->name, this->major, this->minor); 

  int ret = 0;

  while (TRUE)
  {
    expectedFrom = ANY;
    CLEAR_MSGENV(reply);
    tmMsgRecv(&expectedFrom, ANYMSGTYPE, &msg, INFINITY);
    switch (msg.id)
    {
    case IO_READ:
      //-CJ				printf("IODriver[%li]: got IO_READ from %li\n", msg.to, msg.from);
      //printf("(%li) got read \n", msg.to);
      reply.id = IO_READREPLY;
      // attach a page to msg (don't keep, do not init PP)
      if (attachEnvMsg(&reply, 0) != 0)
      {
        reply.msg.ioReadReply.errorCode = IO_READFAILED;
        tmMsgSend(msg.from, &reply);
      }
      ret = this->read(this, msg.from, reply.enveloppe,
                       &(msg.msg.ioRead.size));
      if (ret != IO_LATER)
      {
        //printf("  -> read reply '%c' to (%li)\n", 
        //  (msg.msg.ioRead.size>0 ? reply.enveloppe : ' '), reply.to);
        reply.msg.ioReadReply.errorCode = IO_READOK;
        reply.msg.ioReadReply.bytesRead = msg.msg.ioRead.size;
        tmMsgSend(msg.from, &reply);
      }
      break;
    case IO_WRITE:
      //-CJ				printf("IODriver[%li]: got IO_WRITE from %li size %li\n",
      //-CJ						msg.to, msg.from, msg.msg.ioWrite.size);
      //-CJ				dumpMemory(msg.enveloppe, 0, 64);
      // printf("(%li) got write \n", msg.to);
      reply.id = IO_WRITEREPLY;
      ret = this->write(this, msg.from, msg.enveloppe,
                        &(msg.msg.ioWrite.size));
      if (ret != IO_LATER)
      {
        // printf("  -> write reply to (%li)\n", reply.to);
        reply.msg.ioWriteReply.errorCode = IO_WRITEOK;
        reply.msg.ioWriteReply.bytesWritten = msg.msg.ioWrite.size;
      }
      else
      {
        reply.msg.ioWriteReply.errorCode = IO_WRITEFAILED;
        reply.msg.ioWriteReply.bytesWritten = 0;
      }
      detachEnvMsg(&msg);
      tmMsgSend(msg.from, &reply);
      break;
    case IO_DRIVERCLOSE:
      /* check sender: we do not accept others than ioThread */
      if (msg.from != IOTHREADID)
      {
        reply.id = UNKNOWN_SYSCALL;
        tmMsgSend(msg.from, &reply);
        break;
      }
      reply.id = IO_CLOSEREPLY;
      if (this->close != NULL)
      {
        ret = this->close(this);
        reply.msg.ioCloseReply.errorCode = ret;
      }
      else
      {
        reply.msg.ioCloseReply.errorCode = IO_CLOSEFAILED;
      }
      tmMsgSend(msg.msg.ioClose.deviceThreadId, &reply);
      break;
    case IO_INIT:
      //-CJ				printf("IODriver[%li]: got IO_INIT from %li\n", msg.to, msg.from);
      reply.id = IO_INITREPLY;
      if (this->init != NULL)
      {
        ret = this->init(this);
        reply.msg.ioInitReply.errorCode = ret;
      }
      else
      {
        reply.msg.ioInitReply.errorCode = IO_INITFAILED;
      }
      tmMsgSend(msg.from, &reply);
      break;
    default:
      if (this->handleMsg != NULL)
      {
        this->handleMsg(this, &msg);
      }
      else
      {
        reply.id = UNKNOWN_SYSCALL;
        tmMsgSend(msg.from, &reply);
      }
    }
  }
}
