#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 "device.h"

/*
 * Unit task. First figures out, of which type it is, then waits for
 * command requests from BeginIO.
 */

DECL_DPRINTF;
extern struct MsgPort *CreatePort(char *name, int pri);

extern int (*cmd_table[]) ();

/* this is a unit task */

void
unit_start (struct scsi_unit *su)
{
  struct scsi_msg *sm = &su->scu_hmessage;
  struct SCSICmd *cmd;
  struct IOStdReq *io_req;
  ubyte *cb;
  ulong inq_buf;
  ulong sense_buf;
  struct MsgPort *mp;
  int retries;
  
DPRINTF(("unit-task: %ld started", su->scu_unitnum));

  mp = &su->scu_unit.unit_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);

  sm->scm_message.mn_ReplyPort = mp;

  su->scu_cmd_mp = CreatePort(0,0);
  su->scu_errors = su->scu_errorlevel = 0;

  sm->scm_unit = su->scu_unitnum;
  cmd = &su->scu_scsicmd;

  sm->scm_scsi_cmd = cmd;
  cmd->scsi_Command = cb = su->scu_cmd;
  
  /* first find out, what kind this device is of */
  cb[0] = 0x12; /* INQUIRY */
  cb[1] = cb[2] = cb[3] = 0;
  cb[4] = 2; /* allocation length */
  cb[5] = 0;
  cmd->scsi_Data = (UWORD *)&inq_buf;
  cmd->scsi_Length = 2;
  cmd->scsi_Flags = SCSIF_READ|SCSIF_OLDAUTOSENSE;
  cmd->scsi_SenseData = (UBYTE *)&sense_buf;
  cmd->scsi_SenseLength = 4;
  retries = 60;

  /* account for UNIT ATTENTION sense response */
  do
    {
      sm->scm_cmd = SCM_CMD_EXEC_SCSI;
      
      PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort, 
		(struct Message *)sm);
    }
  /* as long as the device responds UNIT_ATTENTION */
  while (((sm->scm_cmd == HFERR_BadStatus && sense_buf == 0x07000600)
	  || (sm->scm_cmd > HFERR_BadStatus)) && retries-- > 0);

DPRINTF(("identified unit %ld, result = %ld, inq_buf = $%lx, sense = $%lx",
	 su->scu_unitnum, sm->scm_cmd, inq_buf, sense_buf));
  
  if (sm->scm_cmd == 0) /* scm_cmd is io_Error when returned */
    {
      su->scu_type = inq_buf >> 24;
      if (inq_buf & 0x00800000) su->scu_type |= SCU_TYPE_RMB;
    }
  else
    {
      /* in that case assume a non removable DIRECT ACCESS device... */
      su->scu_type = SCU_TYPE_DIRECT;
    }

  /* START/STOP unit on sequential devices means LOAD/UNLOAD, since
   * we can't garantee that there's a tape inserted, this command might
   * always fail... so better don't issue it */

  if ((su->scu_type & ~SCU_TYPE_RMB) == SCU_TYPE_DIRECT)
    {
      cb[0] = 0x1b; /* START/STOP UNIT */
      cb[1] = 0; /* not immed, return, when unit really ready */
      cb[2] = cb[3] = 0;
      cb[4] = 1; /* start unit */
      cb[5] = 0;
      cmd->scsi_Data = 0;
      cmd->scsi_Length = 0;
      
      for (retries = 0; retries < 60; retries ++)
        {
          sm->scm_cmd = SCM_CMD_EXEC_SCSI;
          
          PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort, 
				(struct Message *)sm);
          if (sm->scm_cmd == 0) break;
        }

