/*
  IOMain.c, Copyright  (c) by George Fankhauser, Claudio Jeker
  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/IOMain.c,v $
 	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 "IO.h"
#include "IOMain.h"
#include "IODevice.h"
#include "Syscall.h"
#include "Threads.h"
#include "Exception.h"
#include "IODevTable.h"
#include "Support.h"
#include "TMThread.h"


static Error ioLoadDriver(unsigned short major, unsigned short minor,
                          IODevice dev, int copy);
static void ioLoadDeviceTable(void);

/* The table has been moved to the architecture depending files */

static HashList ioDevices;
static IODevice ioDeviceList;


/* IO thread main procedure */
void ioMain(ThreadArg arg)
{

  Error error;
  ThreadId t, mid = 0;
  ThreadId expectedFrom;
  Message msg, reply, driverMsg;
  IODevice dev;
  Thread*		threadPtr;
  unsigned short	*deviceMajor;
  size_t		len;


  ioDevices = hashListNew();
  ioDeviceList = NULL;

  /* due to the centralized interrupt handling in certain UARTs we
   * need to know each other
   */
#ifdef IO_SERIAL_B
  /* a hack for Architectures where no IO_SERIAL_A and IO_SERIAL_B is defined */
  /* XXX FIX ME, PLEASE !!!! */ /* Fixed CJ-2001/01/10 */ /* Thanks */
  //ioDeviceTable[IO_SERIAL_A].extension = &(ioDeviceTable[IO_SERIAL_B]);
#endif

  ioLoadDeviceTable();

  while (TRUE)
  {
    expectedFrom = ANY;
    CLEAR_MSGENV(reply);
    CLEAR_MSGENV(driverMsg);
    tmMsgRecv(&expectedFrom, ANYMSGTYPE, &msg, INFINITY);
    // printf("ioThread: recv from %2li  &msg   %p  msg.id   %i\n", msg.from, &msg, msg.id);
    
    switch (msg.id)
    {
    case IO_OPEN:
      SETMAJOR(mid, (unsigned short) msg.msg.ioOpen.deviceMajor);
      SETMINOR(mid, (unsigned short) msg.msg.ioOpen.deviceMinor);

      error = hashListGet(ioDevices, (void**)(&dev), mid );
      reply.id = IO_OPENREPLY;
      if (error == HASHNOTFOUND)
      {
        reply.msg.ioOpenReply.errorCode = IO_OPENFAILED;
      }
      else
      {
        t = dev->threadId;
        dev->count++;
        reply.msg.ioOpenReply.errorCode = IO_OPENOK;
      }
      reply.msg.ioOpenReply.deviceThreadId = t;
      tmMsgSend(msg.from, &reply);
      break;
    case IO_CLOSE:
      /* msg is forwarded to driver thread or fails if id is wrong */
      driverMsg.id = IO_DRIVERCLOSE;
      driverMsg.msg.ioClose.deviceThreadId = msg.from;
      error = tmMsgSend(msg.msg.ioClose.deviceThreadId, &driverMsg);
      if (error != TM_MSGSENDOK)
      {
        reply.id = IO_CLOSEREPLY;
        reply.msg.ioCloseReply.errorCode = IO_CLOSEFAILED;
        tmMsgSend(msg.from, &reply);
      }
      /* drivers actions: send reply to syscaller, clean up */
      break;
    case IO_LOAD:
      reply.id = IO_LOADREPLY;
      if ( (threadPtr = getTHbyId(msg.from)) == NULL)
      {
        reply.msg.ioLoadReply.errorCode = IO_LOADFAILED;
        reply.msg.ioLoadReply.deviceMinor = 0;
        detachEnvMsg(&msg);
        tmMsgSend(msg.from, &reply);
        break;
      }
      if (THREAD_PERMISSION(threadPtr) == KERNEL)
      {
        /* has to be a Kernel thread */
        len = popEnvMsg(&msg, (address_t*)&deviceMajor);
        if (len != sizeof(unsigned short) )
        {
          reply.msg.ioLoadReply.errorCode = IO_LOADFAILED;
          reply.msg.ioLoadReply.deviceMinor = 0;
          detachEnvMsg(&msg);
          tmMsgSend(msg.from, &reply);
          break;
        }
        len = popEnvMsg(&msg, (address_t*)&dev);
        if (len != sizeof(IODeviceDesc) )
        {
          reply.msg.ioLoadReply.errorCode = IO_LOADFAILED;
          reply.msg.ioLoadReply.deviceMinor = 0;
          detachEnvMsg(&msg);
          tmMsgSend(msg.from, &reply);
          break;
        }
        SETMAJOR(mid, *deviceMajor);
        SETMINOR(mid, 0);
        hashListGet(ioDevices, (void**)(&t), mid );
        error = ioLoadDriver(GETMAJOR(mid), GETMINOR(t+1), dev, 1);
        reply.msg.ioLoadReply.errorCode = error;
        reply.msg.ioLoadReply.deviceMinor = GETMINOR(t+1);
      }
      else
      {
        reply.msg.ioLoadReply.errorCode = IO_LOADFAILED;
        reply.msg.ioLoadReply.deviceMinor = 0;
      }
      detachEnvMsg(&msg);
      tmMsgSend(msg.from, &reply);
      break;
    case UNKNOWN_SYSCALL:
      /* possible reply on garbage IO_CLOSE forward */
      break;
    default:
      /* return error */
      reply.id = UNKNOWN_SYSCALL;
      printf("ioThread: got UNKNOWN message %i  &msg=%p\n", msg.id, &msg);
      WARNING("unknown message");
      tmMsgSend(msg.from, &reply);
    }
  }
}

