patch-1.3.93 linux/drivers/scsi/esp.c

Next file: linux/drivers/scsi/esp.h
Previous file: linux/drivers/scsi/aic7xxx.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.92/linux/drivers/scsi/esp.c linux/drivers/scsi/esp.c
@@ -0,0 +1,1128 @@
+/* esp.c:  EnhancedScsiProcessor Sun SCSI driver code.
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/malloc.h>
+#include <linux/blk.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+
+#include "scsi.h"
+#include "hosts.h"
+#include "esp.h"
+
+#include <asm/sbus.h>
+#include <asm/dma.h>
+#include <asm/system.h>
+#include <asm/idprom.h>
+#include <asm/machines.h>
+#include <asm/ptrace.h>
+#include <asm/pgtable.h>
+#include <asm/oplib.h>
+#include <asm/vaddrs.h>
+#include <asm/io.h>
+
+#define DEBUG_ESP
+/* #define DEBUG_ESP_SG */
+
+#if defined(DEBUG_ESP)
+#define ESPLOG(foo)  printk foo
+#else
+#define ESPLOG(foo)
+#endif /* (DEBUG_ESP) */
+
+#define INTERNAL_ESP_ERROR \
+        (panic ("Internal ESP driver error in file %s, line %d\n", \
+		__FILE__, __LINE__))
+
+#define INTERNAL_ESP_ERROR_NOPANIC \
+        (printk ("Internal ESP driver error in file %s, line %d\n", \
+		 __FILE__, __LINE__))
+
+/* This enum will be expanded when we have sync code written. */
+enum {
+	not_issued   = 0x01,  /* Still in the issue_SC queue.          */
+	in_selection = 0x02,  /* ESP is arbitrating, awaiting IRQ      */
+	in_datain    = 0x04,  /* Data is transferring over the bus     */
+	in_dataout   = 0x08,  /* Data is transferring over the bus     */
+	in_status    = 0x10,  /* Awaiting status/msg bytes from target */
+	in_finale    = 0x11,  /* Sent Msg ack, awaiting disconnect     */
+};
+
+struct proc_dir_entry proc_scsi_esp = {
+	PROC_SCSI_ESP, 3, "esp",
+	S_IFDIR | S_IRUGO | S_IXUGO, 2
+};
+
+struct Sparc_ESP *espchain;
+
+static void esp_intr(int irq, void *dev_id, struct pt_regs *pregs);
+static void esp_done(struct Sparc_ESP *esp, int error);
+
+/* Debugging routines */
+struct esp_cmdstrings {
+	unchar cmdchar;
+	char *text;
+} esp_cmd_strings[] = {
+	/* Miscellaneous */
+	{ ESP_CMD_NULL, "ESP_NOP", },
+	{ ESP_CMD_FLUSH, "FIFO_FLUSH", },
+	{ ESP_CMD_RC, "RSTESP", },
+	{ ESP_CMD_RS, "RSTSCSI", },
+	/* Disconnected State Group */
+	{ ESP_CMD_RSEL, "RESLCTSEQ", },
+	{ ESP_CMD_SEL, "SLCTNATN", },
+	{ ESP_CMD_SELA, "SLCTATN", },
+	{ ESP_CMD_SELAS, "SLCTATNSTOP", },
+	{ ESP_CMD_ESEL, "ENSLCTRESEL", },
+	{ ESP_CMD_DSEL, "DISSELRESEL", },
+	{ ESP_CMD_SA3, "SLCTATN3", },
+	{ ESP_CMD_RSEL3, "RESLCTSEQ", },
+	/* Target State Group */
+	{ ESP_CMD_SMSG, "SNDMSG", },
+	{ ESP_CMD_SSTAT, "SNDSTATUS", },
+	{ ESP_CMD_SDATA, "SNDDATA", },
+	{ ESP_CMD_DSEQ, "DISCSEQ", },
+	{ ESP_CMD_TSEQ, "TERMSEQ", },
+	{ ESP_CMD_TCCSEQ, "TRGTCMDCOMPSEQ", },
+	{ ESP_CMD_DCNCT, "DISC", },
+	{ ESP_CMD_RMSG, "RCVMSG", },
+	{ ESP_CMD_RCMD, "RCVCMD", },
+	{ ESP_CMD_RDATA, "RCVDATA", },
+	{ ESP_CMD_RCSEQ, "RCVCMDSEQ", },
+	/* Initiator State Group */
+	{ ESP_CMD_TI, "TRANSINFO", },
+	{ ESP_CMD_ICCSEQ, "INICMDSEQCOMP", },
+	{ ESP_CMD_MOK, "MSGACCEPTED", },
+	{ ESP_CMD_TPAD, "TPAD", },
+	{ ESP_CMD_SATN, "SATN", },
+	{ ESP_CMD_RATN, "RATN", },
+};
+#define NUM_ESP_COMMANDS  ((sizeof(esp_cmd_strings)) / (sizeof(struct esp_cmdstrings)))
+
+/* Print textual representation of an ESP command */
+static inline void esp_print_cmd(unchar espcmd)
+{
+	unchar dma_bit = espcmd & ESP_CMD_DMA;
+	int i;
+
+	espcmd &= ~dma_bit;
+	for(i=0; i<NUM_ESP_COMMANDS; i++)
+		if(esp_cmd_strings[i].cmdchar == espcmd)
+			break;
+	if(i==NUM_ESP_COMMANDS)
+		printk("ESP_Unknown");
+	else
+		printk("%s%s", esp_cmd_strings[i].text,
+		       ((dma_bit) ? "+DMA" : ""));
+}
+
+/* Print the status register's value */
+static inline void esp_print_statreg(unchar statreg)
+{
+	unchar phase;
+
+	printk("STATUS<");
+	phase = statreg & ESP_STAT_PMASK;
+	printk("%s,", (phase == ESP_DOP ? "DATA-OUT" :
+		       (phase == ESP_DIP ? "DATA-IN" :
+			(phase == ESP_CMDP ? "COMMAND" :
+			 (phase == ESP_STATP ? "STATUS" :
+			  (phase == ESP_MOP ? "MSG-OUT" :
+			   (phase == ESP_MIP ? "MSG_IN" :
+			    "unknown")))))));
+	if(statreg & ESP_STAT_TDONE)
+		printk("TRANS_DONE,");
+	if(statreg & ESP_STAT_TCNT)
+		printk("TCOUNT_ZERO,");
+	if(statreg & ESP_STAT_PERR)
+		printk("P_ERROR,");
+	if(statreg & ESP_STAT_SPAM)
+		printk("SPAM,");
+	if(statreg & ESP_STAT_INTR)
+		printk("IRQ,");
+	printk(">");
+}
+
+/* Print the interrupt register's value */
+static inline void esp_print_ireg(unchar intreg)
+{
+	printk("INTREG< ");
+	if(intreg & ESP_INTR_S)
+		printk("SLCT_NATN ");
+	if(intreg & ESP_INTR_SATN)
+		printk("SLCT_ATN ");
+	if(intreg & ESP_INTR_RSEL)
+		printk("RSLCT ");
+	if(intreg & ESP_INTR_FDONE)
+		printk("FDONE ");
+	if(intreg & ESP_INTR_BSERV)
+		printk("BSERV ");
+	if(intreg & ESP_INTR_DC)
+		printk("DISCNCT ");
+	if(intreg & ESP_INTR_IC)
+		printk("ILL_CMD ");
+	if(intreg & ESP_INTR_SR)
+		printk("SCSI_BUS_RESET ");
+	printk(">");
+}
+
+/* Print the sequence step registers contents */
+static inline void esp_print_seqreg(unchar stepreg)
+{
+	stepreg &= ESP_STEP_VBITS;
+	printk("STEP<%s>",
+	       (stepreg == ESP_STEP_ASEL ? "SLCT_ARB_CMPLT" :
+		(stepreg == ESP_STEP_SID ? "1BYTE_MSG_SENT" :
+		 (stepreg == ESP_STEP_NCMD ? "NOT_IN_CMD_PHASE" :
+		  (stepreg == ESP_STEP_PPC ? "CMD_BYTES_LOST" :
+		   (stepreg == ESP_STEP_FINI ? "CMD_SENT_OK" :
+		    "UNKNOWN"))))));
+}
+
+/* Manipulation of the ESP command queues.  Thanks to the aha152x driver
+ * and its author, Juergen E. Fischer, for the methods used here.
+ * Note that these are per-ESP queues, not global queues like
+ * the aha152x driver uses.
+ */
+static inline void append_SC(Scsi_Cmnd **SC, Scsi_Cmnd *new_SC)
+{
+	Scsi_Cmnd *end;
+	unsigned long flags;
+
+	save_flags(flags); cli();
+	new_SC->host_scribble = (unsigned char *) NULL;
+	if(!*SC)
+		*SC = new_SC;
+	else {
+		for(end=*SC;end->host_scribble;end=(Scsi_Cmnd *)end->host_scribble)
+			;
+		end->host_scribble = (unsigned char *) new_SC;
+	}
+	restore_flags(flags);
+}
+
+static inline Scsi_Cmnd *remove_first_SC(Scsi_Cmnd **SC)
+{
+	Scsi_Cmnd *ptr;
+	unsigned long flags;
+
+	save_flags(flags); cli();
+	ptr = *SC;
+	if(ptr)
+		*SC = (Scsi_Cmnd *) (*SC)->host_scribble;
+	restore_flags(flags);
+	return ptr;
+}
+
+static inline Scsi_Cmnd *remove_SC(Scsi_Cmnd **SC, int target, int lun)
+{
+	Scsi_Cmnd *ptr, *prev;
+	unsigned long flags;
+
+	save_flags(flags); cli();
+	for(ptr = *SC, prev = NULL;
+	    ptr && ((ptr->target != target) || (ptr->lun != lun));
+	    prev = ptr, ptr = (Scsi_Cmnd *) ptr->host_scribble)
+		;
+	if(ptr) {
+		if(prev)
+			prev->host_scribble=ptr->host_scribble;
+		else
+			*SC=(Scsi_Cmnd *)ptr->host_scribble;
+	}
+	restore_flags(flags);
+	return ptr;
+}
+
+static inline void do_pause(unsigned amount)
+{
+	unsigned long the_time = jiffies + amount;
+
+	while(jiffies < the_time)
+		barrier(); /* Not really needed, but... */
+}
+
+/* This places the ESP into a known state at boot time. */
+static inline void esp_bootup_reset(struct Sparc_ESP *esp, struct Sparc_ESP_regs *eregs)
+{
+	struct sparc_dma_registers *dregs = esp->dregs;
+	volatile unchar trash;
+
+	/* Punt the DVMA into a known state. */
+	dregs->cond_reg |= DMA_RST_SCSI;
+	do_pause(100);
+	dregs->cond_reg &= ~(DMA_RST_SCSI);
+	if(esp->dma->revision == dvmarev2)
+		if(esp->erev != esp100)
+			dregs->cond_reg |= DMA_3CLKS;
+	else if(esp->dma->revision == dvmarev3)
+		if(esp->erev == fas236 || esp->erev == fas100a) {
+			dregs->cond_reg &= ~(DMA_3CLKS);
+			dregs->cond_reg |= DMA_2CLKS;
+		}
+	else if(esp->dma->revision == dvmaesc1)
+		dregs->cond_reg |= DMA_ADD_ENABLE;
+	DMA_INTSON(dregs);
+
+	/* Now reset the ESP chip */
+	eregs->esp_cmd = ESP_CMD_RC;
+	eregs->esp_cmd = (ESP_CMD_NULL | ESP_CMD_DMA);
+	eregs->esp_cmd = (ESP_CMD_NULL | ESP_CMD_DMA); /* borken hardware... */
+
+	/* Reload the configuration registers */
+	eregs->esp_cfg1  = esp->config1;
+	eregs->esp_cfact = esp->cfact;
+	eregs->esp_stp   = 0;
+	eregs->esp_soff  = 0;
+	eregs->esp_timeo = esp->sync_defp;
+	if(esp->erev == esp100a || esp->erev == esp236)
+		eregs->esp_cfg2 = esp->config2;
+	if(esp->erev == esp236)
+		eregs->esp_cfg3 = esp->config3[0];
+	/* Eat any bitrot in the chip */
+	trash = eregs->esp_intrpt;
+
+	/* Reset the SCSI bus, but tell ESP not to generate an irq */
+	eregs->esp_cfg1 |= ESP_CONFIG1_SRRDISAB;
+	eregs->esp_cmd = ESP_CMD_RS;
+	do_pause(200);
+	eregs->esp_cfg1 = esp->config1;
+
+	/* Eat any bitrot in the chip and we are done... */
+	trash = eregs->esp_intrpt;
+}
+
+/* Detecting ESP chips on the machine.  This is the simple and easy
+ * version.
+ */
+int esp_detect(Scsi_Host_Template *tpnt)
+{
+	struct Sparc_ESP *esp, *elink;
+	struct Scsi_Host *esp_host;
+	struct linux_sbus *sbus;
+	struct linux_sbus_device *esp_dev, *sbdev_iter;
+	struct Sparc_ESP_regs *eregs;
+	struct sparc_dma_registers *dregs;
+	struct Linux_SBus_DMA *dma, *dlink;
+	unsigned int fmhz;
+	unchar ccf, bsizes, bsizes_more;
+	int nesps = 0;
+	int esp_node;
+
+	espchain = 0;
+	if(!SBus_chain)
+		panic("No SBUS in esp_detect()");
+	for_each_sbus(sbus) {
+		for_each_sbusdev(sbdev_iter, sbus) {
+			/* Is it an esp sbus device? */
+			esp_dev = sbdev_iter;
+			if(strcmp(esp_dev->prom_name, "esp") &&
+			   strcmp(esp_dev->prom_name, "SUNW,esp")) {
+				if(!esp_dev->child ||
+				   strcmp(esp_dev->prom_name, "espdma"))
+					continue; /* nope... */
+				esp_dev = esp_dev->child;
+				if(strcmp(esp_dev->prom_name, "esp") &&
+				   strcmp(esp_dev->prom_name, "SUNW,esp"))
+					continue; /* how can this happen? */
+			}
+			esp_host = scsi_register(tpnt, sizeof(struct Sparc_ESP));
+			if(!esp_host)
+				panic("Cannot register ESP SCSI host");
+			esp = (struct Sparc_ESP *) esp_host->hostdata;
+			if(!esp)
+				panic("No esp in hostdata");
+			esp->ehost = esp_host;
+			esp->edev = esp_dev;
+			/* Put into the chain of esp chips detected */
+			if(espchain) {
+				elink = espchain;
+				while(elink->next) elink = elink->next;
+				elink->next = esp;
+			} else {
+				espchain = esp;
+			}
+			esp->next = 0;
+
+			/* Get misc. prom information */
+#define ESP_IS_MY_DVMA(esp, dma)  \
+	((esp->edev->my_bus == dma->SBus_dev->my_bus) && \
+         (esp->edev->slot == dma->SBus_dev->slot) && \
+	 (!strcmp(dma->SBus_dev->prom_name, "dma") || \
+	  !strcmp(dma->SBus_dev->prom_name, "espdma")))
+
+			esp_node = esp_dev->prom_node;
+			prom_getstring(esp_node, "name", esp->prom_name,
+				       sizeof(esp->prom_name));
+			esp->prom_node = esp_node;
+			for_each_dvma(dlink) {
+				if(ESP_IS_MY_DVMA(esp, dlink) && !dlink->allocated)
+					break;
+			}
+#undef ESP_IS_MY_DVMA
+			/* If we don't know how to handle the dvma, do not use this device */
+			if(!dlink){
+				printk ("Cannot find dvma for ESP SCSI\n");
+				scsi_unregister (esp_host);
+				continue;
+			}
+			if (dlink->allocated){
+				printk ("esp: can't use my espdma\n");
+				scsi_unregister (esp_host);
+				continue;
+			}
+			dlink->allocated = 1;
+			dma = dlink;
+			esp->dma = dma;
+			esp->dregs = dregs = dma->regs;
+
+			/* Map in the ESP registers from I/O space */
+			prom_apply_sbus_ranges(esp->edev->reg_addrs, 1);
+			esp->eregs = eregs = (struct Sparc_ESP_regs *)
+				sparc_alloc_io(esp->edev->reg_addrs[0].phys_addr, 0,
+					       PAGE_SIZE, "ESP Registers",
+					       esp->edev->reg_addrs[0].which_io, 0x0);
+			if(!eregs)
+				panic("ESP registers unmappable");
+			esp->esp_command =
+				sparc_dvma_malloc(16, "ESP DVMA Cmd Block");
+			if(!esp->esp_command)
+				panic("ESP DVMA transport area unmappable");
+
+			/* Set up the irq's etc. */
+			esp->ehost->base = (unsigned char *) esp->eregs;
+			esp->ehost->io_port = (unsigned int) esp->eregs;
+			esp->ehost->n_io_port = (unsigned char)
+				esp->edev->reg_addrs[0].reg_size;
+			/* XXX The following may be different on sun4ms XXX */
+			esp->ehost->irq = esp->irq = esp->edev->irqs[0].pri;
+
+			/* Allocate the irq only if necessary */
+			for_each_esp(elink) {
+				if((elink != esp) && (esp->irq == elink->irq)) {
+					goto esp_irq_acquired; /* BASIC rulez */
+				}
+			}
+			/* XXX We have shared interrupts per level now, maybe
+			 * XXX use them, maybe not...
+			 */
+			if(request_irq(esp->ehost->irq, esp_intr, SA_INTERRUPT,
+				       "Sparc ESP SCSI", NULL))
+				panic("Cannot acquire ESP irq line");
+esp_irq_acquired:
+			printk("esp%d: IRQ %d ", nesps, esp->ehost->irq);
+			/* Figure out our scsi ID on the bus */
+			esp->scsi_id = prom_getintdefault(esp->prom_node,
+							  "initiator-id", -1);
+			if(esp->scsi_id == -1)
+				esp->scsi_id = prom_getintdefault(esp->prom_node,
+								  "scsi-initiator-id", -1);
+			if(esp->scsi_id == -1)
+				esp->scsi_id =
+					prom_getintdefault(esp->edev->my_bus->prom_node,
+							   "scsi-initiator-id", 7);
+			esp->ehost->this_id = esp->scsi_id;
+			esp->scsi_id_mask = (1 << esp->scsi_id);
+			/* Check for differential bus */
+			esp->diff = prom_getintdefault(esp->prom_node, "differential", -1);
+			esp->diff = (esp->diff == -1) ? 0 : 1;
+			/* Check out the clock properties of the chip */
+			fmhz = prom_getintdefault(esp->prom_node, "clock-frequency", -1);
+			if(fmhz==-1)
+				fmhz = prom_getintdefault(esp->edev->my_bus->prom_node,
+							  "clock-frequency", -1);
+			if(fmhz <= (5000))
+				ccf = 0;
+			else
+				ccf = (((5000 - 1) + (fmhz))/(5000));
+			if(!ccf || ccf > 8) {
+				ccf = ESP_CCF_F4;
+				fmhz = (5000 * 4);
+			}
+			if(ccf==(ESP_CCF_F7+1))
+				esp->cfact = ESP_CCF_F0;
+			else if(ccf == ESP_CCF_NEVER)
+				esp->cfact = ESP_CCF_F2;
+			else
+				esp->cfact = ccf;
+			esp->cfreq = fmhz;
+			esp->ccycle = ((1000000000) / ((fmhz)/1000));
+			esp->ctick = ((7682 * esp->cfact * esp->ccycle)/1000);
+			esp->sync_defp = ((7682 + esp->ctick - 1) / esp->ctick);
+
+			/* XXX HACK HACK HACK XXX */
+			if (esp->sync_defp < 153)
+				esp->sync_defp = 153;
+
+			printk("SCSI ID %d  Clock %d MHz Period %2x ", esp->scsi_id,
+			       (fmhz / 1000), esp->sync_defp);
+
+			/* Find the burst sizes this dma supports. */
+			bsizes = prom_getintdefault(esp->prom_node, "burst-sizes", 0xff);
+			bsizes_more = prom_getintdefault(esp->edev->my_bus->prom_node,
+							 "burst-sizes", 0xff);
+			if(bsizes_more != 0xff) bsizes &= bsizes_more;
+			if(bsizes == 0xff || (bsizes & DMA_BURST16)==0 ||
+			   (bsizes & DMA_BURST32)==0)
+				bsizes = (DMA_BURST32 - 1);
+			esp->bursts = bsizes;
+
+			/* Probe the revision of this esp */
+			esp->config1 = (ESP_CONFIG1_PENABLE | (esp->scsi_id & 7));
+			esp->config2 = (ESP_CONFIG2_SCSI2ENAB | ESP_CONFIG2_REGPARITY);
+			esp->config3[0] = ESP_CONFIG3_TENB;
+			eregs->esp_cfg2 = esp->config2;
+			if((eregs->esp_cfg2 & ~(ESP_CONFIG2_MAGIC)) !=
+			   (ESP_CONFIG2_SCSI2ENAB | ESP_CONFIG2_REGPARITY)) {
+				printk("NCR53C90(esp100) detected\n");
+				esp->erev = esp100;
+			} else {
+				eregs->esp_cfg2 = esp->config2 = 0;
+				eregs->esp_cfg3 = 0;
+				eregs->esp_cfg3 = esp->config3[0] = 5;
+				if(eregs->esp_cfg3 != 5) {
+					printk("NCR53C90A(esp100a) detected\n");
+					esp->erev = esp100a;
+				} else {
+					int target;
+
+					for(target=0; target<8; target++)
+						esp->config3[target] = 0;
+					eregs->esp_cfg3 = 0;
+					if(esp->cfact > ESP_CCF_F5) {
+						printk("NCR53C9XF(espfast) detected\n");
+						esp->erev = fast;
+						esp->config2 |= ESP_CONFIG2_FENAB;
+						eregs->esp_cfg2 = esp->config2;
+					} else {
+						printk("NCR53C9x(esp236) detected\n");
+						esp->erev = esp236;
+						eregs->esp_cfg2 = esp->config2 = 0;
+					}
+				}
+			}				
+
+			/* Initialize the command queues */
+			esp->current_SC = 0;
+			esp->disconnected_SC = 0;
+			esp->issue_SC = 0;
+
+			/* Reset the thing before we try anything... */
+			esp_bootup_reset(esp, eregs);
+
+			nesps++;
+#ifdef THREADED_ESP_DRIVER
+			kernel_thread(esp_kernel_thread, esp, 0);
+#endif
+		} /* for each sbusdev */
+	} /* for each sbus */
+	return nesps;
+}
+
+/*
+ * The info function will return whatever useful
+ * information the developer sees fit.  If not provided, then
+ * the name field will be used instead.
+ */
+const char *esp_info(struct Scsi_Host *host)
+{
+	struct Sparc_ESP *esp;
+
+	esp = (struct Sparc_ESP *) host->hostdata;
+	switch(esp->erev) {
+	case esp100:
+		return "Sparc ESP100 (NCR53C90)";
+	case esp100a:
+		return "Sparc ESP100A (NCR53C90A)";
+	case esp236:
+		return "Sparc ESP236";
+	case fast:
+		return "Sparc ESP-FAST (236 or 100A)";
+	case fas236:
+		return "Sparc ESP236-FAST";
+	case fas100a:
+		return "Sparc ESP100A-FAST";
+	default:
+		panic("Bogon ESP revision");
+	};
+}
+
+/* Execute a SCSI command when the bus is free.   All callers
+ * turn off all interrupts, so we don't need to explicitly do
+ * it here.
+ */
+static inline void esp_exec_cmd(struct Sparc_ESP *esp)
+{
+	struct sparc_dma_registers *dregs;
+	struct Sparc_ESP_regs *eregs;
+	Scsi_Cmnd *SCptr;
+	int i;
+
+	eregs = esp->eregs;
+	dregs = esp->dregs;
+
+	/* Grab first member of the issue queue. */
+	SCptr = esp->current_SC = remove_first_SC(&esp->issue_SC);
+	if(!SCptr)
+		goto bad;
+	SCptr->SCp.phase = in_selection;
+
+	/* NCR docs say:
+	 * 1) Load select/reselect Bus ID register with target ID
+	 * 2) Load select/reselect Timeout Reg with desired value
+	 * 3) Load Synchronous offset register with zero (for
+	 *    asynchronous transfers).
+	 * 4) Load Synchronous Transfer Period register (if
+	 *    synchronous)
+	 * 5) Load FIFO with 6, 10, or 12 byte SCSI command
+	 * 6) Issue SELECTION_WITHOUT_ATTENTION command
+	 *
+	 * They also mention that a DMA NOP command must be issued
+	 * to the SCSI chip under many circumstances, plus it's
+	 * also a good idea to flush out the fifo just in case.
+	 */
+
+	/* Load zeros into COUNTER via 2 DMA NOP chip commands
+	 * due to flaky implementations of the 53C9x which don't
+	 * get the idea the first time around.
+	 */
+	dregs->cond_reg = (DMA_INT_ENAB | DMA_FIFO_INV);
+
+	eregs->esp_tclow = 0;
+	eregs->esp_tcmed = 0;
+	eregs->esp_cmd   = (ESP_CMD_NULL | ESP_CMD_DMA);
+
+	/* Flush the fifo of excess garbage. */
+	eregs->esp_cmd   = ESP_CMD_FLUSH;
+
+	/* Load bus-id and timeout values. */
+	eregs->esp_busid = (SCptr->target & 7);
+	eregs->esp_timeo = esp->sync_defp;
+
+	eregs->esp_soff  = 0; /* This means async transfer... */
+	eregs->esp_stp   = 0;
+
+	/* Load FIFO with the actual SCSI command. */
+	for(i=0; i < SCptr->cmd_len; i++)
+		eregs->esp_fdata = SCptr->cmnd[i];
+
+	/* Make sure the dvma forwards the ESP interrupt. */
+	dregs->cond_reg = DMA_INT_ENAB;
+
+	/* Tell ESP to SELECT without asserting ATN. */
+	eregs->esp_cmd = ESP_CMD_SEL;
+	return;
+
+bad:
+	panic("esp: daaarrrkk starrr crashesss....");
+}
+
+/* Queue a SCSI command delivered from the mid-level Linux SCSI code. */
+int esp_queue(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
+{
+	struct Sparc_ESP *esp;
+	unsigned long flags;
+
+	save_flags(flags); cli();
+
+	/* Set up func ptr and initial driver cmd-phase. */
+	SCpnt->scsi_done = done;
+	SCpnt->SCp.phase = not_issued;
+
+	esp = (struct Sparc_ESP *) SCpnt->host->hostdata;
+
+	/* We use the scratch area. */
+	if(!SCpnt->use_sg) {
+		SCpnt->SCp.this_residual    = SCpnt->request_bufflen;
+		SCpnt->SCp.buffer           =
+			(struct scatterlist *) SCpnt->request_buffer;
+		SCpnt->SCp.buffers_residual = 0;
+		SCpnt->SCp.Status           = CHECK_CONDITION;
+		SCpnt->SCp.Message          = 0;
+		SCpnt->SCp.have_data_in     = 0;
+		SCpnt->SCp.sent_command     = 0;
+		SCpnt->SCp.ptr = mmu_get_scsi_one((char *)SCpnt->SCp.buffer,
+						     SCpnt->SCp.this_residual,
+						     esp->edev->my_bus);
+	} else {
+#ifdef DEBUG_ESP_SG
+		printk("esp: sglist at %p with %d buffers\n",
+		       SCpnt->buffer, SCpnt->use_sg);
+#endif
+		SCpnt->SCp.buffer           = (struct scatterlist *) SCpnt->buffer;
+		SCpnt->SCp.buffers_residual = SCpnt->use_sg - 1;
+		SCpnt->SCp.this_residual    = SCpnt->SCp.buffer->length;
+		mmu_get_scsi_sgl((struct mmu_sglist *) SCpnt->SCp.buffer,
+				 SCpnt->SCp.buffers_residual,
+				 esp->edev->my_bus);
+		SCpnt->SCp.ptr              = (char *) SCpnt->SCp.buffer->alt_address;
+	}
+
+	/* Place into our queue. */
+	append_SC(&esp->issue_SC, SCpnt);
+
+	/* Run it now if we can */
+	if(!esp->current_SC)
+		esp_exec_cmd(esp);
+
+	restore_flags(flags);
+	return 0;
+}
+
+/* Only queuing supported in this ESP driver. */
+int esp_command(Scsi_Cmnd *SCpnt)
+{
+	ESPLOG(("esp: esp_command() called...\n"));
+	return -1;
+}
+
+/* Abort a command.  Those that are on the bus force a SCSI bus
+ * reset.
+ */
+int esp_abort(Scsi_Cmnd *SCpnt)
+{
+	ESPLOG(("esp_abort: Not implemented yet\n"));
+	return SCSI_ABORT_ERROR;
+}
+
+/* Reset ESP chip, reset hanging bus, then kill active and
+ * disconnected commands for targets without soft reset.
+ */
+int esp_reset(Scsi_Cmnd *SCptr, unsigned int how)
+{
+	ESPLOG(("esp_reset: Not implemented yet\n"));
+	return SCSI_RESET_ERROR;
+}
+
+/* Internal ESP done function. */
+static inline void esp_done(struct Sparc_ESP *esp, int error)
+{
+	unsigned long flags;
+	Scsi_Cmnd *done_SC;
+
+	if(esp->current_SC) {
+		/* Critical section... */
+		save_flags(flags); cli();
+		done_SC = esp->current_SC;
+		esp->current_SC = NULL;
+		/* Free dvma entry. */
+		if(!done_SC->use_sg) {
+			mmu_release_scsi_one(done_SC->SCp.ptr,
+						done_SC->SCp.this_residual,
+						esp->edev->my_bus);
+		} else {
+			struct scatterlist *scl = (struct scatterlist *)done_SC->buffer;
+#ifdef DEBUG_ESP_SG
+			printk("esp: unmapping sg ");
+#endif
+			mmu_release_scsi_sgl((struct mmu_sglist *) scl,
+					     done_SC->use_sg - 1,
+					     esp->edev->my_bus);
+#ifdef DEBUG_ESP_SG
+			printk("done.\n");
+#endif
+		}
+		done_SC->result = error;
+		if(done_SC->scsi_done)
+			done_SC->scsi_done(done_SC);
+		else
+			panic("esp: esp->current_SC->scsi_done() == NULL");
+
+		/* Bus is free, issue any commands in the queue. */
+		if(esp->issue_SC)
+			esp_exec_cmd(esp);
+
+		restore_flags(flags);
+		/* End of critical section... */
+	} else
+		panic("esp: done() called with NULL esp->current_SC");
+}
+
+#ifdef THREADED_ESP_DRIVER /* planning stage... */
+
+/* With multiple lots of commands being processed I frequently
+ * see a situation where we see galloping esp herds.  esp_done()
+ * wakes the entire world up and each interrupt causes a reschedule.
+ * This kernel thread fixes some of these unwanted effects during
+ * IO intensive activity.... I hope...
+ */
+
+static void esp_kernel_thread(void *opaque)
+{
+	struct Sparc_ESP *esp = opaque;
+
+	for(;;) {
+		unsigned long flags;
+
+		while(esp->eatme_SC) {
+			struct Scsi_Cmnd *SCpnt;
+
+			SCpnt = remove_first_SC(esp->eatme_SC);
+			esp_done(esp, error, SCpnt);
+		}
+		sleep();
+	}
+}
+#endif
+
+/* Read the interrupt status registers on this ESP board */
+static inline void esp_updatesoft(struct Sparc_ESP *esp, struct Sparc_ESP_regs *eregs)
+{
+	/* Update our software copies of the three ESP status
+	 * registers for this ESP.  Be careful, reading the
+	 * ESP interrupt register clears the status and sequence
+	 * step registers (unlatches them, you get the idea).
+	 * So read the interrupt register last.
+	 */
+
+	esp->seqreg = eregs->esp_sstep;
+	esp->sreg = eregs->esp_status;
+
+	/* Supposedly, the ESP100A and above assert the highest
+	 * bit in the status register if an interrupt is pending.
+	 * I've never seen this work properly, so let's clear it
+	 * manually while we are here.  If I see any esp chips
+	 * for which this bit is reliable I will conditionalize
+	 * this.  However, I don't see what this extra bit can
+	 * buy me with all the tests I'll have to place all over
+	 * the code to actually use it when I 'can'.  Plus the
+	 * 'pending interrupt' condition can more than reliably
+	 * be obtained from the DVMA control register.
+	 *
+	 * "Broken hardware"  -Linus
+	 */
+	esp->sreg &= (~ESP_STAT_INTR);
+	esp->ireg = eregs->esp_intrpt;   /* Must be last or we lose */
+}
+
+/* #define ESP_IRQ_TRACE */
+
+#ifdef ESP_IRQ_TRACE
+#define ETRACE(foo)  printk foo
+#else
+#define ETRACE(foo)
+#endif
+
+static char last_fflags, last_status, last_msg;
+
+/* Main interrupt handler for an esp adapter. */
+static inline void esp_handle(struct Sparc_ESP *esp)
+{
+	struct sparc_dma_registers *dregs;
+	struct Sparc_ESP_regs *eregs;
+	Scsi_Cmnd *SCptr;
+
+	eregs = esp->eregs;
+	dregs = esp->dregs;
+	SCptr = esp->current_SC;
+
+	DMA_IRQ_ENTRY(esp->dma, dregs);
+	esp_updatesoft(esp, eregs);
+
+	ETRACE(("ESPIRQ: <%2x,%2x,%2x> --> ", esp->ireg, esp->sreg, esp->seqreg));
+
+	/* Check for errors. */
+	if(!SCptr)
+		panic("esp_handle: current_SC == penguin within interrupt!");
+
+	/* At this point in time, this esp driver should not see
+	 * scsibus resets, parity errors, or gross errors unless
+	 * something truly terrible happens which we are not ready
+	 * to properly recover from yet.
+	 */
+	if((esp->ireg & (ESP_INTR_SR | ESP_INTR_IC)) ||
+	   (esp->sreg & (ESP_STAT_PERR | ESP_STAT_SPAM))) {
+		printk("esp: really bad error detected\n");
+		printk("esp: intr<%2x> stat<%2x> seq<%2x>",
+		       esp->ireg, esp->sreg, esp->seqreg);
+		printk("esp: SCptr->SCp.phase = %d\n", SCptr->SCp.phase);
+		panic("esp: cannot continue\n");
+	}
+	if(dregs->cond_reg & DMA_HNDL_ERROR) {
+		printk("esp: DMA shows an error cond_reg<%08lx> addr<%p>\n",
+		       dregs->cond_reg, dregs->st_addr);
+		printk("esp: intr<%2x> stat<%2x> seq<%2x>",
+		       esp->ireg, esp->sreg, esp->seqreg);
+		printk("esp: SCptr->SCp.phase = %d\n", SCptr->SCp.phase);
+		panic("esp: cannot continue\n");
+	}
+	if(esp->sreg & ESP_STAT_PERR) {
+		printk("esp: SCSI bus parity error\n");
+		printk("esp: intr<%2x> stat<%2x> seq<%2x>",
+		       esp->ireg, esp->sreg, esp->seqreg);
+		printk("esp: SCptr->SCp.phase = %d\n", SCptr->SCp.phase);
+		panic("esp: cannot continue\n");
+	}
+
+	/* Service interrupt. */
+	switch(SCptr->SCp.phase) {
+	case not_issued:
+		panic("Unexpected ESP interrupt, current_SC not issued.");
+		break;
+	case in_selection:
+		if(esp->ireg & ESP_INTR_RSEL) {
+			/* XXX Some day XXX */
+			panic("ESP penguin reselected in async mode.");
+		} else if(esp->ireg & ESP_INTR_DC) {
+			/* Either we are scanning the bus and no-one
+			 * lives at this target or it didn't respond.
+			 */
+			ETRACE(("DISCONNECT\n"));
+#ifdef THREADED_ESP_DRIVER
+			append_SC(esp->eatme_SC, esp->current_SC);
+			esp->current_SC = 0;
+			wake_up(esp_kernel_thread);
+#else
+			esp_done(esp, (DID_NO_CONNECT << 16));
+#endif
+			goto esp_handle_done;
+		} else if((esp->ireg & (ESP_INTR_FDONE | ESP_INTR_BSERV)) ==
+			  (ESP_INTR_FDONE | ESP_INTR_BSERV)) {
+			/* Selection successful, check the sequence step. */
+			/* XXX I know, I know... add error recovery.  XXX */
+			switch(esp->seqreg & ESP_STEP_VBITS) {
+			case ESP_STEP_NCMD:
+				panic("esp: penguin didn't enter cmd phase.");
+				break;
+			case ESP_STEP_PPC:
+				panic("esp: penguin prematurely changed from cmd phase.");
+				break;
+			case ESP_STEP_FINI:
+				/* At the completion of every command
+				 * or message-out phase, we _must_
+				 * unlatch the fifo-flags register
+				 * with an ESP nop command.
+				 */
+				eregs->esp_cmd = ESP_CMD_NULL;
+
+				/* Selection/Command sequence completed.  We
+				 * (at least for this driver) will be in
+				 * either one of the data phases or status
+				 * phase, check the status register to find
+				 * out.
+				 */
+				switch(esp->sreg & ESP_STAT_PMASK) {
+				default:
+					printk("esp: Not datain/dataout/status.\n");
+					panic("esp: penguin phase transition after selection.");
+					break;
+				case ESP_DOP:
+					/* Data out phase. */
+					dregs->cond_reg |= DMA_FIFO_INV;
+					while(dregs->cond_reg & DMA_FIFO_ISDRAIN)
+						barrier();
+					SCptr->SCp.phase = in_dataout;
+#ifdef DEBUG_ESP_SG
+					if(SCptr->use_sg)
+						printk("esp: sg-start <%p,%d>",
+						       SCptr->SCp.ptr,
+						       SCptr->SCp.this_residual);
+#endif
+					eregs->esp_tclow = SCptr->SCp.this_residual;
+					eregs->esp_tcmed = (SCptr->SCp.this_residual>>8);
+					eregs->esp_cmd = (ESP_CMD_DMA | ESP_CMD_NULL);
+
+					/* This is either the one buffer dvma ptr,
+					 * or the first one in the scatter gather
+					 * list.  Check out esp_queue to see how
+					 * this is set up.
+					 */
+					dregs->st_addr = SCptr->SCp.ptr;
+					dregs->cond_reg &= ~(DMA_ST_WRITE);
+					dregs->cond_reg |= (DMA_ENABLE | DMA_INT_ENAB);
+					eregs->esp_cmd = (ESP_CMD_DMA | ESP_CMD_TI);
+					ETRACE(("DATA_OUT\n"));
+					goto esp_handle_done;
+				case ESP_DIP:
+					/* Data in phase. */
+					dregs->cond_reg |= DMA_FIFO_INV;
+					while(dregs->cond_reg & DMA_FIFO_ISDRAIN)
+						barrier();
+					SCptr->SCp.phase = in_datain;
+#ifdef DEBUG_ESP_SG
+					if(SCptr->use_sg)
+						printk("esp: sg-start <%p,%d>",
+						       SCptr->SCp.ptr,
+						       SCptr->SCp.this_residual);
+#endif
+					eregs->esp_tclow = SCptr->SCp.this_residual;
+					eregs->esp_tcmed = (SCptr->SCp.this_residual>>8);
+					eregs->esp_cmd = (ESP_CMD_DMA | ESP_CMD_NULL);
+
+					/* This is either the one buffer dvma ptr,
+					 * or the first one in the scatter gather
+					 * list.  Check out esp_queue to see how
+					 * this is set up.
+					 */
+					dregs->st_addr = SCptr->SCp.ptr;
+					dregs->cond_reg |= (DMA_ENABLE | DMA_ST_WRITE | DMA_INT_ENAB);
+					eregs->esp_cmd = (ESP_CMD_DMA | ESP_CMD_TI);
+					ETRACE(("DATA_IN\n"));
+					goto esp_handle_done;
+				case ESP_STATP:
+					/* Status phase. */
+					SCptr->SCp.phase = in_status;
+					eregs->esp_cmd = ESP_CMD_ICCSEQ;
+					ETRACE(("STATUS\n"));
+					goto esp_handle_done; /* Wait for message. */
+				};
+			};
+		} else if(esp->ireg & ESP_INTR_FDONE) {
+			/* I'd like to investigate why this happens... */
+			ESPLOG(("esp: This is weird, halfway through "));
+			ESPLOG(("selection, trying to continue anyways.\n"));
+			goto esp_handle_done;
+		} else {
+			panic("esp: Did not get bus service during selection.");
+			goto esp_handle_done;
+		}
+		panic("esp: Mr. Potatoe Head is on the loose!");
+
+	case in_datain:
+		/* Drain the fifo for writes to memory. */
+		switch(esp->dma->revision) {
+		case dvmarev0:
+		case dvmarev1:
+		case dvmarevplus:
+		case dvmarev2:
+		case dvmarev3:
+			/* Force a drain. */
+			dregs->cond_reg |= DMA_FIFO_STDRAIN;
+
+			/* fall through */
+		case dvmaesc1:
+			/* Wait for the fifo to drain completely. */
+			while(dregs->cond_reg & DMA_FIFO_ISDRAIN)
+				barrier();
+			break;
+		};
+
+	case in_dataout:
+		dregs->cond_reg &= ~DMA_ENABLE;
+
+		/* We may be pipelining an sg-list. */
+		if(SCptr->use_sg) {
+			if(SCptr->SCp.buffers_residual) {
+				/* If we do not see a BUS SERVICE interrupt
+				 * at this point, or we see that we have left
+				 * the current data phase, then we lose.
+				 */
+				if(!(esp->ireg & ESP_INTR_BSERV) ||
+				   ((esp->sreg & ESP_STAT_PMASK) > 1))
+					panic("esp: Aiee penguin on the SCSI-bus.");
+
+				++SCptr->SCp.buffer;
+				--SCptr->SCp.buffers_residual;
+				SCptr->SCp.this_residual = SCptr->SCp.buffer->length;
+				SCptr->SCp.ptr = SCptr->SCp.buffer->alt_address;
+
+#ifdef DEBUG_ESP_SG
+				printk("<%p,%d> ", SCptr->SCp.ptr,
+				       SCptr->SCp.this_residual);
+#endif
+
+				/* Latch in new esp counters... */
+				eregs->esp_tclow = SCptr->SCp.this_residual;
+				eregs->esp_tcmed = (SCptr->SCp.this_residual>>8);
+				eregs->esp_cmd = (ESP_CMD_DMA | ESP_CMD_NULL);
+
+				/* Reload DVMA gate array with new vaddr and enab. */
+				dregs->st_addr = SCptr->SCp.ptr;
+				dregs->cond_reg |= DMA_ENABLE;
+
+				/* Tell the esp to start transferring. */
+				eregs->esp_cmd = (ESP_CMD_DMA | ESP_CMD_TI);
+				goto esp_handle_done;
+			}
+#ifdef DEBUG_ESP_SG
+			printk("done.\n");
+#endif
+		}
+		/* Take a look at what happened. */
+		if(esp->ireg & ESP_INTR_DC) {
+			panic("esp: target disconnects during data transfer.");
+			goto esp_handle_done;
+		} else if(esp->ireg & ESP_INTR_BSERV) {
+			if((esp->sreg & ESP_STAT_PMASK) != ESP_STATP) {
+				panic("esp: Not status phase after data phase.");
+				goto esp_handle_done;
+			}
+			SCptr->SCp.phase = in_status;
+			eregs->esp_cmd = ESP_CMD_ICCSEQ;
+			ETRACE(("STATUS\n"));
+			goto esp_handle_done; /* Wait for message. */
+		} else {
+			printk("esp: did not get bus service after data transfer.");
+			printk("esp_status: intr<%2x> stat<%2x> seq<%2x>\n",
+			       esp->ireg, esp->sreg, esp->seqreg);
+			panic("esp: penguin data transfer.");
+			goto esp_handle_done;
+		}
+	case in_status:
+		if(esp->ireg & ESP_INTR_DC) {
+			panic("esp: penguin disconnects in status phase.");
+			goto esp_handle_done;
+		} else if (esp->ireg & ESP_INTR_FDONE) {
+			/* Status and Message now sit in the fifo for us. */
+			last_fflags = eregs->esp_fflags;
+			SCptr->SCp.phase   = in_finale;
+			last_status = SCptr->SCp.Status  = eregs->esp_fdata;
+			last_msg = SCptr->SCp.Message = eregs->esp_fdata;
+			eregs->esp_cmd = ESP_CMD_MOK;
+			ETRACE(("FINALE\n"));
+			goto esp_handle_done;
+		} else {
+			panic("esp: penguin status phase.");
+		}
+	case in_finale:
+		if(esp->ireg & ESP_INTR_BSERV) {
+			panic("esp: penguin doesn't disconnect after status msg-ack.");
+			goto esp_handle_done;
+		} else if(esp->ireg & ESP_INTR_DC) {
+			/* Nexus is complete. */
+#ifdef THREADED_ESP_DRIVER
+			append_SC(esp->eatme_SC, esp->current_SC);
+			esp->current_SC = 0;
+			wake_up(esp_kernel_thread);
+#else
+			esp_done(esp, ((SCptr->SCp.Status & 0xff) |
+				 ((SCptr->SCp.Message & 0xff) << 8) |
+				 (DID_OK << 16)));
+#endif
+			ETRACE(("NEXUS_COMPLETE\n"));
+			goto esp_handle_done;
+		} else {
+			printk("esp: wacky state while in in_finale phase.\n");
+			printk("esp_status: intr<%2x> stat<%2x> seq<%2x>\n",
+			       esp->ireg, esp->sreg, esp->seqreg);
+			panic("esp: penguin esp state.");
+			goto esp_handle_done;
+		}
+	default:
+		panic("esp: detected penguin phase.");
+		goto esp_handle_done;
+	}
+	panic("esp: Heading to the promised land.");
+
+esp_handle_done:
+	DMA_IRQ_EXIT(esp->dma, dregs);
+	return;
+}
+
+static void esp_intr(int irq, void *dev_id, struct pt_regs *pregs)
+{
+	struct Sparc_ESP *esp;
+
+	/* Handle all ESP interrupts showing */
+	for_each_esp(esp) {
+		if(DMA_IRQ_P(esp->dregs)) {
+			esp_handle(esp);
+		}
+	}
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this