freebsd-dev/sys/i386/isa/wd7000.c
Justin T. Gibbs 07b1c6b3c9 Remove hard coded assumption that SCSI busses have 7 targets.
This change forces the controller drivers to allocate a scsibus_data struct
via a call to scsi_alloc_bus(), fill in the adapter_link field, and optionally
modify any other fields of the struct.  Scsi_alloc_bus() initializes all fields
to the default, so the changes in most drivers are very minimal.  For drivers
that support Wide controllers, the maxtarg field will have to be updated to
allow probing of all targets (for an example, look at the aic7xxx driver).

Scsi_attachdevs() now takes a scsibus_data* as its argument instead of an
sc_link*.  This allows us to expand the role of the scsibus_data struct for
other bus level configuration setings (max number of transactions, current
transaction opennings, etc for better tagged queuing support).

Reviewed by: Rodney Grimes <rgrimes>, Peter Dufault <dufault>, Julian Elischer <julian>
1995-08-23 23:03:34 +00:00

759 lines
16 KiB
C

/*
* 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/clock.h>
#include <machine/cpu.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_UNCONFIGURED, /* state */
"Western Digital WD-7000 SCSI host adapter",
DC_CLS_MISC /* class */
} };
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; /* XXX WRONG! */
wds[wdsunit].addr = dev->id_iobase;
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]);
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;
}
scsi_uto3b(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);
scsi_uto3b(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);
scsi_uto3b(sxp->datalen ? KVTOPHYS(wds_data[unit]) : 0, r->cmd.data);
r->cmd.write = (sxp->flags&SCSI_DATA_IN)? 0x80 : 0x00;
scsi_uto3b(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;
scsi_uto3b(KVTOPHYS(&sxp->sense),r->sense.data);
scsi_uto3b(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 *)scsi_3btou(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;
scsi_uto3b(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;
struct scsibus_data *scbus;
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);
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;
/*
* Prepare the scsibus_data area for the upperlevel
* scsi code.
*/
scbus = scsi_alloc_bus();
if(!scbus)
return 0;
scbus->adapter_link = &wds[unit].sc_link;
kdc_wds[unit].kdc_state = DC_BUSY;
scsi_attachdevs(scbus);
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;
scsi_uto3b(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