#include <exec/types.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>
#include <inline/expansion.h>
  
#include "device.h"

/*
 * in this file you find those device-commands, that are actually 
 * implemented for this device. For commands, that just return true or
 * some constant look in device.c.
 */
  
extern int  lookup_cache (struct scsi_unit *su, ulong block_num, ubyte *buf);
extern void update_cache (struct scsi_unit *su, long block_num, ubyte *buf);
extern void invalidate_cache (struct scsi_unit *su);
extern int AutoAutoRequest(char *l1, char *l2, char *l3, char *left, char *right);

DECL_DPRINTF;

#ifdef DEBUG
#define FLASH_SCREEN() { int i; for (i=0; i < 10000; i++) *(unsigned short*)0xdff180=i;}
#else
#define FLASH_SCREEN()
#endif

int
scsi_read(struct IOStdReq *io_req)
{
  struct scsi_unit *su = (struct scsi_unit *)io_req->io_Unit;
  struct SCSICmd *sc;
  ulong length, this_length, this_offset, max_inc, data, type;
  
DPRINTF(("read: unit %ld, type $%lx, offset %ld, length %ld", 
	 su->scu_unitnum, su->scu_type, io_req->io_Offset, io_req->io_Length));
  
  length = io_req->io_Length;
  data = (ulong) io_req->io_Data;
  
#ifdef ENABLE_CACHE
  if (length == UNIT_SEC_SIZE)
    {
      if (lookup_cache (su, io_req->io_Offset / UNIT_SEC_SIZE, (ubyte *)data))
	{
	  io_req->io_Actual = length;
	  return 0;
	}
    }
#endif
  
  /*
   * if the buffer is not word aligned, I have to use the unit buffer
   * and later copy the bits and bytes, argl.. 
   */
  if (data & 1) 
    {
      data = (ulong) su->scu_buf;
      max_inc = sizeof(su->scu_buf); /* *has* to be <= DMA_MAX_TRANSFER */
    }
  else
    {
      /* 2090-DMA circuit can only handle up to 64K direct DMA, sigh.. */
      max_inc = DMA_MAX_TRANSFER;
    }
  
  this_length = length > max_inc ? max_inc : length;
  this_offset = io_req->io_Offset;
  
  /* don't care whether the medium is removable or not.. (yet:-)) */
  type = su->scu_type & ~SCU_TYPE_RMB;
  
  if (type != SCU_TYPE_DIRECT && type != SCU_TYPE_SEQUENTIAL)
    /* don't know how to access cd-roms, printers etc... */
    return IOERR_NOCMD;
  
  sc = &su->scu_scsicmd;
  sc->scsi_Flags = SCSIF_READ;
  
  /*
   * if type is sequential, AND offset is 0, then we issue a REWIND
   * before we start 
   */
  if (type == SCU_TYPE_SEQUENTIAL && this_offset == 0)
    {
      ubyte *cb = su->scu_cmd;
      *cb++ = 1; /* REWIND */
      *cb++ = 0; /* wait for completion */
      *cb++ = 0;
      *cb++ = 0;
      *cb++ = 0;
      *cb   = 0;
      su->scu_hmessage.scm_cmd = SCM_CMD_EXEC_SCSI;
      sc->scsi_Length = 0;
      PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort,
		(struct Message *)&su->scu_hmessage);
    }
  
  io_req->io_Actual = 0;

  /* 
   * this statement moved after REWIND to allow for READs with length 0
   * to rewind the tape 
   */
  if (!length)
    {
      /* just abort right here.. it could be hazardrous to the DMA
       * circuit if it had to transfer 0 byte .. */
      return 0;
    }
  
  do
    {
      if (type == SCU_TYPE_DIRECT)
        {
          ubyte *cb = su->scu_cmd;
          *cb++ = 0x28;
          *cb++ = 0;
	  *cb++ = 0; /* 24 + 9 exceeds 32 bit.. */
	  *cb++ = this_offset >> (16 + 9); /* heavy stuff for the */
	  *cb++ = this_offset >> ( 8 + 9); /* optimizer... :-))) */
	  *cb++ = this_offset >> (     9);
          *cb++ = 0;
          *cb++ = this_length >> ( 8 + 9);
          *cb++ = this_length >> (     9);
          *cb   = 0;
        }
      else /* SCU_TYPE_SEQUENTIAL */
        {
          ubyte *cb = su->scu_cmd;
          *cb++ = 0x8; /* no extended read for seq devices */
	  *cb++ = 1;   /* use fixed size blocks */
	  *cb++ = this_length >> (16 + 9);
	  *cb++ = this_length >> ( 8 + 9);
          *cb++ = this_length >> (     9);
          *cb   = 0;
        }
      su->scu_hmessage.scm_cmd = SCM_CMD_EXEC_SCSI;
      sc->scsi_Data = (UWORD *)data;
      sc->scsi_Length = this_length;
      PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort,
		(struct Message *)&su->scu_hmessage);
      
      /* error check */
      if (su->scu_hmessage.scm_cmd != 0)
	{
 	  /* better not retry sequ accesses.. */
	  if (type == SCU_TYPE_SEQUENTIAL)
	    return su->scu_hmessage.scm_cmd;
	  
	  if (su->scu_errors++ > UNIT_MAX_ERRORS)
	    {
	      sprintf(su->scu_buf, "Unit %d had %d errors",
		      su->scu_unitnum,
		      su->scu_errors + UNIT_MAX_ERRORS*su->scu_errorlevel);
	      
	      su->scu_errors = 0;
	      switch (su->scu_errorlevel ++)
		{
		case 0:  AutoAutoRequest(SCSI_NAME, (char *)su->scu_buf,
					 "Please safe your data!",
					 "Cancel", "Cancel");
		  break;
		case 1:  AutoAutoRequest(SCSI_NAME, (char *)su->scu_buf,
					 "Please don't use this unit anymore!",
					 "I won't!", "I won't!");
		  break;
		default: AutoAutoRequest(SCSI_NAME, (char *)su->scu_buf,
					 "You'd better use FORMAT anyway!",
					 "Yes dad!", "Yes dad!");
		  break;
		}
	      return su->scu_hmessage.scm_cmd;
	    }
	  continue; /* retry command */
	}
      /* do we have to copy (shudder, performance killer...) ?? */
      if (data == (ulong) su->scu_buf)
	/* I'm not allowed to directly increment the io_req pointer */
	CopyMem((char *)data, (char*)io_req->io_Data+io_req->io_Actual, 
		this_length);
      else
	data += this_length;
      
      io_req->io_Actual += sc->scsi_Actual;
      length -= this_length;
      this_offset += this_length;
      
      this_length = length > max_inc ? max_inc : length;
      /*
       * if scsi_Actual is 0, and there is no error, I can't imagine
       * what's happening .. to prevent an infinite loop, I'll break
       * and the application should take care of this strange device.. 
       */
    } while (this_length && sc->scsi_Actual);
  
