/****************************************************************************
 * NS32K Monitor SCSI driver
 * Bruce Culbertson
 * 8 March 1990
 *
 * Adapted from Minix SCSI driver.  Do not use DMA -- makes 32016 and pc532
 * versions compatible.  Do not use interrupts -- makes it harder for the
 * user code to bomb this code.
 *
 * There are three monitor SCSI commands.  "Read" and "write" I think are
 * fairly self explanatory once you read the help messages.  They, in fact,
 * execute the "extended read", "extended write", and "request sense"
 * commands from the SCSI standard.
 * 
 * "Raw" lets you execute any SCSI command but you need a SCSI reference to
 * know what the commands are and what their formats are.  The SCSI
 * standard specifies that there are six buffers which, for example, hold a
 * SCSI command or are the source or destination for data.  You provide
 * "raw" with an array of pointers to the six buffers.  Using "edit", you
 * can enter a SCSI command somewhere in memory and you can create the
 * array of pointers.  The array must actually be eight entries long; two
 * entries are not used.  By typing "raw <array address>", the SCSI command
 * is executed.
 * 
 * By the way, "read", "write", and "raw" talk only to the DP8490 SCSI
 * controller.  I have not had time to read the Adaptec data sheet and
 * write a driver for it.
 ****************************************************************************/
#include "debugger.h"

#define OK 		0
#define NOT_OK		OK+1
#define	PRIVATE
#define PUBLIC
#define DISK_READ	3
#define DISK_WRITE	4
#define WR_ADR(adr,val)	(*((volatile unsigned char *)(adr))=(val))
#define RD_ADR(adr)	(*((volatile unsigned char *)(adr)))
#define AIC6250		0
#define DP8490		1
#define DEFAULT_SCSI_ADR	1
#define DEFAULT_SCSI_LUN	0
#define MAX_CACHE	0x10000

/* SCSI bus phases
 */
#define PH_ODATA	0
#define PH_IDATA	1
#define PH_CMD		2
#define PH_STAT		3
#define PH_IMSG		7
#define PH_NONE		8
#define PH_IN(phase)	((phase) & 1)

/* NCR5380 SCSI controller registers
 */
#define SC_CTL		0x30000000	/* base for control registers */
#define SC_DMA		0x38000000	/* base for data registers */
#define SC_CURDATA	SC_CTL+0
#define SC_OUTDATA	SC_CTL+0
#define SC_ICMD		SC_CTL+1
#define SC_MODE		SC_CTL+2
#define SC_TCMD		SC_CTL+3
#define SC_STAT1	SC_CTL+4
#define SC_STAT2	SC_CTL+5
#define SC_START_SEND	SC_CTL+5
#define SC_INDATA	SC_CTL+6
#define SC_RESETIP	SC_CTL+7
#define SC_START_RCV	SC_CTL+7

/* Bits in NCR5380 registers
 */
#define SC_A_RST	0x80
#define SC_A_SEL	0x04
#define SC_S_SEL	0x02
#define SC_S_REQ	0x20
#define SC_S_BSY	0x40
#define SC_S_BSYERR	0x04
#define SC_S_PHASE	0x08
#define SC_S_IRQ	0x10
#define SC_S_DRQ	0x40
#define SC_M_DMA	0x02
#define SC_M_BSY	0x04
#define SC_ENABLE_DB	0x01

/* Status of interrupt routine, returned in m1_i1 field of message.
 */
#define ISR_NOTDONE	0
#define ISR_OK		1
#define ISR_BSYERR	2
#define ISR_RSTERR	3
#define ISR_BADPHASE	4
#define ISR_TIMEOUT	5

#define ICU_ADR		0xffffffe0
#define ICU_IO		(ICU_ADR+20)
#define ICU_DIR		(ICU_ADR+21)
#define ICU_DATA	(ICU_ADR+19)
#define ICU_SCSI_BIT	0x80

/* Miscellaneous
 */
#define MAX_WAIT	2000000
#define SC_LOG_LEN	32

struct scsi_args {
  long ptr [8];
};

