summaryrefslogtreecommitdiff
path: root/sys/arch/vax/boot/scsi_low.c
diff options
context:
space:
mode:
authorMats O Jansson <maja@cvs.openbsd.org>1997-01-15 23:25:34 +0000
committerMats O Jansson <maja@cvs.openbsd.org>1997-01-15 23:25:34 +0000
commitbf6413046b73005ecca3f5352c8711d00a6d00b9 (patch)
treefcd2c64f80b1be68d962f84a1e34da0a02d5385f /sys/arch/vax/boot/scsi_low.c
parent957078809571dbe61aac81705145fa4cee6bfda8 (diff)
sync with NetBSD 970112 -moj
Diffstat (limited to 'sys/arch/vax/boot/scsi_low.c')
-rw-r--r--sys/arch/vax/boot/scsi_low.c479
1 files changed, 479 insertions, 0 deletions
diff --git a/sys/arch/vax/boot/scsi_low.c b/sys/arch/vax/boot/scsi_low.c
new file mode 100644
index 00000000000..a86a82de7c9
--- /dev/null
+++ b/sys/arch/vax/boot/scsi_low.c
@@ -0,0 +1,479 @@
+/* $NetBSD: scsi_low.c,v 1.1 1996/08/02 11:22:34 ragge Exp $ */
+
+/****************************************************************************
+ * NS32K Monitor SCSI low-level driver
+ * Bruce Culbertson
+ * 8 March 1990
+ * (This source is public domain source.)
+ *
+ * Originally written by Bruce Culbertson for a ns32016 port of Minix.
+ * Adapted from that for the pc532 (ns32632) monitor.
+ * Adapted from that for NetBSD/pc532 by Philip L. Bunde.
+ *
+ * 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.
+ ****************************************************************************/
+
+#include "so.h"
+#include "ka410.h"
+
+#define BB_DEBUG(x) printf x
+#define CLEAR_INTR() *ka410_intclr=INTR_SC
+#define CHECK_INTR() *ka410_intreq&INTR_SC
+
+#define OK 0
+#define NOT_OK OK+1
+#define PRIVATE
+#define PUBLIC
+#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 MAX_CACHE 0x4000
+
+/* 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 0x200C0080 /* base for control registers */
+#define SC_DMA 0x200D0000 /* base for data registers (8/16K) */
+#define SC_CURDATA SC_CTL+(4*0)
+#define SC_OUTDATA SC_CTL+(4*0)
+#define SC_ICMD SC_CTL+(4*1)
+#define SC_MODE SC_CTL+(4*2)
+#define SC_TCMD SC_CTL+(4*3)
+#define SC_STAT1 SC_CTL+(4*4)
+#define SC_STAT2 SC_CTL+(4*5)
+#define SC_START_SEND SC_CTL+(4*5)
+#define SC_INDATA SC_CTL+(4*6)
+#define SC_RESETIP SC_CTL+(4*7)
+#define SC_START_RCV SC_CTL+(4*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 0xfffffe00
+#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 (1000*1000)
+#define SC_LOG_LEN 32
+
+PRIVATE struct scsi_args *sc_ptrs;
+PRIVATE char sc_cur_phase,
+ sc_reset_done = 1,
+ sc_have_msg,
+ sc_accept_int,
+ sc_dma_dir;
+
+long sc_dma_port = SC_DMA,
+ sc_dma_adr;
+
+#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 */
+};
+
+/*===========================================================================*
+ * exec_scsi_low *
+ *===========================================================================*/
+/* Execute a generic SCSI command. Passed pointers to eight buffers:
+ * data-out, data-in, command, status, dummy, dummy, message-out, message-in.
+ */
+PUBLIC
+int
+exec_scsi_low (args, scsi_adr)
+struct scsi_args *args;
+long scsi_adr;
+{
+ int ret;
+
+ BB_DEBUG (("exec_scsi_low(0x%x, %d)\n", args, scsi_adr));
+
+ sc_ptrs = args; /* make pointers globally accessible */
+ /* bertram ??? 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 */
+ printf("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();
+ printf("SCSI: %s\n", scsi_errors[ret]);
+ return NOT_OK;
+ }
+}
+
+/*===========================================================================*
+ * sc_reset *
+ *===========================================================================*/
+/*
+ * Reset SCSI bus.
+ */
+PRIVATE
+sc_reset()
+{
+ volatile int i;
+
+ BB_DEBUG (("sc_reset()\n"));
+
+ 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;
+
+ BB_DEBUG (("sc_wait_bus_free()\n"));
+
+ 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;
+
+ BB_DEBUG (("sc_select(%d)\n", adr));
+
+ CLEAR_INTR();
+ 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 */
+ if (CHECK_INTR() == 0)
+ continue;
+ stat1 = RD_ADR (SC_STAT1);
+ if (stat1 & SC_S_BSY) break; /* select successful */
+ if (i > MAX_WAIT) { /* timeout */
+ printf("SCSI: SELECT timeout\n");
+ sc_reset();
+ return NOT_OK;
+ }
+ }
+ CLEAR_INTR();
+ 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 (CHECK_INTR() == 0)
+ continue;
+ if (stat1 & SC_S_REQ) break; /* target requesting transfer */
+ if (i > MAX_WAIT) { /* timeout */
+ printf("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) {
+ printf("SCSI: bad phase = %d\n", sc_cur_phase);
+ sc_reset();
+ return NOT_OK;
+ }
+ new_ptr = sc_ptrs->ptr[PH_CMD];
+ if (new_ptr == 0) {
+ printf("SCSI: NULL command pointer\n");
+ sc_reset();
+ return NOT_OK;
+ }
+ sc_accept_int = 1;
+ sc_dma_setup (DISK_WRITE, new_ptr);
+ CLEAR_INTR();
+ 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;
+
+ BB_DEBUG (("scsi_interrupt()\n"));
+
+ 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) != 0))
+ {
+# ifdef DEBUG
+ ++sc_spurious_int;
+# endif
+ printf ("sc_spurious_int\n");
+ 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 = sc_dma_adr; /* 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? */
+ printf ("target deasserted BSY?\n");
+ if (sc_have_msg) ret = ISR_OK;
+ else ret = ISR_BSYERR;
+ } else if (!(stat2 & SC_S_PHASE)) {/* if phase mismatch, setup new phase */
+ printf ("phase mismatch\n");
+ 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, new_ptr);
+ RD_ADR (SC_MODE) |= SC_M_DMA;
+ CLEAR_INTR();
+ WR_ADR (SC_START_RCV, 0); /* tell SCSI to start DMA */
+ } else {
+ sc_dma_setup (DISK_WRITE, new_ptr);
+ RD_ADR (SC_MODE) |= SC_M_DMA;
+ WR_ADR (SC_ICMD, SC_ENABLE_DB);
+ CLEAR_INTR();
+ 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.
+ *
+ * The pseudo-DMA is subtler than it looks because of the cache.
+ *
+ * 1) When accessing I/O devices through a cache, some mechanism is
+ * necessary to ensure you access the device rather than the cache.
+ * On the 32532, the IODEC signal is supposed to be asserted for I/O
+ * addresses to accomplish this. However, a bug makes this much
+ * slower than necessary and severely hurts pseudo-DMA performance.
+ * Hence, IODEC is not asserted for the SCSI DMA port.
+ *
+ * 2) Because of (1), we must devise our own method of forcing the
+ * SCSI DMA port to be read. 0x8000000 addresses have been decoded
+ * to all access this port. By always using new addresses to access
+ * the DMA port (wrapping only after reading MAX_CACHE bytes), we
+ * force cache misses and, hence, device reads. Since the cache
+ * is write-through, we do not need to worry about writes.
+ *
+ * 3) It is possible to miss the last few bytes of a transfer if
+ * bus transfer size is not considered. The loop in sc_receive()
+ * transfers data until the interrupt signal is asserted. If
+ * bytes are transferred, the attempt to move the first byte of a
+ * double word causes the whole word to be read into the cache.
+ * Then the byte is transferred. If reading the double word
+ * completed the SCSI transfer, then the loop exits since
+ * interrupt is asserted. However, the last few bytes have only
+ * been moved into the cache -- they have not been moved to the
+ * DMA destination.
+ *
+ * 4) It is also possible to miss the first few bytes of a transfer.
+ * If the address used to access pseudo-dma port is not double word
+ * aligned, the whole double word is read into the cache, and then
+ * data is moved from the middle of the word (i.e. something other
+ * than the first bytes read from the SCSI controller) by the
+ * pseudo-dma loop in sc_receive().
+ */
+sc_dma_setup (dir, adr)
+int dir;
+long adr;
+{
+ BB_DEBUG (("sc_dma_setup(%d, %d)\n", dir, adr));
+
+ CLEAR_INTR();
+ /* if (sc_dma_port > SC_DMA + MAX_CACHE) */
+ sc_dma_port = SC_DMA;
+ sc_dma_dir = dir;
+ sc_dma_adr = 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.
+ * See sc_dma_setup for details.
+ */
+int
+sc_receive()
+{
+ int stat2, isr_ret;
+ int i, c;
+
+ BB_DEBUG (("sc_receive()\n"));
+
+ /*
+ * check the interrupt-flag and wait if it reappears...
+ */
+ c = *ka410_intreq;
+ printf ("begin: %x/%x ", c, *ka410_intreq);
+ for (i=0; i<100; i++) {
+ if ((c = *ka410_intreq) & INTR_SC)
+ break;
+ printf (" %x ", c);
+ }
+ if (i==100)
+ printf ("timeout in sc_receive.\n");
+
+#if 1
+ 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) { /* test really not necessary on pc532 */
+ if (sc_dma_dir == DISK_READ)
+ *((long *)sc_dma_adr)++ = *((volatile long *)sc_dma_port)++;
+ else *((volatile long *)sc_dma_port)++ = *((long *)sc_dma_adr)++;
+ }
+ }
+#endif
+ printf ("isr_ret: %d (ISR_NOTDONE: %d)\n", isr_ret, ISR_NOTDONE);
+ return isr_ret;
+}
+
+/*===========================================================================*
+ * scCtlrSelect
+ *===========================================================================*/
+/* Select a SCSI device.
+ */
+scCtlrSelect (ctlr)
+int ctlr;
+{
+ BB_DEBUG (("scCtlrSelect()\n"));
+#if 0
+ 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 */
+#endif
+}