#ifdef ENABLE_CACHE
  if (io_req->io_Length == UNIT_SEC_SIZE)
    update_cache (su, io_req->io_Offset / UNIT_SEC_SIZE, (ubyte*)io_req->io_Data);
#endif
  
  return 0; /* if we get here, no errors happened.. */
}

int
scsi_write(struct IOStdReq *io_req)
{
  struct scsi_unit *su = (struct scsi_unit *)io_req->io_Unit;
  struct SCSICmd *sc;
  ulong length, this_length, this_offset, max_inc, data, type;
  
DPRINTF(("write: unit %ld, type $%lx, offset %ld, length %ld", 
	 su->scu_unitnum, su->scu_type, io_req->io_Offset, io_req->io_Length));
  
  length = io_req->io_Length;
  data = (ulong) io_req->io_Data;
  
  if (!length)
    {
      /*
       * just abort right here.. it could be hazardrous to the DMA
       * circuit if it had to transfer 0 byte .. 
       */
      io_req->io_Actual = 0;
      return 0;
    }
  
  /*
   * if the buffer is not word aligned, I have to use the unit buffer
   * and copy the bits and bytes before the transfer, argl.. 
   */
  if (data & 1) 
    {
      data = (ulong) su->scu_buf;
      max_inc = sizeof(su->scu_buf); /* *has* to be <= DMA_MAX_TRANSFER */
    }
  else
    {
      /* 2090-DMA circuit can only handle up to 64K direct DMA, sigh.. */
      max_inc = DMA_MAX_TRANSFER;
    }
  
  this_length = length > max_inc ? max_inc : length;
  this_offset = io_req->io_Offset;
  
  /*
   * do this check before performing any actions.. if something goes
   * wrong, we end up with an invalidated cache, that's lots better
   * than pretending it's still ok.. 
   */
  if (io_req->io_Length != UNIT_SEC_SIZE)
    invalidate_cache (su);
  
  /* don't care whether the medium is removable or not.. (yet:-)) */
  type = su->scu_type & ~SCU_TYPE_RMB;
  if (type != SCU_TYPE_DIRECT && type != SCU_TYPE_SEQUENTIAL)
    /* don't know how to access cd-roms, printers etc... */
    return IOERR_NOCMD;
  
  sc = &su->scu_scsicmd;
  /* rewind is not a write or a read, stick to the safe option.. */
  sc->scsi_Flags = SCSIF_READ;
  
  /* if type is sequential, AND offset is 0, then we issue a REWIND
   * before we start */
  if (type == SCU_TYPE_SEQUENTIAL && this_offset==0)
    {
      ubyte *cb = su->scu_cmd;
      *cb++ = 1; /* REWIND */
      *cb++ = 0; /* wait for completion */
      *cb++ = 0;
      *cb++ = 0;
      *cb++ = 0;
      *cb   = 0;
      su->scu_hmessage.scm_cmd = SCM_CMD_EXEC_SCSI;
      sc->scsi_Length = 0;
      PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort,
		(struct Message *)&su->scu_hmessage);
    }
  
  io_req->io_Actual = 0;
  
  do
    {
      /* do we have to copy (shudder, performance killer...) ?? */
      if (data == (ulong) su->scu_buf)
	/* I'm not allowed to directly increment the io_req pointer */
	CopyMem((char*)io_req->io_Data+io_req->io_Actual,
		(char*)data, this_length);
      
      if (type == SCU_TYPE_DIRECT)
        {
          ubyte *cb = su->scu_cmd;
          *cb++ = 0x2a;
          *cb++ = 0;
	  *cb++ = 0; /* 24 + 9 exceeds 32 bit.. */
	  *cb++ = this_offset >> (16 + 9); /* heavy stuff for the */
	  *cb++ = this_offset >> ( 8 + 9); /* optimizer... :-))) */
	  *cb++ = this_offset >> (     9);
          *cb++ = 0;
          *cb++ = this_length >> ( 8 + 9);
          *cb++ = this_length >> (     9);
          *cb   = 0;
        }
      else /* SCU_TYPE_SEQUENTIAL */
        {
          ubyte *cb = su->scu_cmd;
          *cb++ = 0xa; /* no extended write for seq devices */
	  *cb++ = 1;   /* use fixed size blocks */
	  *cb++ = this_length >> (16 + 9);
	  *cb++ = this_length >> ( 8 + 9);
          *cb++ = this_length >> (     9);
          *cb   = 0;
        }
      su->scu_hmessage.scm_cmd = SCM_CMD_EXEC_SCSI;
      sc->scsi_Data = (UWORD *)data;
      sc->scsi_Length = this_length;
      sc->scsi_Flags = SCSIF_WRITE;
      PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort,
		(struct Message *)&su->scu_hmessage);
      
      /* error check */
      if (su->scu_hmessage.scm_cmd != 0)
	{
 	  /* better not retry sequ accesses.. */
	  if (type == SCU_TYPE_SEQUENTIAL)
	    return su->scu_hmessage.scm_cmd;
	  
	  if (su->scu_errors++ > UNIT_MAX_ERRORS)
	    {
	      sprintf(su->scu_buf, "Unit %d had %d errors",
		      su->scu_unitnum,
		      su->scu_errors + UNIT_MAX_ERRORS*su->scu_errorlevel);
	      
	      su->scu_errors = 0;
	      switch (su->scu_errorlevel ++)
		{
		case 0:  AutoAutoRequest(SCSI_NAME, (char *)su->scu_buf,
					 "Please safe your data!",
					 "Cancel", "Cancel");
		  break;
		case 1:  AutoAutoRequest(SCSI_NAME, (char *)su->scu_buf,
					 "Please don't use this unit anymore!",
					 "I won't!", "I won't!");
		  break;
		default: AutoAutoRequest(SCSI_NAME, (char *)su->scu_buf,
					 "You'd better use FORMAT anyway!",
					 "Yes dad!", "Yes dad!");
		  break;
		}
	      return su->scu_hmessage.scm_cmd;
	    }
	  continue; /* retry command */
	}
      
      if (data != (ulong) su->scu_buf)
	data += this_length;
      
      io_req->io_Actual += sc->scsi_Actual;
      length -= this_length;
      this_offset += this_length;
      
      this_length = length > max_inc ? max_inc : length;
      /*
       * if scsi_Actual is 0, and there is no error, I can't imagine
       * what's happening .. to prevent an infinite loop, I'll break
       * and the application should take care of this strange device.. 
       */
    } while (this_length && sc->scsi_Actual);
  