PRIVATE struct scsi_args	*sc_ptrs;
PRIVATE char			sc_cur_phase,
				sc_reset_done,
				sc_have_msg,
				sc_accept_int,
				*scsiDmaAdr = (char *)SC_DMA,
				*memDmaAdr,
				dmaDir;
long scsiAdr = DEFAULT_SCSI_ADR,	/* default SCSI address */
     scsiLun = DEFAULT_SCSI_LUN;

#ifdef DEBUG
struct sc_log {
  unsigned char stat1, stat2;
}				sc_log [SC_LOG_LEN],
				*sc_log_head = sc_log;
int				sc_spurious_int;
#endif
unsigned char
	sc_watchdog_error;		/* watch dog error */

/* error messages */
char *scsi_errors[] = {
  0,					/* ISR_NOTDONE */
  0,					/* ISR_OK */
  "busy error",				/* ISR_BSYERR */
  "reset error",			/* ISR_RSTERR */
  "NULL pointer for current phase",	/* ISR_BADPHASE */
  "timeout",				/* ISR_TIMEOUT */
};

/*===========================================================================*
 *				scsiRaw					     *
 *===========================================================================*/
scsiRaw(p)
char *p;
{
  long parm_ptr, scsi_adr;
  int ret;

  if (GOT_NUM != getIntScan (&p, &parm_ptr)) {
    myPrintf ("Bad parameter address\n");
    return;
  }
  if (BAD_NUM == (ret = getIntScan (&p, &scsi_adr))) {	/* SCSI adr */
    myPrintf ("Bad SCSI address\n");
    return;
  } else if (ret == NO_NUM) scsi_adr = scsiAdr;		/* use default */
  ret = execute_scsi_cmd ((struct scsi_args *)parm_ptr, scsi_adr);
  if (ret != OK) myPrintf ("Error %d\n", ret);
}

/*===========================================================================*
 *				scsiRead				     * 
 *===========================================================================*/
scsiRead (p)
char *p;
{
  scsi_generic (p, DISK_READ);
}

/*===========================================================================*
 *				scsiWrite				     * 
 *===========================================================================*/
scsiWrite (p)
char *p;
{
  scsi_generic (p, DISK_WRITE);
}

/*===========================================================================*
 *				scsi_generic				     * 
 *===========================================================================*/
scsi_generic (p, op)
char *p;
int op;
{
  long block, ram_adr, len, scsi_adr, scsi_lun;
  int ret;

  if (GOT_NUM != getIntScan (&p, &block)) {		/* block */
    myPrintf ("Bad block number\n");
    return;
  }
  if (GOT_NUM != getIntScan (&p, &ram_adr)) {		/* memory buf */
    myPrintf ("Bad RAM address\n");
    return;
  }
  if (BAD_NUM == (ret = getIntScan (&p, &len))) {	/* number blocks */
    myPrintf ("Bad number of blocks\n");
    return;
  } else if (ret == NO_NUM) len = 1;			/* use default */
  if (BAD_NUM == (ret = getIntScan (&p, &scsi_adr))) {	/* SCSI adr */
    myPrintf ("Bad SCSI address\n");
    return;
  } else if (ret == NO_NUM) scsi_adr = scsiAdr;		/* use default */
  if (BAD_NUM == (ret = getIntScan (&p, &scsi_lun))) {	/* SCSI LUN */
    myPrintf ("Bad logical unit number\n");
    return;
  } else if (ret == NO_NUM) scsi_lun = scsiLun;		/* use default */
  if (debug)
	myPrintf ("blk=%ld adr=0x%lx len=%ld adr=%d lun=%d\n",
	block, ram_adr, len, scsi_adr, scsi_lun);
  ret = sc_rdwt (op, block, ram_adr, len, scsi_adr, scsi_lun);
  if (debug) myPrintf ("return = %d\n", ret);
  else if (ret != OK) myPrintf ("Error %d\n", ret);
}

#define U8	unsigned char

#define MAX_SCSI_RETRIES	6
#define CMD_IX		2
#define CMD_LEN		12		/* longest SCSI command */
#define CMD_SENSE	0x03
#define CMD_READ	0x28
#define CMD_WRITE	0x2a
PRIVATE U8		cmd_buf[CMD_LEN];

