summaryrefslogtreecommitdiff
path: root/sys/arch/vax/boot/scsi_low.c
blob: a86a82de7c9d54c99c4dfa9c14d106e782543df6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
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
}