#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/nodes.h>
#include <exec/resident.h>
#include <exec/errors.h>
#include <libraries/expansionbase.h>
#include <libraries/configvars.h>
#include <devices/trackdisk.h>

/*
 * Set SysBase to a local variable, that loads directly from 4 when it
 * has to be reloaded
 */
#define BASE_EXT_DECL
#define BASE_NAME (*(void **)4)
#include <inline/exec.h>
#undef PRIVATE
#define BASE_EXT_DECL
#define BASE_PAR_DECL void *ExpansionBase,
#define BASE_PAR_DECL0 void *ExpansionBase
#include <inline/expansion.h>

#include <stddef.h>

#include "device.h"

extern void scsi_cmd();

ulong func_table[];
struct init_struct data_table;
struct scsi_dev *init_routine();
/*
 * small assembly glue.. needed, because these routines have to preserve ALL
 * registers.. 
 */
extern ulong glue_init(), glue_scsi_expunge(), glue_scsi_open(), 
       glue_scsi_close(), glue_scsi_beginio(), glue_scsi_abortio();
DECL_DPRINTF;
extern void try_unit (struct scsi_dev *dev, int unit);
int expunge_unit (struct scsi_dev *dev, struct scsi_unit *unit);

ulong Init[] = {
  sizeof(struct scsi_dev),
  (ulong) func_table,
  (ulong) &data_table,
  (ulong) glue_init,
};

int scsi_open(), scsi_close(), scsi_expunge(), 
    scsi_beginio(), scsi_abortio(), null_func();

ulong func_table[] = {
  (ulong) glue_scsi_open,
  (ulong) glue_scsi_close,
  (ulong) glue_scsi_expunge,
  (ulong) null_func,
  (ulong) glue_scsi_beginio,
  (ulong) glue_scsi_abortio,
  ~0,
};

struct init_struct {
  DECLARE_BYTE(1); DECLARE_LONG(2); DECLARE_BYTE(3); DECLARE_WORD(4);
  DECLARE_WORD(5); DECLARE_LONG(6); 
  ubyte end[4];
} data_table = {
  INITBYTE( offsetof(struct Node, ln_Type), NT_DEVICE ),
  INITLONG( offsetof(struct Node, ln_Name), SCSI_NAME ),
  INITBYTE( offsetof(struct Library, lib_Flags), LIBF_SUMUSED|LIBF_CHANGED ),
  INITWORD( offsetof(struct Library, lib_Version), SCSI_VERSION ),
  INITWORD( offsetof(struct Library, lib_Revision), SCSI_REVISION ),
  INITLONG( offsetof(struct Library, lib_IdString), SCSI_IDSTRING ),
  0,0,0,0,
};  

/*
 * those glue functions just save the trashed registers on the stack and
 * call the appropriate function with the set up argument(s)
 * Pitty gas doesn't accept comments.. or I was to stupid to guess the
 * correct syntax:-))
 */