#define SENSE_LEN 	24		/* extended sense length */
#define SENSE_KEY	2
#define NO_SENSE	0
#define RECOVERY_ERR	1
#define UNIT_ATTN	6
#define ADD_SENSE_CODE	12
#define SENSE_RST	0x29
PRIVATE	U8		sense_buf[SENSE_LEN];

#define CHECK_CONDITION	2
#define STAT_IX		3
PRIVATE U8		stat_buf[1];
#define IMSG_IX		7
PRIVATE U8		msg_buf[1];

#define ODATA_IX	0
#define IDATA_IX	1
PRIVATE struct scsi_args scsi_args;

/*===========================================================================*
 *				sc_rdwt					     * 
 *===========================================================================*/
PRIVATE int
sc_rdwt(op, block, ram_adr, len, sc_adr, lun)
long block, ram_adr, len, sc_adr, lun;
{
/* Carry out a read or write request for the SCSI disk. */
  int retries;
  U8 *p;

  for (retries = 0; retries < MAX_SCSI_RETRIES; ++retries) {
    p = cmd_buf;		/* build SCSI command */
    *p++ = (op == DISK_READ)? CMD_READ: CMD_WRITE;
    *p++ = lun << 5;
    *p++ = (block >> 24) & 0xff;
    *p++ = (block >> 16) & 0xff;
    *p++ = (block >> 8) & 0xff;
    *p++ = (block >> 0) & 0xff;
    *p++ = 0;
    *p++ = (len >> 8) & 0xff;
    *p++ = (len >> 0) & 0xff;
    *p = 0;
    scsi_args.ptr[CMD_IX] = (long)cmd_buf;
    scsi_args.ptr[STAT_IX] = (long)stat_buf;
    scsi_args.ptr[IMSG_IX] = (long)msg_buf;
    if (op == DISK_READ) {
      scsi_args.ptr[IDATA_IX] = ram_adr;
      scsi_args.ptr[ODATA_IX] = 0;
    } else {
      scsi_args.ptr[ODATA_IX] = ram_adr;
      scsi_args.ptr[IDATA_IX] = 0;
    }
    if (OK != execute_scsi_cmd (&scsi_args, sc_adr))
      continue;
    if (*stat_buf == 0)
      /* Success -- this should be the usual case */
      return OK;
    if (*stat_buf != CHECK_CONDITION) {
      /* do not know how to handle this so return error */
      myPrintf ("SCSI device returned unknown status: %d\n", *stat_buf);
      continue;
    }
    /* Something funny happened, need to execute request-sense command
     * to learn more.
     */
    if (OK == get_sense(sc_adr, lun))
      /* Something funny happened, but the device recovered from it and
       * the command succeeded.
       */
      return OK;
  }
  myPrintf ("SCSI %s, block %d failed even after retries\n",
    op == DISK_READ? "READ": "WRITE", block);
  return NOT_OK;
}

/*===========================================================================*
 *				get_sense				     * 
 *===========================================================================*/
/* Execute a "request sense" SCSI command and check results.  When a SCSI
 * command returns CHECK_CONDITION, a request-sense command must be executed.
 * A request-sense command provides information about the original command.
 * The original command might have succeeded, in which case it does not
 * need to be retried and OK is returned.  Examples: read error corrected
 * with error correction code, or error corrected by retries performed by
 * the SCSI device.  The original command also could have failed, in
 * which case NOT_OK is returned.
 */
#define LOGICAL_ADR	\
  (sense_buf[3]<<24 | sense_buf[4]<<16 | sense_buf[5]<<8 | sense_buf[6])