void ioRemoveDriver(IODevice this)
{
  unsigned long t = 0;

  SETMAJOR(t, this->major);
  SETMINOR(t, this->minor);

  hashListRemove(ioDevices, t ); /* this function may fail */

  /* unlink the IODeviceDesc */
  this->prev->next = this->next;
  this->next->prev = this->prev;

}

static Error ioLoadDriver(unsigned short major, unsigned short minor,
                          IODevice dev, int copy)
{
  IODevice cdev;
  ThreadId t, m = 0;
  
  // printf("ioLoadDriver: major %i  minor %i  dev %p  copy %i\n", major, minor, dev, copy);

  if ( copy == 1 )
  {
    /* we copy the deviceDesc so the caller can use his
       structure for a later call again */
    if ( hmAlloc((Address*) &cdev, sizeof(IODeviceDesc)+MAXNAMESIZE ) !=
         HM_ALLOCOK )
      return IO_LOADFAILED;
    byteCopy( cdev, dev, sizeof(IODeviceDesc) );
    cdev->name = (char *)(&cdev+sizeof(IODeviceDesc));
    /* name is stored at the end of the IODeviceDesc */
    if (dev->name)
      str_ncpy( cdev->name, dev->name, MAXNAMESIZE);
    else
      str_ncpy( cdev->name, "no name", MAXNAMESIZE);
  }
  else
  {
    cdev = dev;
  }

  SETMAJOR(m, major);
  SETMINOR(m, minor);

  /* main function is for all devices the same */
  printf(" %s key %#lx .", cdev->name, m);

  /* first init the device */
  if (cdev->init != NULL)
  {
    cdev->init( cdev );
  }
  printf(".");

  if (cdev->interruptHandler != NULL && (cdev->interrupt > 0) )
  {
    tmSetInterruptHandler(cdev->interrupt, cdev->interruptHandler,cdev);
  }
  printf(".");

  cdev->major = major;
  cdev->minor = minor;
  cdev->count = 0;

  printf("before tmStart in IO\n");
  if (tmStart( &t, (ThreadMainFunction)ioDeviceMain,
               cdev, cdev->name) != TM_STARTOK)
  {
    ERROR("Driver could not be started\n");
  }
  printf(".");
  /* save thread id for handy access to kSend from interrupt */
  cdev->threadId = t;

  // printf("&cdev->major %p  cdev->major %i\n", &cdev->major, cdev->major);

  /* track the numbers to be able to open drivers later */
  if ( hashListAdd(ioDevices, (void*)cdev, m ) == HASHDUPLICATEKEY )
  {
    printf(" ABORT: device %hd %hd already loaded\n", major, minor);
    return IO_LOADFAILED;
  }
  SETMINOR(m, 0); /* add a major, 0 device for ioLoad, ioNew */
  hashListRemove(ioDevices, m ); /* this function may fail */
  hashListAdd(ioDevices, (void*)(long)minor, m ); /* this function may fail */
  printf(".");

  /* link new device into the ioDeviceList */
  if ( ioDeviceList == NULL )
  {
    ioDeviceList = cdev;
    ioDeviceList->next = NULL;
    ioDeviceList->prev = NULL;
  }
  else
  {
    cdev->next = ioDeviceList;
    cdev->prev = ioDeviceList->prev; /* NULL */
    ioDeviceList->prev = cdev;
  }
  printf(" done\n");
  return IO_LOADOK;
}

static void ioLoadDeviceTable(void)
{
  int i = 0, err;

  printf("loading Devices ...\n");
  for ( i = 0 ; ioDeviceTable[i].dev ; i++ )
  {
    err = ioLoadDriver(ioDeviceTable[i].major, ioDeviceTable[i].minor,
                 ioDeviceTable[i].dev, ioDeviceTable[i].copy);
    if (err != IO_LOADOK)
      printf("  failed to load Device: major %i  minor %i  &dev %i  copy %i\n", 
            ioDeviceTable[i].major, ioDeviceTable[i].minor, ioDeviceTable[i].dev, ioDeviceTable[i].copy);
  }
  printf("done\n");
}
