diff options
-rw-r--r-- | sys/i386/isa/wd7000.c | 745 |
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 |