PRIVATE int
get_sense (scsi_adr, lun)
long scsi_adr;
long lun;
{
  U8 *p;

  p = cmd_buf;				/* build SCSI command */
  *p++ = CMD_SENSE;
  *p++ = lun << 5;
  *p++ = 0;
  *p++ = 0;
  *p++ = SENSE_LEN;
  *p = 0;
  scsi_args.ptr[IDATA_IX] = (long)sense_buf;
  scsi_args.ptr[ODATA_IX] = 0;
  scsi_args.ptr[CMD_IX] = (long)cmd_buf;
  scsi_args.ptr[STAT_IX] = (long)stat_buf;
  scsi_args.ptr[IMSG_IX] = (long)msg_buf;
  if (OK != execute_scsi_cmd (&scsi_args, scsi_adr)) {
    myPrintf ("SCSI SENSE command failed\n");
    return NOT_OK;
  }
  if (*stat_buf != 0) {
    myPrintf ("SCSI SENSE returned wrong status %d\n", *stat_buf);
    return NOT_OK;
  }
  switch (sense_buf[SENSE_KEY] & 0xf) {
    case NO_SENSE:
    case UNIT_ATTN:			/* reset */
      return NOT_OK;			/* must retry command */
    case RECOVERY_ERR:
      /* eventually, we probably do not want to hear about these. */
      myPrintf (
	"SCSI ok with recovery, code 0x%x, logical address 0x%x\n",
	sense_buf[ADD_SENSE_CODE], LOGICAL_ADR);
      return OK;			/* orig command was ok with recovery */
    default:
      myPrintf (
	"SCSI failure, key 0x%x, code 0x%x, log adr 0x%x, sense buf 0x%x\n",
	sense_buf[SENSE_KEY], sense_buf[ADD_SENSE_CODE],
	LOGICAL_ADR, sense_buf);
      return NOT_OK;			/* orig command failed */
  }
}

/*===========================================================================*
 *				execute_scsi_cmd			     * 
 *===========================================================================*/
/* Execute a generic SCSI command.  Passed pointers to eight buffers:
 * data-out, data-in, command, status, dummy, dummy, message-out, message-in.
 */
PUBLIC
int
execute_scsi_cmd (args, scsi_adr)
struct scsi_args *args;
long scsi_adr;
{
  int ret;

  sc_ptrs = args;			/* make pointers globally accessible */
  scCtlrSelect (DP8490);
  if (!sc_reset_done) sc_reset();
  /* TCMD has some undocumented behavior in initiator mode.  I think the
   * data bus cannot be enabled if i/o is asserted.
   */
  WR_ADR (SC_TCMD, 0);
  if (OK != sc_wait_bus_free ()) {	/* bus-free phase */
    myPrintf ("SCSI: bus not free\n");
    return NOT_OK;
  }
  sc_cur_phase = PH_NONE;
  sc_have_msg = 0;
  if (OK != sc_select (scsi_adr))	/* select phase */
    return NOT_OK;
  sc_watchdog_error = 0;
  ret = sc_receive ();			/* isr does the rest */
  if (ret == ISR_OK) return OK;
  else {
    sc_reset();
    myPrintf ("SCSI: %s\n", scsi_errors[ret]);
    return NOT_OK;
  }
}

/*===========================================================================*
 *				sc_reset				     * 
 *===========================================================================*/
/*
 * Reset SCSI bus.
 */
PRIVATE
sc_reset()
{
  volatile int i;

  WR_ADR (SC_MODE, 0);			/* get into harmless state */
  WR_ADR (SC_OUTDATA, 0);
  WR_ADR (SC_ICMD, SC_A_RST);		/* assert RST on SCSI bus */
  i = 200;				/* wait 25 usec */
  while (i--);
  WR_ADR (SC_ICMD, 0);			/* deassert RST, get off bus */
  sc_reset_done = 1;
}

/*===========================================================================*
 *				sc_wait_bus_free			     * 
 *===========================================================================*/
PRIVATE int
sc_wait_bus_free()
{
  int i = MAX_WAIT;
  volatile int j;

  while (i--) {
    /* Must be clear for 2 usec, so read twice */
    if (RD_ADR (SC_STAT1) & (SC_S_BSY | SC_S_SEL)) continue;
    for (j = 0; j < 25; ++j);
    if (RD_ADR (SC_STAT1) & (SC_S_BSY | SC_S_SEL)) continue;
    return OK;
  }
  sc_reset_done = 0;
  return NOT_OK;
}

/*===========================================================================*
 *				sc_select				     * 
 *===========================================================================*/