DPRINTF(("unit %ld, start_unit returned %ld, retry = %ld",
	 su->scu_unitnum, sm->scm_cmd, retries));
    }

  /* now set block length.. this is important for streamers or other
   * devices that may support different block lengths */
  if ((su->scu_type & ~SCU_TYPE_RMB) == SCU_TYPE_DIRECT)
    {
      cb[0] = 0x15; /* MODE_SELECT */
      cb[1] = 1; /* save parameters */
      cb[2] = cb[3] = 0;
      cb[4] = 12; /* parameter list length */
      cb[5] = 0;
      /* this is the actual parameter list, that switches the device into
       * 512 byte block mode */
      su->scu_buf[0] = 
      su->scu_buf[1] =
      su->scu_buf[2] = 0;
      su->scu_buf[3] = 8;
      su->scu_buf[4] =
      su->scu_buf[5] =
      su->scu_buf[6] =
      su->scu_buf[7] =
      su->scu_buf[8] =
      su->scu_buf[9] = 0;
      su->scu_buf[10] = 2; /* 0x200 = 512 */
      su->scu_buf[11] = 0;
    }
  else /* SCU_TYPE_SEQUENTIAL */
    {
      cb[0] = 0x15; /* MODE_SELECT */
      cb[1] = 0; /* reserved */
      cb[2] = cb[3] = 0;
      cb[4] = 12; /* parameter list length */
      cb[5] = 0;
      /* this is the actual parameter list, that switches the device into
       * 512 byte block mode */
      su->scu_buf[0] = 
      su->scu_buf[1] = 0; /* reserved */
      su->scu_buf[2] = 0x10; /* buffered writes ok, default speed */
      su->scu_buf[3] = 8;
      su->scu_buf[4] =    /* default density */
      su->scu_buf[5] =
      su->scu_buf[6] =
      su->scu_buf[7] =
      su->scu_buf[8] =
      su->scu_buf[9] = 0;
      su->scu_buf[10] = 2; /* 0x200 = 512 */
      su->scu_buf[11] = 0;
   }

  cmd->scsi_Data = (UWORD *)su->scu_buf;
  cmd->scsi_Flags = SCSIF_OLDAUTOSENSE; /* no READ, this is a write ! */
  cmd->scsi_Length = 12;

  for (retries = 0; retries < 60; retries ++)
    {
      sm->scm_cmd = SCM_CMD_EXEC_SCSI;
      
      PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort, 
		(struct Message *)sm);
      if (sm->scm_cmd == 0) break;
    }

DPRINTF(("unit %ld, mode_select returned %ld, retry = %ld",
	 su->scu_unitnum, sm->scm_cmd, retries));
  

  /* reading the first cylinder, until we don't get any errors, seems
   * a good way to ensure, that the unit is working.. well, hddisk
   * does it that way... */

  if ((su->scu_type & ~SCU_TYPE_RMB) == SCU_TYPE_DIRECT)
    {
      cb[0] = 0x8; /* READ */
      cb[1] = cb[2] = cb[3] = 0;
      cb[4] = 1; /* allocation length (1 Block)*/
      cb[5] = 0;
      cmd->scsi_Length = 512;
      cmd->scsi_Data = (UWORD *)su->scu_buf;
      cmd->scsi_Flags = SCSIF_READ|SCSIF_OLDAUTOSENSE;

      for (retries = 0; retries < 60; retries ++)
	{
	  sm->scm_cmd = SCM_CMD_EXEC_SCSI;
      
	  PutGetMsg(su->scu_hmsgport, &su->scu_unit.unit_MsgPort, 
		    (struct Message *)sm);
	  if (sm->scm_cmd == 0) break;
	}
    }
  
  /* for now, DON'T mount the disk.. we can do this manually, and this
   * garantees yet another source of bugs we can eliminate during the
   * testing phase.. */

DPRINTF(("unit %ld ready, type $%lx, (retries = %ld), awaiting commands",
	 su->scu_unitnum, su->scu_type, retries));

#ifdef ENABLE_CACHE
  invalidate_cache (su);
#endif

  for (;;)  
    {
      WaitPort(su->scu_cmd_mp);
      while (io_req = (struct IOStdReq *)GetMsg(su->scu_cmd_mp))
	{
DPRINTF(("unit %ld (%ld), command %ld",
	 su->scu_unitnum, ((struct scsi_unit*)io_req->io_Unit)->scu_unitnum,
	 io_req->io_Command));

	  /* beginio already validated the io_Command field */          
	  io_req->io_Error = (*cmd_table[io_req->io_Command])(io_req);
	  ReplyMsg((struct Message *)io_req);
        }
    }
}
