summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/i386/isa/wd7000.c745
1 files changed, 745 insertions, 0 deletions
diff --git a/sys/i386/isa/wd7000.c b/sys/i386/isa/wd7000.c
new file mode 100644
index 0000000..44d94aa
--- /dev/null
+++ b/sys/i386/isa/wd7000.c
@@ -0,0 +1,745 @@
+/*
+ * Copyright (c) 1994 Ludd, University of Lule}, Sweden.
+ * All rights reserved.
+ *
+ * Written by Olof Johansson (offe@ludd.luth.se) 1995.
+ * Based on code written by Theo de Raadt (deraadt@fsa.ca).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed at Ludd, University of Lule}.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /* All bugs are subject to removal without further notice */
+
+/*
+ * offe 01/07/95
+ *
+ * This version of the driver _still_ doesn't implement scatter/gather for the
+ * WD7000-FASST2. This is due to the fact that my controller doesn't seem to
+ * support it. That, and the lack of documentation makes it impossible for
+ * me to implement it.
+ * What I've done instead is allocated a local buffer, contiguous buffer big
+ * enough to handle the requests. I haven't seen any read/write bigger than 64k,
+ * so I allocate a buffer of 64+16k. The data that needs to be DMA'd to/from
+ * the controller is copied to/from that buffer before/after the command is
+ * sent to the card.
+ */
+
+#include "wds.h"
+#if NWDS > 0
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/buf.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+#include <sys/dkbad.h>
+#include <sys/disklabel.h>
+
+#include <scsi/scsi_all.h>
+#include <scsi/scsiconf.h>
+#include <sys/devconf.h>
+
+#include <machine/cpu.h>
+#include <machine/cpufunc.h>
+
+#include <i386/isa/isa_device.h>
+
+static struct kern_devconf kdc_wds[NWDS] = { {
+ 0, 0, 0,
+ "wds", 0, {MDDT_ISA, 0, "bio"},
+ isa_generic_externalize, 0, 0, ISA_EXTERNALLEN,
+ &kdc_isa0,
+ 0,
+ DC_BUSY,
+ "Western Digital WD-7000 SCSI host adapter"
+} };
+
+struct scsi_device wds_dev =
+{
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "wds",
+ 0,
+ { 0, 0 }
+};
+
+/*
+ XXX THIS SHOULD BE FIXED!
+ I haven't got the KERNBASE-version to work, but on my system the kernel
+ is at virtual address 0xFxxxxxxx, responding to physical address
+ 0x0xxxxxxx.
+#define PHYSTOKV(x) ((x) + KERNBASE)
+*/
+#define PHYSTOKV(x) ((x) | 0xf0000000)
+#define KVTOPHYS(x) vtophys(x)
+/* 0x10000 (64k) should be enough. But just to be sure... */
+#define BUFSIZ 0x12000
+
+
+/* WD7000 registers */
+#define WDS_STAT 0 /* read */
+#define WDS_IRQSTAT 1 /* read */
+
+#define WDS_CMD 0 /* write */
+#define WDS_IRQACK 1 /* write */
+#define WDS_HCR 2 /* write */
+
+/* WDS_STAT (read) defs */
+#define WDS_IRQ 0x80
+#define WDS_RDY 0x40
+#define WDS_REJ 0x20
+#define WDS_INIT 0x10
+
+/* WDS_IRQSTAT (read) defs */
+#define WDSI_MASK 0xc0
+#define WDSI_ERR 0x00
+#define WDSI_MFREE 0x80
+#define WDSI_MSVC 0xc0
+
+/* WDS_CMD (write) defs */
+#define WDSC_NOOP 0x00
+#define WDSC_INIT 0x01
+#define WDSC_DISUNSOL 0x02
+#define WDSC_ENAUNSOL 0x03
+#define WDSC_IRQMFREE 0x04
+#define WDSC_SCSIRESETSOFT 0x05
+#define WDSC_SCSIRESETHARD 0x06
+#define WDSC_MSTART(m) (0x80 + (m))
+#define WDSC_MMSTART(m) (0xc0 + (m))
+
+/* WDS_HCR (write) defs */
+#define WDSH_IRQEN 0x08
+#define WDSH_DRQEN 0x04
+#define WDSH_SCSIRESET 0x02
+#define WDSH_ASCRESET 0x01
+
+struct wds_cmd {
+ u_char cmd;
+ u_char targ;
+ u_char scb[12]; /*u_char scb[12];*/
+ u_char stat;
+ u_char venderr;
+ u_char len[3];
+ u_char data[3];
+ u_char next[3];
+ u_char write;
+ u_char xx[6];
+};
+
+struct wds_req {
+ struct wds_cmd cmd;
+ struct wds_cmd sense;
+ struct scsi_xfer *sxp;
+ int busy, polled;
+ int done, ret, ombn;
+};
+
+#define WDSX_SCSICMD 0x00
+#define WDSX_OPEN_RCVBUF 0x80
+#define WDSX_RCV_CMD 0x81
+#define WDSX_RCV_DATA 0x82
+#define WDSX_RCV_DATASTAT 0x83
+#define WDSX_SND_DATA 0x84
+#define WDSX_SND_DATASTAT 0x85
+#define WDSX_SND_CMDSTAT 0x86
+#define WDSX_READINIT 0x88
+#define WDSX_READSCSIID 0x89
+#define WDSX_SETUNSOLIRQMASK 0x8a
+#define WDSX_GETUNSOLIRQMASK 0x8b
+#define WDSX_GETFIRMREV 0x8c
+#define WDSX_EXECDIAG 0x8d
+#define WDSX_SETEXECPARM 0x8e
+#define WDSX_GETEXECPARM 0x8f
+
+struct wds_mb {
+ u_char stat;
+ u_char addr[3];
+};
+/* ICMB status value */
+#define ICMB_OK 0x01
+#define ICMB_OKERR 0x02
+#define ICMB_ETIME 0x04
+#define ICMB_ERESET 0x05
+#define ICMB_ETARCMD 0x06
+#define ICMB_ERESEL 0x80
+#define ICMB_ESEL 0x81
+#define ICMB_EABORT 0x82
+#define ICMB_ESRESET 0x83
+#define ICMB_EHRESET 0x84
+
+struct wds_setup {
+ u_char cmd;
+ u_char scsi_id;
+ u_char buson_t;
+ u_char busoff_t;
+ u_char xx;
+ u_char mbaddr[3];
+ u_char nomb;
+ u_char nimb;
+};
+
+#define WDS_NOMB 8
+#define WDS_NIMB 8
+#define MAXSIMUL 8
+
+int wdsunit=0;
+
+u_char wds_data[NWDS][BUFSIZ];
+u_char wds_data_in_use[NWDS];
+
+struct wds {
+ int addr;
+ struct wds_req wdsr[MAXSIMUL];
+ struct wds_mb ombs[WDS_NOMB], imbs[WDS_NIMB];
+ struct scsi_link sc_link;
+} wds[NWDS];
+
+int wdsprobe(struct isa_device *);
+void wds_minphys(struct buf *);
+struct wds_req *wdsr_alloc(int);
+int32 wds_scsi_cmd(struct scsi_xfer *);
+u_int32 wds_adapter_info(int);
+int wdsintr(int);
+int wds_done(int, struct wds_cmd *, u_char);
+int wdsattach(struct isa_device *);
+int wds_init(struct isa_device *);
+int wds_cmd(int, u_char *, int);
+void wds_wait(int, int, int);
+
+struct isa_driver wdsdriver =
+{
+ wdsprobe,
+ wdsattach,
+ "wds"
+};
+
+struct scsi_adapter wds_switch =
+{
+ wds_scsi_cmd,
+ wds_minphys,
+ 0,
+ 0,
+ wds_adapter_info,
+ "wds",
+ {0,0}
+};
+
+int
+wdsprobe(struct isa_device *dev)
+{
+ if(wdsunit > NWDS)
+ return 0;
+
+ dev->id_unit = wdsunit;
+ wds[wdsunit].addr = dev->id_iobase;
+
+ if(wds_init(dev) != 0)
+ return 0;
+ wdsunit++;
+ return 8;
+}
+
+void
+wds_minphys(struct buf *bp)
+{
+ if(bp->b_bcount > BUFSIZ)
+ bp->b_bcount = BUFSIZ;
+}
+
+struct wds_req *
+wdsr_alloc(int unit)
+{
+ struct wds_req *r;
+ int x;
+ int i;
+
+ r = NULL;
+ x = splbio();
+ for(i=0; i<MAXSIMUL; i++)
+ if(!wds[unit].wdsr[i].busy)
+ {
+ r = &wds[unit].wdsr[i];
+ r->busy = 1;
+ break;
+ }
+ if(!r)
+ {
+ splx(x);
+ return NULL;
+ }
+
+ r->ombn = -1;
+ for(i=0; i<WDS_NOMB; i++)
+ if(!wds[unit].ombs[i].stat)
+ {
+ wds[unit].ombs[i].stat = 1;
+ r->ombn = i;
+ break;
+ }
+ if(r->ombn == -1 )
+ {
+ r->busy = 0;
+ splx(x);
+ return NULL;
+ }
+ splx(x);
+ return r;
+}
+
+int32
+wds_scsi_cmd(struct scsi_xfer *sxp)
+{
+ struct wds_req *r;
+ int unit = sxp->sc_link->adapter_unit;
+ int base;
+ u_char c, *p;
+ int i;
+
+ base = wds[unit].addr;
+
+ if( sxp->flags & SCSI_RESET)
+ {
+ printf("reset!\n");
+ return COMPLETE;
+ }
+
+ r = wdsr_alloc(unit);
+ if(r==NULL)
+ {
+ printf("no request slot available!\n");
+ sxp->error = XS_DRIVER_STUFFUP;
+ return TRY_AGAIN_LATER;
+ }
+ r->done = 0;
+ r->sxp = sxp;
+
+ if(sxp->flags & SCSI_DATA_UIO)
+ {
+ printf("UIO!\n");
+ sxp->error = XS_DRIVER_STUFFUP;
+ return TRY_AGAIN_LATER;
+ }
+
+ lto3b(KVTOPHYS(&r->cmd),wds[unit].ombs[r->ombn].addr);
+
+ bzero(&r->cmd, sizeof r->cmd);
+ r->cmd.cmd = WDSX_SCSICMD;
+ r->cmd.targ = (sxp->sc_link->target << 5) | sxp->sc_link->lun;
+ bcopy(sxp->cmd, &r->cmd.scb, sxp->cmdlen<12 ? sxp->cmdlen : 12);
+ lto3b(sxp->datalen, r->cmd.len);
+
+ if(wds_data_in_use[unit])
+ {
+ sxp->error = XS_DRIVER_STUFFUP;
+ return TRY_AGAIN_LATER;
+ }
+ else
+ wds_data_in_use[unit] = 1;
+
+ if(sxp->datalen && !(sxp->flags&SCSI_DATA_IN))
+ bcopy(sxp->data, wds_data[unit], sxp->datalen);
+
+ lto3b(sxp->datalen ? KVTOPHYS(wds_data[unit]) : 0, r->cmd.data);
+
+ r->cmd.write = (sxp->flags&SCSI_DATA_IN)? 0x80 : 0x00;
+
+ lto3b(KVTOPHYS(&r->sense),r->cmd.next);
+
+ bzero(&r->sense, sizeof r->sense);
+ r->sense.cmd = r->cmd.cmd;
+ r->sense.targ = r->cmd.targ;
+ r->sense.scb[0] = REQUEST_SENSE;
+ lto3b(KVTOPHYS(&sxp->sense),r->sense.data);
+ lto3b(sizeof(sxp->sense), r->sense.len);
+ r->sense.write = 0x80;
+
+ if(sxp->flags & SCSI_NOMASK)
+ {
+ outb(base+WDS_HCR, WDSH_DRQEN);
+ r->polled = 1;
+ } else
+ {
+ outb(base+WDS_HCR, WDSH_IRQEN|WDSH_DRQEN);
+ r->polled = 0;
+ }
+
+ c = WDSC_MSTART(r->ombn);
+
+ if( wds_cmd(base, &c, sizeof c) != 0)
+ {
+ printf("wds%d: unable to start outgoing mbox\n", unit);
+ r->busy = 0;
+ wds[unit].ombs[r->ombn].stat = 0;
+
+ return TRY_AGAIN_LATER;
+ }
+
+ if(sxp->flags & SCSI_NOMASK)
+ {
+ repoll:
+
+ i = 0;
+ while(!(inb(base+WDS_STAT) & WDS_IRQ))
+ {
+
+ DELAY(20000);
+ if(++i == 20)
+ {
+ outb(base + WDS_IRQACK, 0);
+ /*r->busy = 0;*/
+ sxp->error = XS_TIMEOUT;
+ return HAD_ERROR;
+ }
+ }
+ wdsintr(unit);
+ if(r->done)
+ {
+ r->sxp->flags |= ITSDONE;
+ r->busy = 0;
+ return r->ret;
+ }
+ goto repoll;
+ }
+
+ return SUCCESSFULLY_QUEUED;
+}
+
+u_int32
+wds_adapter_info(int unit)
+{
+ return 1;
+}
+
+int
+wdsintr(int unit)
+{
+ struct wds_cmd *pc, *vc;
+ struct wds_mb *in;
+ u_char stat;
+ u_char c;
+
+ if(!inb(wds[unit].addr+WDS_STAT) & WDS_IRQ)
+ {
+ outb(wds[unit].addr + WDS_IRQACK, 0);
+ return 1;
+ }
+
+ c = inb(wds[unit].addr + WDS_IRQSTAT);
+ if( (c&WDSI_MASK) == WDSI_MSVC)
+ {
+ c = c & ~WDSI_MASK;
+ in = &wds[unit].imbs[c];
+
+ pc = (struct wds_cmd *)_3btol(in->addr);
+ vc = (struct wds_cmd *)PHYSTOKV((long)pc);
+ stat = in->stat;
+
+ wds_done(unit, vc, stat);
+ in->stat = 0;
+
+ outb(wds[unit].addr + WDS_IRQACK, 0);
+ }
+ return 1;
+}
+
+int
+wds_done(int unit, struct wds_cmd *c, u_char stat)
+{
+ struct wds_req *r;
+ int i;
+ char slask[80];
+
+ r = (struct wds_req *)NULL;
+
+ for(i=0; i<MAXSIMUL; i++)
+ if( c == &wds[unit].wdsr[i].cmd && !wds[unit].wdsr[i].done)
+ {
+ r = &wds[unit].wdsr[i];
+ break;
+ }
+ if(r == (struct wds_req *)NULL)
+ {
+ /* failed to find request! */
+ return 1;
+ }
+
+ r->done = 1;
+ wds[unit].ombs[r->ombn].stat = 0;
+ r->ret = HAD_ERROR;
+ switch(stat)
+ {
+ case ICMB_OK:
+ r->ret = COMPLETE;
+ if(r->sxp)
+ r->sxp->resid = 0;
+ break;
+ case ICMB_OKERR:
+ if(!(r->sxp->flags & SCSI_ERR_OK) && c->stat)
+ {
+ r->sxp->sense.error_code = c->venderr;
+ r->sxp->error=XS_SENSE;
+ }
+ else
+ r->sxp->error=XS_NOERROR;
+ r->ret = COMPLETE;
+ break;
+ case ICMB_ETIME:
+ r->sxp->error = XS_TIMEOUT;
+ r->ret = HAD_ERROR;
+ break;
+ case ICMB_ERESET:
+ case ICMB_ETARCMD:
+ case ICMB_ERESEL:
+ case ICMB_ESEL:
+ case ICMB_EABORT:
+ case ICMB_ESRESET:
+ case ICMB_EHRESET:
+ r->sxp->error = XS_DRIVER_STUFFUP;
+ r->ret = HAD_ERROR;
+ break;
+ }
+
+ if(r->sxp)
+ if(r->sxp->datalen && (r->sxp->flags&SCSI_DATA_IN))
+ bcopy(wds_data[unit],r->sxp->data,r->sxp->datalen);
+
+ wds_data_in_use[unit] = 0;
+
+ if(!r->polled)
+ {
+ r->sxp->flags |= ITSDONE;
+ scsi_done(r->sxp);
+ }
+
+ r->busy = 0;
+
+ return 0;
+}
+
+int
+wds_getvers(int unit)
+{
+ struct wds_req *r;
+ int base;
+ u_char c, *p;
+ int i;
+
+ base = wds[unit].addr;
+
+ r = wdsr_alloc(unit);
+ if(!r)
+ {
+ printf("wds%d: no request slot available!\n", unit);
+ return -1;
+ }
+
+ r->done = 0;
+ r->sxp = NULL;
+
+ lto3b(KVTOPHYS(&r->cmd), wds[unit].ombs[r->ombn].addr);
+
+ bzero(&r->cmd, sizeof r->cmd);
+ r->cmd.cmd = WDSX_GETFIRMREV;
+
+ outb(base+WDS_HCR, WDSH_DRQEN);
+ r->polled = 1;
+
+ c = WDSC_MSTART(r->ombn);
+ if(wds_cmd(base, (u_char *)&c, sizeof c))
+ {
+ printf("wds%d: version request failed\n", unit);
+ r->busy = 0;
+ wds[unit].ombs[r->ombn].stat = 0;
+ return -1;
+ }
+
+ while(1)
+ {
+ i = 0;
+ while( (inb(base+WDS_STAT) & WDS_IRQ) == 0)
+ {
+ DELAY(9000);
+ if(++i == 20)
+ return -1;
+ }
+ wdsintr(unit);
+ if(r->done)
+ {
+ printf("wds%d: firmware version %d.%02d\n", unit,
+ r->cmd.targ, r->cmd.scb[0]);
+ r->busy = 0;
+ return 0;
+ }
+ }
+}
+
+int
+wdsattach(struct isa_device *dev)
+{
+ int masunit;
+ static int firstswitch[NWDS];
+ static u_long versprobe=0; /* max 32 controllers */
+ int r;
+ int unit = dev->id_unit;
+
+ masunit = dev->id_unit;
+
+ if( !(versprobe & (1<<masunit)))
+ {
+ versprobe |= (1<<masunit);
+ if(wds_getvers(masunit)==-1)
+ printf("wds%d: getvers failed\n", masunit);
+ }
+
+ printf("wds%d: using %d bytes for dma buffer\n",unit,BUFSIZ);
+
+ if(dev->id_unit)
+ kdc_wds[dev->id_unit] = kdc_wds[0];
+ kdc_wds[dev->id_unit].kdc_unit = dev->id_unit;
+ kdc_wds[dev->id_unit].kdc_parentdata = dev;
+ dev_attach(&kdc_wds[dev->id_unit]);
+
+ wds[unit].sc_link.adapter_unit = unit;
+ wds[unit].sc_link.adapter_targ = 7;
+ wds[unit].sc_link.adapter = &wds_switch;
+ wds[unit].sc_link.device = &wds_dev;
+ wds[unit].sc_link.flags = SDEV_BOUNCE;
+
+ scsi_attachdevs(&wds[unit].sc_link);
+
+ return 1;
+}
+
+int
+wds_init(struct isa_device *dev)
+{
+ struct wds_setup init;
+ int base;
+ u_char *p, c;
+ int unit, i;
+ struct wds_cmd wc;
+
+ unit = dev->id_unit;
+ base = wds[unit].addr;
+
+ /*
+ * Sending a command causes the CMDRDY bit to clear.
+ */
+
+ outb(base+WDS_CMD, WDSC_NOOP);
+ if( inb(base+WDS_STAT) & WDS_RDY)
+ return 1;
+
+ /*
+ * the controller exists. reset and init.
+ */
+ outb(base+WDS_HCR, WDSH_ASCRESET|WDSH_SCSIRESET);
+ DELAY(30);
+ outb(base+WDS_HCR, 0);
+
+ outb(base+WDS_HCR, WDSH_DRQEN);
+
+ isa_dmacascade(dev->id_drq);
+
+ if( (inb(base+WDS_STAT) & (WDS_RDY)) != WDS_RDY)
+ {
+ for(i=0; i<10; i++)
+ {
+ if( (inb(base+WDS_STAT) & (WDS_RDY)) == WDS_RDY)
+ break;
+ DELAY(40000);
+ }
+ if( (inb(base+WDS_STAT) & (WDS_RDY)) != WDS_RDY) /* probe timeout */
+ return 1;
+ }
+
+ bzero(&init, sizeof init);
+ init.cmd = WDSC_INIT;
+ init.scsi_id = 7;
+ init.buson_t = 24;
+ init.busoff_t = 48;
+ lto3b(KVTOPHYS(wds[unit].ombs), init.mbaddr);
+ init.xx = 0;
+ init.nomb = WDS_NOMB;
+ init.nimb = WDS_NIMB;
+
+ wds_wait(base+WDS_STAT, WDS_RDY, WDS_RDY);
+ if( wds_cmd(base, (u_char *)&init, sizeof init) != 0)
+ {
+ printf("wds%d: wds_cmd failed\n", unit);
+ return 1;
+ }
+
+ wds_wait(base+WDS_STAT, WDS_INIT, WDS_INIT);
+
+ wds_wait(base+WDS_STAT, WDS_RDY, WDS_RDY);
+
+ bzero(&wc,sizeof wc);
+ wc.cmd = WDSC_DISUNSOL;
+ if( wds_cmd(base, (char *)&wc, sizeof wc) != 0)
+ {
+ printf("wds%d: wds_cmd failed\n", unit);
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+wds_cmd(int base, u_char *p, int l)
+{
+ int s=splbio();
+ u_char c;
+
+ while(l--)
+ {
+ do
+ {
+ outb(base+WDS_CMD,*p);
+ wds_wait(base+WDS_STAT,WDS_RDY,WDS_RDY);
+ } while (inb(base+WDS_STAT) & WDS_REJ);
+ p++;
+ }
+
+ wds_wait(base+WDS_STAT,WDS_RDY,WDS_RDY);
+
+ splx(s);
+
+ return 0;
+}
+
+void
+wds_wait(int reg, int mask, int val)
+{
+ while((inb(reg) & mask) != val);
+}
+
+#endif
OpenPOWER on IntegriCloud