/* This duplicates much of the work that the interrupt routine would do on a
 * phase mismatch and, in fact, the original plan was to just do the select,
 * let a phase mismatch occur, and let the interrupt routine do the rest.
 * That didn't work because the 5380 did not reliably generate the phase
 * mismatch interrupt after selection.
 */
PRIVATE int
sc_select(adr)
long adr;
{
  int i, stat1;
  long new_ptr;

  WR_ADR (SC_OUTDATA, adr);		/* SCSI bus address */
  WR_ADR (SC_ICMD, SC_A_SEL | SC_ENABLE_DB);
  for (i = 0;; ++i) {			/* wait for target to assert SEL */
    stat1 = RD_ADR (SC_STAT1);
    if (stat1 & SC_S_BSY) break;	/* select successful */
    if (i > MAX_WAIT) {			/* timeout */
      myPrintf ("SCSI: SELECT timeout\n");
      sc_reset();
      return NOT_OK;
    }
  }
  WR_ADR (SC_ICMD, 0);			/* clear SEL, disable data out */
  WR_ADR (SC_OUTDATA, 0);
  for (i = 0;; ++i) {			/* wait for target to assert REQ */
    if (stat1 & SC_S_REQ) break;	/* target requesting transfer */
    if (i > MAX_WAIT) {			/* timeout */
      myPrintf ("SCSI: REQ timeout\n");
      sc_reset();
      return NOT_OK;
    }
    stat1 = RD_ADR (SC_STAT1);
  }
  sc_cur_phase = (stat1 >> 2) & 7;	/* get new phase from controller */
  if (sc_cur_phase != PH_CMD) {
    myPrintf ("SCSI: bad phase = %d\n", sc_cur_phase);
    sc_reset();
    return NOT_OK;
  }
  new_ptr = sc_ptrs->ptr[PH_CMD];
  if (new_ptr == 0) {
    myPrintf ("SCSI: NULL command pointer\n");
    sc_reset();
    return NOT_OK;
  }
  sc_accept_int = 1;
  sc_dma_setup (DISK_WRITE, (char *)new_ptr);
  WR_ADR (SC_TCMD, PH_CMD);
  WR_ADR (SC_ICMD, SC_ENABLE_DB);
  WR_ADR (SC_MODE, SC_M_BSY | SC_M_DMA);
  WR_ADR (SC_START_SEND, 0);
  return OK;
}

/*===========================================================================*
 *				scsi_interrupt				     *
 *===========================================================================*/
/* SCSI interrupt handler.
 */
