429 lines
13 KiB
C
429 lines
13 KiB
C
/* $NetBSD: scsi_low.c,v 1.1 1997/05/17 13:56:10 matthias 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 <lib/libsa/stand.h>
|
|
|
|
#include <pc532/stand/common/so.h>
|
|
|
|
#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 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 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 2000000
|
|
#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;
|
|
|
|
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 */
|
|
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;
|
|
|
|
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 */
|
|
printf("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 */
|
|
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);
|
|
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) == SC_S_PHASE))
|
|
{
|
|
# 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 = 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? */
|
|
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, 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, 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.
|
|
*
|
|
* 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;
|
|
{
|
|
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;
|
|
|
|
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)++;
|
|
}
|
|
}
|
|
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 */
|
|
}
|