asm("
.text
	.globl _glue_init
_glue_init:
	moveml #0x7ffc,sp@-

	moveml #0x8080,sp@-
	jsr _init_routine
	addw   #8,sp

	moveml sp@+,#0x3ffe
        rts

	.globl _glue_scsi_open
_glue_scsi_open:
	moveml #0x7ffc,sp@-

	jsr _scsi_open

	moveml sp@+,#0x3ffe
	rts

	.globl _glue_scsi_close
_glue_scsi_close:
	moveml #0x7ffc,sp@-

	jsr _scsi_close

	moveml sp@+,#0x3ffe
	rts


	.globl _glue_scsi_expunge
_glue_scsi_expunge:
	moveml #0x7ffc,sp@-

	jsr _scsi_expunge

	moveml sp@+,#0x3ffe
	rts

	.globl _glue_scsi_beginio
_glue_scsi_beginio:
	moveml #0x7ffc,sp@-

	jsr _scsi_beginio

	moveml sp@+,#0x3ffe
	rts

	.globl _glue_scsi_abortio
_glue_scsi_abortio:
	moveml #0x7ffc,sp@-

	jsr _scsi_abortio

	moveml sp@+,#0x3ffe
	rts
");

struct scsi_dev *
init_routine(struct scsi_dev *dev, ulong seg_list, volatile ulong a1)
{
  struct ConfigDev *cd;
  struct MsgPort *mp;
  struct Task *tcb;
  int unit;
  struct ExpansionBase *ExpansionBase;

DPRINTF(("init ($%lx): seglist=$%lx, dev=$%lx", init_routine, seg_list, dev));

  dev->sc_seglist = seg_list;

  mp = &dev->sc_msgport;
  mp->mp_Node.ln_Type = NT_MSGPORT;
  mp->mp_SigBit = AllocSignal(-1);
  mp->mp_SigTask = FindTask(0);
  mp->mp_Flags = PA_SIGNAL;
  NewList(&mp->mp_MsgList);

  InitSemaphore(& dev->sc_semaph);
  /* 
   * This will take care, that - while we're waiting later - Init won't 
   * be interrupted by an OpenDevice call... 
   */
  ObtainSemaphore(& dev->sc_semaph);

  if (ExpansionBase = (struct ExpansionBase *)OpenLibrary("expansion.library",0))
    {
      /*
       * using FindConfigDev() instead of GetCurrentBinding() ensures,
       * that these units can be mounted without having been initialized
       * by Binddrivers.. the first mount will init the device 
       */
      cd = FindConfigDev(ExpansionBase, 0, BOARD_MANUFACT, BOARD_PROCUCT);

      if (cd)
	{
	  dev->sc_base = (ulong)cd->cd_BoardAddr;
	  cd->cd_Flags &= ~CDF_CONFIGME;

	  /*
	   * now fire up the handler task, that will execute our 
	   * scsi command requests for us (message queues are an excellent
	   * way of garanteeing single-threaded device hardware usage.. ) 
	   */
	  
	  tcb = &dev->sc_htcb;
	  tcb->tc_SPLower = (APTR)dev->sc_hstack;
	  tcb->tc_SPUpper = (APTR)(dev->sc_hstack + HANDLER_STACKSIZE);
	  tcb->tc_SPReg = (APTR)((ulong)tcb->tc_SPUpper - 4);
	  /* pass the child the device pointer */
	  *(struct scsi_dev **)tcb->tc_SPReg = dev;
	  
	  tcb->tc_Node.ln_Type = NT_TASK;	    
	  tcb->tc_Node.ln_Pri = HANDLER_PRIORITY;
	  tcb->tc_Node.ln_Name = HANDLER_NAME;
	  
	  NewList(&tcb->tc_MemEntry);

DPRINTF(("starting handler task"));
	  AddTask(tcb, scsi_cmd, 0);
	  
	  /*
	   * wait for child to get ready.. don't know, how bad this is
	   * at this point to wait - it *is* bad according to the RKM - 
	   * but how bad.....?? 
	   */
	  WaitPort(&dev->sc_msgport); GetMsg(&dev->sc_msgport);
	  /* when this returns, dev->sc_hmsgport will be valid */

DPRINTF(("Handler task ok"));
	  
	  /* 
	   * Now try to find out, which devices are connected to
	   * the SCSI bus, and fire up the respective units, that mount
	   * themselves as DOS nodes (not at the moment:-)) 
	   */
	  for (unit = 0; unit < 7; unit++) try_unit (dev, unit);
	}
      CloseLibrary(ExpansionBase);
    }
DPRINTF(("scsi_init terminated"));
  ReleaseSemaphore(& dev->sc_semaph);
  return dev;
}

int
scsi_open()
{
  /* can't use them as regular variables, because d0/a1 are likely to
   * get used as temporary registers */
  register struct scsi_dev *dev_pass asm("a6");
  register struct IORequest *io_req_pass asm("a1");
  register int unit_pass asm("d0");

  struct scsi_dev *dev = dev_pass;
  struct IORequest *io_req = io_req_pass;
  int unit = unit_pass;
  
  /*
   * we will only be able to get this semaphore, when Init is completely
   * thru with its job, this was necessary, because Init breaks the Forbid
   * Exec enabled during its execution 
   */
  ObtainSemaphore(& dev->sc_semaph);

DPRINTF(("open: dev = $%lx, unit# = %ld, io_req = $%lx", dev, unit, io_req));

  /* perform validity checking on the unit range */
  if (unit >= 0 && unit <= 6)
    {
      /*
       * since we already fired up all units that exist, if the requested
       * unit doesn't have a corresponding unit task, we return with 
       * an error 
       */
      if (dev->sc_units[unit])
        {
	  ++ dev->sc_device.dd_Library.lib_OpenCnt;
	  ++ dev->sc_units[unit]->scu_unit.unit_OpenCnt;
	  io_req->io_Error = 0;
	  io_req->io_Unit = (struct Unit *)dev->sc_units[unit];
	  dev->sc_device.dd_Library.lib_Flags &= ~LIBF_DELEXP;
DPRINTF(("open-ok!"));
 	  ReleaseSemaphore(& dev->sc_semaph);
	  return 0;
	}
    }
DPRINTF(("open-Error!"));
  ReleaseSemaphore(& dev->sc_semaph);
  io_req->io_Error = IOERR_OPENFAIL;
  return IOERR_OPENFAIL;
}

int
scsi_close()
{
  register struct scsi_dev *dev_pass asm("a6");
  register struct IORequest *io_req_pass asm("a1");
  struct IORequest *io_req = io_req_pass;
  struct scsi_unit *su = (struct scsi_unit *)io_req->io_Unit;
  struct scsi_dev *dev = dev_pass;
 
DPRINTF(("close: dev = $%lx, unit = $%lx, io_req = $%lx", dev,su,io_req));

  io_req->io_Unit = (void *)
  io_req->io_Device = (void *)-1;
  
  if (-- su->scu_unit.unit_OpenCnt == 0)
    expunge_unit(dev, su);
    
  if (-- dev->sc_device.dd_Library.lib_OpenCnt == 0)
    {
      if (dev->sc_device.dd_Library.lib_Flags & LIBF_DELEXP)
        return scsi_expunge(dev);
    }
  return 0;
}

int
scsi_expunge()
{
  register struct scsi_dev *dev_pass asm("a6");
  struct scsi_dev *dev = dev_pass;
  ulong seglist;

DPRINTF(("expunge: dev = $%lx", dev));

#ifdef DO_EXPUNGE

  if (dev->sc_device.dd_Library.lib_OpenCnt > 0)
    {
      dev->sc_device.dd_Library.lib_Flags |= LIBF_DELEXP;
      return 0;
    }
 
  dev->sc_hmessage.scm_cmd = SCM_CMD_SHUTDOWN;
  PutGetMsg(dev->sc_hmsgport, &dev->sc_msgport, 
	    (struct Message *)&dev->sc_hmessage);
  /* handler task now shut down */
  
  Remove((struct Node *)dev);

  seglist = dev->sc_seglist;

  FreeMem(dev,
	  dev->sc_device.dd_Library.lib_NegSize +
          dev->sc_device.dd_Library.lib_PosSize);
  return seglist;
#else
  
  return 0;
#endif

}

int
null_func()
{
  return 0;
}

int
expunge_unit(struct scsi_dev *dev, struct scsi_unit *unit)
{
  /* since I spawned the units at Init-time, not at Open-time, I don't
   * want to get rid of them now.. */
  return 0;
}

/****************************************************************************/

int scsi_invalid(), scsi_reset(),
    scsi_read(), scsi_write(), scsi_update(), scsi_clear(), scsi_stop(),
    scsi_start(), scsi_flush(), scsi_motor(), scsi_seek(), scsi_format(),
    scsi_remove(), scsi_changenum(), scsi_changestate(), scsi_protstatus(),
    scsi_rawread(), scsi_rawwrite(), scsi_getdrivetype(), 
    scsi_getnumtracks(), scsi_addchangeint(), scsi_remchangeint(),
    scsi_direct();

int
(*cmd_table []) () = {
    scsi_invalid,	/* 00 */
    scsi_reset,		/* 01 */
    scsi_read,		/* 02 */
    scsi_write,		/* 03 */
    scsi_update,	/* 04 */
    scsi_clear,		/* 05 */
    scsi_stop,		/* 06 */
    scsi_start,		/* 07 */
    scsi_flush,		/* 08 */
    scsi_motor,		/* 09 */
    scsi_seek,		/* 10 */
    scsi_format,	/* 11 */
    scsi_remove,	/* 12 */
    scsi_changenum,	/* 13 */
    scsi_changestate,	/* 14 */
    scsi_protstatus,	/* 15 */
    scsi_rawread,	/* 16 */
    scsi_rawwrite,	/* 17 */
    scsi_getdrivetype,	/* 18 */
    scsi_getnumtracks,	/* 19 */
    scsi_addchangeint,	/* 20 */
    scsi_remchangeint,	/* 21 */
    scsi_invalid,	/* 22 */
    scsi_invalid,	/* 23 */
    scsi_invalid,	/* 24 */
    scsi_invalid,	/* 25 */
    scsi_invalid,	/* 26 */
    scsi_invalid,	/* 27 */
    scsi_direct,	/* 28 */
};
    
int
scsi_beginio()
{
  register struct IORequest *io_req_pass asm("a1");
  register struct scsi_dev *dev_pass asm("a6");
  struct scsi_dev *dev = dev_pass;
  struct IORequest *io_req = io_req_pass;
  struct scsi_unit *su = (struct scsi_unit *)io_req->io_Unit;

  if (!su) return IOERR_OPENFAIL;

DPRINTF(("beginio: dev = $%lx, unit# = %ld, cmd = %ld",
	 dev, su->scu_unitnum, io_req->io_Command));
  
  /*
   * in the first release of the device, the RMB bit is ignored, and
   * thus removeable mediums are not supported. To be compatible to
   * later releases, ETD-commands are just routed to their normal
   * partners.. 
   */
   
  io_req->io_Command &= ~TDF_EXTCOM;
  io_req->io_Message.mn_Node.ln_Type = NT_MESSAGE;

  if (io_req->io_Command >= 0 && io_req->io_Command <= 28)
    {
      /* no immediates supported for now.. */
      io_req->io_Flags &= ~IOF_QUICK;
      PutMsg(su->scu_cmd_mp, (struct Message *)io_req);
      return 0;
    }
  return IOERR_NOCMD;
}

int
scsi_abortio()
{
  return IOERR_NOCMD;
}

/* now follow all - so far - ignored commands */

int
scsi_invalid()
{
  return IOERR_NOCMD;
}

int
scsi_reset()
{
  return IOERR_NOCMD;
}

int
scsi_stop()
{
  return IOERR_NOCMD;
}
int
scsi_start()
{
  return IOERR_NOCMD;
}

int
scsi_flush(struct IOStdReq *io_req)
{
#ifdef ENABLE_CACHE
  invalidate_cache (io_req->io_Unit);
  return 0;
#endif
  return IOERR_NOCMD;
}
int
scsi_motor()
{
  return 0;
}
int
scsi_seek()
{
  return IOERR_NOCMD;
}
int
scsi_remove()
{
  return IOERR_NOCMD;
}

int
scsi_changenum(struct IOStdReq *io_req)
{
  io_req->io_Actual = 1; /* always disk label 1... */
  return 0;
}
int
scsi_changestate(struct IOStdReq *io_req)
{
  io_req->io_Actual = 0; /* always a disk present?? with RMB surely not... */
  return 0;
}
int
scsi_rawread()
{
  return IOERR_NOCMD;
}
int
scsi_rawwrite()
{
  return IOERR_NOCMD;
}

int
scsi_getdrivetype(struct IOStdReq *io_req)
{
  io_req->io_Actual = DRIVE3_5;
  return 0;
}

int
scsi_getnumtracks(struct IOStdReq *io_req)
{
  io_req->io_Actual = 1;
  return 0;
}

int
scsi_clear(struct IOStdReq *io_req)
{
#ifdef ENABLE_CACHE
  invalidate_cache (io_req->io_Unit);
  return 0;
#endif
  return IOERR_NOCMD;
}

int
scsi_update()
{
#ifdef ENABLE_CACHE
  return 0; /* we have a write-thru cache */
#endif
  return IOERR_NOCMD;
}

int
scsi_addchangeint()
{
  return IOERR_NOCMD;
}

int
scsi_remchangeint()
{
  return IOERR_NOCMD;
}