PUBLIC
int
scsi_interrupt()
{
  unsigned char stat2, dummy;
  long new_ptr;
  int ret = ISR_NOTDONE;

  stat2 = RD_ADR (SC_STAT2);		/* get status before clearing request */

# ifdef DEBUG				/* debugging log of interrupts */
  sc_log_head->stat1 = RD_ADR (SC_STAT1);
  sc_log_head->stat2 = stat2;
  if (++sc_log_head >= sc_log + SC_LOG_LEN) sc_log_head = sc_log;
  sc_log_head->stat1 = sc_log_head->stat2 = 0xff;
# endif

  for (;;) {
    dummy = RD_ADR (SC_RESETIP);	/* clear interrupt request */
    if (!sc_accept_int ||		/* return if spurious interrupt */
        (!sc_watchdog_error &&
         (stat2 & SC_S_BSYERR) == 0 && (stat2 & SC_S_PHASE) == 1))
    {
#     ifdef DEBUG
        ++sc_spurious_int;
#     endif
      return ret;
    }
    RD_ADR (SC_MODE) &= ~SC_M_DMA;	/* clear DMA mode */
    WR_ADR (SC_ICMD, 0);		/* disable data bus */
    if (sc_cur_phase != PH_NONE) {	/* if did DMA, save the new pointer */
      new_ptr = (long) memDmaAdr;	/* fetch new pointer from DMA cntlr */
      if (sc_cur_phase == PH_IMSG &&	/* have message? */
        new_ptr != sc_ptrs->ptr[PH_IMSG]) sc_have_msg = 1;
      sc_ptrs->ptr[sc_cur_phase] =	/* save pointer */
        new_ptr;
    }
    if (sc_watchdog_error) ret = ISR_TIMEOUT;
    else if (stat2 & SC_S_BSYERR) {	/* target deasserted BSY? */
      if (sc_have_msg) ret = ISR_OK;
      else ret = ISR_BSYERR;
    } else if (!(stat2 & SC_S_PHASE)) {	/* if phase mismatch, setup new phase */
      sc_cur_phase = 			/* get new phase from controller */
        (RD_ADR (SC_STAT1) >> 2) & 7;
      new_ptr = sc_ptrs->ptr[sc_cur_phase];
      if (new_ptr == 0) ret = ISR_BADPHASE;
      else {
        WR_ADR (SC_TCMD, sc_cur_phase);	/* write new phase into TCMD */
        if (PH_IN (sc_cur_phase)) {	/* set DMA controller */
          sc_dma_setup (DISK_READ, (char *)new_ptr);
          RD_ADR (SC_MODE) |= SC_M_DMA;
          WR_ADR (SC_START_RCV, 0);	/* tell SCSI to start DMA */
	} else {
          sc_dma_setup (DISK_WRITE, (char *)new_ptr);
	  RD_ADR (SC_MODE) |= SC_M_DMA;
	  WR_ADR (SC_ICMD, SC_ENABLE_DB);
	  WR_ADR (SC_START_SEND, 0);
	}
      }
    } else ret = ISR_RSTERR;
    if (ret != ISR_NOTDONE) {		/* if done, send message to task */
      sc_watchdog_error = 0;
      sc_accept_int = 0;
      WR_ADR (SC_MODE, 0);		/* clear monbsy, dma */
      break;				/* reti re-enables ints */
    }
    if (0 == ((stat2 =			/* check for another interrupt */
      RD_ADR (SC_STAT2)) & SC_S_IRQ)) 
    {
      break;
    }
  }
  return ret;
}

/*===========================================================================*
 *				sc_dma_setup				     *
 *===========================================================================*/
/* Fake DMA setup.  Just store pointers and direction in global variables.
 * Note: scsi dma address does not assert IODEC and, hence, the cache is
 * not bypassed.  Fortunately, 0x38000000 - 0x3fffffff all map to the
 * same DMA data register in the SCSI controller.  If we keep incrementing
 * the address we read from the controller, we make sure we always get a
 * cache miss and read fresh data.
 */
sc_dma_setup (dir, adr)
int dir;
char *adr;
{
  if (scsiDmaAdr > (char *)(SC_DMA + MAX_CACHE))
    scsiDmaAdr = (char *)SC_DMA;
  dmaDir = dir;
  memDmaAdr = adr;
}

/*===========================================================================*
 *				sc_receive				     *
 *===========================================================================*/
/* Replacement for Minix receive(), which waits for a message.  This code
 * spins, waiting for data to transfer or interrupt requests to handle.
 */
int
sc_receive()
{
  int stat2, isr_ret;

  for (;;) {
    stat2 = RD_ADR (SC_STAT2);
    if (stat2 & SC_S_IRQ) {
      if (ISR_NOTDONE != (isr_ret = scsi_interrupt())) break;
    } else if (stat2 & SC_S_DRQ) {
      /* increment scsi address to fool cache */
      if (dmaDir == DISK_READ) *memDmaAdr++ = RD_ADR (scsiDmaAdr++);
      else WR_ADR (scsiDmaAdr++, *memDmaAdr++);
    }
  }
  return isr_ret;
}

/*===========================================================================*
 *				scCtlrSelect
 *===========================================================================*/
/* Select a SCSI device.
 */
scCtlrSelect (ctlr)
int ctlr;
{
  RD_ADR (ICU_IO) &= ~ICU_SCSI_BIT;	/* i/o, not port */
  RD_ADR (ICU_DIR) &= ~ICU_SCSI_BIT;	/* output */
  if (ctlr == DP8490)
    RD_ADR (ICU_DATA) &= ~ICU_SCSI_BIT;	/* select = 0 for 8490 */
  else
    RD_ADR (ICU_DATA) |= ICU_SCSI_BIT;	/* select = 1 for AIC6250 */
}