#ifdef ENABLE_CACHE
  if (io_req->io_Length == UNIT_SEC_SIZE)
    update_cache (su, io_req->io_Offset / UNIT_SEC_SIZE, (ubyte*)io_req->io_Data);
#endif
  
  return 0; /* if we get here, no errors happened.. */
}

int
scsi_format(struct IOStdReq *io_req)
{
  /*
   * "could" do some checking, whether the parameters meet certain
   * tougher criteria than for write, but who cares, my write can
   * even cope with odd-aligned data, so what ? 
   */
  return scsi_write(io_req);
}

/*
 * this is one of the easiest commands of the whole device... the entire
 * device structure is laid out for this to be implemented as good as
 * possible:-))
 */
int
scsi_direct(struct IOStdReq *io_req)
{
  struct scsi_msg sm;
  struct scsi_unit *su;
  
  su = (struct scsi_unit *)io_req->io_Unit;
  sm.scm_cmd = SCM_CMD_EXEC_SCSI;
  sm.scm_unit = su->scu_unitnum;
  sm.scm_message.mn_ReplyPort = &su->scu_unit.unit_MsgPort;
  sm.scm_scsi_cmd = (struct SCSICmd *)io_req->io_Data;
  
  PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort,
	    (struct Message *)&sm);
DPRINTF(("scsi-direct: unit %ld, result %ld", su->scu_unitnum, sm.scm_cmd));
  return sm.scm_cmd;
}

int
scsi_protstatus(struct IOStdReq *io_req)
{
  struct scsi_unit *su = (struct scsi_unit *)io_req->io_Unit;
  struct SCSICmd *sc;
  ulong  sense_buf;
  ubyte *cb;
  
  sc = &su->scu_scsicmd;
  sc->scsi_Flags = SCSIF_READ;
  cb = su->scu_cmd;
  *cb++ = 0x1A; /* MODE SENSE */
  *cb++ = *cb++ = *cb++ = 0;
  *cb++ = 4; /* allocated length */
  *cb   = 0;
  
  su->scu_hmessage.scm_cmd = SCM_CMD_EXEC_SCSI;
  sense_buf = 0;
  sc->scsi_Data = (UWORD *)&sense_buf;
  sc->scsi_Length = 4;
  PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort,
	    (struct Message *)&su->scu_hmessage);
  
  io_req->io_Actual = sense_buf & 0x00008000 ? 1 : 0; /* WP bit */
  return su->scu_hmessage.scm_cmd;
}
