freebsd-skq/sys/dev/wds/wd7000.c
scottl 7605c12c0f Remove Giant from CAM. Drivers (SIMs) now register a mutex that CAM will
use to synchornize and protect all data objects that are used for that
SIM.  Drivers that are not yet MPSAFE register Giant and operate as
usual.  RIght now, no drivers are MPSAFE, though a few will be changed
in the coming week as this work settles down.

The driver API has changed, so all CAM drivers will need to be recompiled.
The userland API has not changed, so tools like camcontrol do not need to
be recompiled.
2007-04-15 08:49:19 +00:00

1444 lines
36 KiB
C

/*-
* Copyright (c) 1994 Ludd, University of Lule}, Sweden.
* Copyright (c) 2000 Sergey A. Babkin
* All rights reserved.
*
* Written by Olof Johansson (offe@ludd.luth.se) 1995.
* Based on code written by Theo de Raadt (deraadt@fsa.ca).
* Resurrected, ported to CAM and generally cleaned up by Sergey Babkin
* <babkin@bellatlantic.net> or <babkin@users.sourceforge.net>.
*
* 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}
* and by the FreeBSD project.
* 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.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/* 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.
*
* SB 03/30/00
*
* An intermediate buffer is needed anyway to make sure that the buffer is
* located under 16MB, otherwise it's out of reach of ISA cards. I've added
* optimizations to allocate space in buffer in fragments.
*/
/*
* Jumpers: (see The Ref(TM) for more info)
* W1/W2 - interrupt selection:
* W1 (1-2) IRQ3, (3-4) IRQ4, (5-6) IRQ5, (7-8) IRQ7, (9-10) IRQ9
* W2 (21-22) IRQ10, (19-20) IRQ11, (17-18) IRQ12, (15-16) IRQ14, (13-14) IRQ15
*
* W2 - DRQ/DACK selection, DRQ and DACK must be the same:
* (5-6) DRQ5 (11-12) DACK5
* (3-4) DRQ6 (9-10) DACK6
* (1-2) DRQ7 (7-8) DACK7
*
* W3 - I/O address selection: open pair of pins (OFF) means 1, jumpered (ON) means 0
* pair (1-2) is bit 3, ..., pair (9-10) is bit 7. All the other bits are equal
* to the value 0x300. In bitwise representation that would be:
* 0 0 1 1 (9-10) (7-8) (5-6) (3-4) (1-2) 0 0 0
* For example, address 0x3C0, bitwise 1111000000 will be represented as:
* (9-10) OFF, (7-8) OFF, (5-6) ON, (3-4) ON, (1-2) ON
*
* W4 - BIOS address: open pair of pins (OFF) means 1, jumpered (ON) means 0
* pair (1-2) is bit 13, ..., pair (7-8) is bit 16. All the other bits are
* equal to the value 0xC0000. In bitwise representation that would be:
* 1 1 0 (7-8) (5-6) (3-4) (1-2) 0 0000 0000 0000
* For example, address 0xD8000 will be represented as:
* (7-8) OFF, (5-6) OFF, (3-4) ON, (1-2) ON
*
* W98 (on newer cards) - BIOS enabled; on older cards just remove the BIOS
* chip to disable it
* W99 (on newer cards) - ROM size (1-2) OFF, (3-4) ON
*
* W5 - terminator power
* ON - host supplies term. power
* OFF - target supplies term. power
*
* W6, W9 - floppy support (a bit cryptic):
* W6 ON, W9 ON - disabled
* W6 OFF, W9 ON - enabled with HardCard only
* W6 OFF, W9 OFF - enabled with no hardCard or Combo
*
* Default: I/O 0x350, IRQ15, DMA6
*/
/*
* debugging levels:
* 0 - disabled
* 1 - print debugging messages
* 2 - collect debugging messages in an internal log buffer which can be
* printed later by calling wds_printlog from DDB
*
* Both kind of logs are heavy and interact significantly with the timing
* of commands, so the observed problems may become invisible if debug
* logging is enabled.
*
* The light-weight logging facility may be enabled by defining
* WDS_ENABLE_SMALLOG as 1. It has very little overhead and allows observing
* the traces of various race conditions without affectiong them but the log is
* quite terse. The small log can be printer from DDB by calling
* wds_printsmallog.
*/
#ifndef WDS_DEBUG
#define WDS_DEBUG 0
#endif
#ifndef WDS_ENABLE_SMALLOG
#define WDS_ENABLE_SMALLOG 0
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/assym.h>
#include <sys/malloc.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/cam_sim.h>
#include <cam/cam_xpt_sim.h>
#include <cam/cam_debug.h>
#include <cam/scsi/scsi_all.h>
#include <cam/scsi/scsi_message.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/pmap.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <isa/isavar.h>
#include <isa/pnpvar.h>
#define WDSTOPHYS(wp, a) ( ((u_long)a) - ((u_long)wp->dx) + ((u_long)wp->dx_p) )
#define WDSTOVIRT(wp, a) ( ((char *)a) - ((char*)wp->dx_p) + ((char *)wp->dx) )
/* 0x10000 (64k) should be enough. But just to be sure... */
#define BUFSIZ 0x12000
/* buffer fragment size, no more than 32 frags per buffer */
#define FRAGSIZ 0x1000
/* 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 */
#define WDS_NPORTS 4 /* number of ports used */
/* 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 /* disable unsolicited ints */
#define WDSC_ENAUNSOL 0x03 /* enable unsolicited ints */
#define WDSC_IRQMFREE 0x04 /* interrupt on free RQM */
#define WDSC_SCSIRESETSOFT 0x05 /* soft reset */
#define WDSC_SCSIRESETHARD 0x06 /* hard reset ack */
#define WDSC_MSTART(m) (0x80 + (m)) /* start mailbox */
#define WDSC_MMSTART(m) (0xc0 + (m)) /* start all mailboxes */
/* WDS_HCR (write) defs */
#define WDSH_IRQEN 0x08
#define WDSH_DRQEN 0x04
#define WDSH_SCSIRESET 0x02
#define WDSH_ASCRESET 0x01
struct wds_cmd {
u_int8_t cmd;
u_int8_t targ;
u_int8_t scb[12];
u_int8_t stat;
u_int8_t venderr;
u_int8_t len[3];
u_int8_t data[3];
u_int8_t next[3];
u_int8_t write;
u_int8_t xx[6];
};
struct wds_req {
struct wds_cmd cmd;
union ccb *ccb;
enum {
WR_DONE = 0x01,
WR_SENSE = 0x02
} flags;
u_int8_t *buf; /* address of linear data buffer */
u_int32_t mask; /* mask of allocated fragments */
u_int8_t ombn;
u_int8_t id; /* number of request */
};
#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_int8_t stat;
u_int8_t 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_int8_t cmd;
u_int8_t scsi_id;
u_int8_t buson_t;
u_int8_t busoff_t;
u_int8_t xx;
u_int8_t mbaddr[3];
u_int8_t nomb;
u_int8_t nimb;
};
/* the code depends on equality of these parameters */
#define MAXSIMUL 8
#define WDS_NOMB MAXSIMUL
#define WDS_NIMB MAXSIMUL
static int fragsiz;
static int nfrags;
/* structure for data exchange with controller */
struct wdsdx {
struct wds_req req[MAXSIMUL];
struct wds_mb ombs[MAXSIMUL];
struct wds_mb imbs[MAXSIMUL];
u_int8_t data[BUFSIZ];
};
/* structure softc */
struct wds {
device_t dev;
int unit;
int addr;
int drq;
struct cam_sim *sim; /* SIM descriptor for this card */
struct cam_path *path; /* wildcard path for this card */
char want_wdsr; /* resource shortage flag */
u_int32_t data_free;
u_int32_t wdsr_free;
struct wdsdx *dx;
struct wdsdx *dx_p; /* physical address */
struct resource *port_r;
int port_rid;
struct resource *drq_r;
int drq_rid;
struct resource *intr_r;
int intr_rid;
void *intr_cookie;
bus_dma_tag_t bustag;
bus_dmamap_t busmap;
};
#define ccb_wdsr spriv_ptr1 /* for wds request */
static int wds_probe(device_t dev);
static int wds_attach(device_t dev);
static void wds_intr(struct wds *wp);
static void wds_action(struct cam_sim * sim, union ccb * ccb);
static void wds_poll(struct cam_sim * sim);
static int wds_preinit(struct wds *wp);
static int wds_init(struct wds *wp);
static void wds_alloc_callback(void *arg, bus_dma_segment_t *seg,
int nseg, int error);
static void wds_free_resources(struct wds *wp);
static struct wds_req *wdsr_alloc(struct wds *wp);
static void wds_scsi_io(struct cam_sim * sim, struct ccb_scsiio * csio);
static void wdsr_ccb_done(struct wds *wp, struct wds_req *r,
union ccb *ccb, u_int32_t status);
static void wds_done(struct wds *wp, struct wds_req *r, u_int8_t stat);
static int wds_runsense(struct wds *wp, struct wds_req *r);
static int wds_getvers(struct wds *wp);
static int wds_cmd(int base, u_int8_t * p, int l);
static void wds_wait(int reg, int mask, int val);
static struct wds_req *cmdtovirt(struct wds *wp, u_int32_t phys);
static u_int32_t frag_alloc(struct wds *wp, int size, u_int8_t **res,
u_int32_t *maskp);
static void frag_free(struct wds *wp, u_int32_t mask);
void wds_print(void);
#if WDS_ENABLE_SMALLOG==1
static __inline void smallog(char c);
void wds_printsmallog(void);
#endif /* SMALLOG */
/* SCSI ID of the adapter itself */
#ifndef WDS_HBA_ID
#define WDS_HBA_ID 7
#endif
#if WDS_DEBUG == 2
#define LOGLINESIZ 81
#define NLOGLINES 300
#define DBX wds_nextlog(), LOGLINESIZ,
#define DBG snprintf
static char wds_log[NLOGLINES][LOGLINESIZ];
static int logwrite = 0, logread = 0;
static char *wds_nextlog(void);
void wds_printlog(void);
#elif WDS_DEBUG != 0
#define DBX
#define DBG printf
#else
#define DBX
#define DBG if(0) printf
#endif
/* the table of supported bus methods */
static device_method_t wds_isa_methods[] = {
DEVMETHOD(device_probe, wds_probe),
DEVMETHOD(device_attach, wds_attach),
{ 0, 0 }
};
static driver_t wds_isa_driver = {
"wds",
wds_isa_methods,
sizeof(struct wds),
};
static devclass_t wds_devclass;
DRIVER_MODULE(wds, isa, wds_isa_driver, wds_devclass, 0, 0);
MODULE_DEPEND(wds, isa, 1, 1, 1);
MODULE_DEPEND(wds, cam, 1, 1, 1);
#if WDS_ENABLE_SMALLOG==1
#define SMALLOGSIZ 512
static char wds_smallog[SMALLOGSIZ];
static char *wds_smallogp = wds_smallog;
static char wds_smallogover = 0;
static __inline void
smallog(char c)
{
*wds_smallogp = c;
if (++wds_smallogp == &wds_smallog[SMALLOGSIZ]) {
wds_smallogp = wds_smallog;
wds_smallogover = 1;
}
}
#define smallog2(a, b) (smallog(a), smallog(b))
#define smallog3(a, b, c) (smallog(a), smallog(b), smallog(c))
#define smallog4(a, b, c, d) (smallog(a),smallog(b),smallog(c),smallog(d))
void
wds_printsmallog(void)
{
int i;
char *p;
printf("wds: ");
p = wds_smallogover ? wds_smallogp : wds_smallog;
i = 0;
do {
printf("%c", *p);
if (++p == &wds_smallog[SMALLOGSIZ])
p = wds_smallog;
if (++i == 70) {
i = 0;
printf("\nwds: ");
}
} while (p != wds_smallogp);
printf("\n");
}
#else
#define smallog(a)
#define smallog2(a, b)
#define smallog3(a, b, c)
#define smallog4(a, b, c, d)
#endif /* SMALLOG */
static int
wds_probe(device_t dev)
{
struct wds *wp;
int error = 0;
int irq;
/* No pnp support */
if (isa_get_vendorid(dev))
return (ENXIO);
wp = (struct wds *) device_get_softc(dev);
wp->unit = device_get_unit(dev);
wp->dev = dev;
wp->addr = bus_get_resource_start(dev, SYS_RES_IOPORT, 0 /*rid*/);
if (wp->addr == 0 || wp->addr <0x300
|| wp->addr > 0x3f8 || wp->addr & 0x7) {
device_printf(dev, "invalid port address 0x%x\n", wp->addr);
return (ENXIO);
}
if (bus_set_resource(dev, SYS_RES_IOPORT, 0, wp->addr, WDS_NPORTS) < 0)
return (ENXIO);
/* get the DRQ */
wp->drq = bus_get_resource_start(dev, SYS_RES_DRQ, 0 /*rid*/);
if (wp->drq < 5 || wp->drq > 7) {
device_printf(dev, "invalid DRQ %d\n", wp->drq);
return (ENXIO);
}
/* get the IRQ */
irq = bus_get_resource_start(dev, SYS_RES_IRQ, 0 /*rid*/);
if (irq < 3) {
device_printf(dev, "invalid IRQ %d\n", irq);
return (ENXIO);
}
wp->port_rid = 0;
wp->port_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &wp->port_rid,
/*start*/ 0, /*end*/ ~0,
/*count*/ 0, RF_ACTIVE);
if (wp->port_r == NULL)
return (ENXIO);
error = wds_preinit(wp);
/*
* We cannot hold resources between probe and
* attach as we may never be attached.
*/
wds_free_resources(wp);
return (error);
}
static int
wds_attach(device_t dev)
{
struct wds *wp;
struct cam_devq *devq;
struct cam_sim *sim;
struct cam_path *pathp;
int i;
int error = 0;
wp = (struct wds *)device_get_softc(dev);
wp->port_rid = 0;
wp->port_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &wp->port_rid,
/*start*/ 0, /*end*/ ~0,
/*count*/ 0, RF_ACTIVE);
if (wp->port_r == NULL)
return (ENXIO);
/* We must now release resources on error. */
wp->drq_rid = 0;
wp->drq_r = bus_alloc_resource(dev, SYS_RES_DRQ, &wp->drq_rid,
/*start*/ 0, /*end*/ ~0,
/*count*/ 0, RF_ACTIVE);
if (wp->drq_r == NULL)
goto bad;
wp->intr_rid = 0;
wp->intr_r = bus_alloc_resource(dev, SYS_RES_IRQ, &wp->intr_rid,
/*start*/ 0, /*end*/ ~0,
/*count*/ 0, RF_ACTIVE);
if (wp->intr_r == NULL)
goto bad;
error = bus_setup_intr(dev, wp->intr_r, INTR_TYPE_CAM | INTR_ENTROPY,
NULL, (driver_intr_t *)wds_intr, (void *)wp,
&wp->intr_cookie);
if (error)
goto bad;
/* now create the memory buffer */
error = bus_dma_tag_create(NULL, /*alignment*/4,
/*boundary*/0,
/*lowaddr*/BUS_SPACE_MAXADDR_24BIT,
/*highaddr*/ BUS_SPACE_MAXADDR,
/*filter*/ NULL, /*filterarg*/ NULL,
/*maxsize*/ sizeof(* wp->dx),
/*nsegments*/ 1,
/*maxsegsz*/ sizeof(* wp->dx), /*flags*/ 0,
/*lockfunc*/busdma_lock_mutex,
/*lockarg*/&Giant,
&wp->bustag);
if (error)
goto bad;
error = bus_dmamem_alloc(wp->bustag, (void **)&wp->dx,
/*flags*/ 0, &wp->busmap);
if (error)
goto bad;
bus_dmamap_load(wp->bustag, wp->busmap, (void *)wp->dx,
sizeof(* wp->dx), wds_alloc_callback,
(void *)&wp->dx_p, /*flags*/0);
/* initialize the wds_req structures on this unit */
for(i=0; i<MAXSIMUL; i++) {
wp->dx->req[i].id = i;
wp->wdsr_free |= 1<<i;
}
/* initialize the memory buffer allocation for this unit */
if (BUFSIZ / FRAGSIZ > 32) {
fragsiz = (BUFSIZ / 32) & ~0x01; /* keep it word-aligned */
device_printf(dev, "data buffer fragment size too small. "
"BUFSIZE / FRAGSIZE must be <= 32\n");
} else
fragsiz = FRAGSIZ & ~0x01; /* keep it word-aligned */
wp->data_free = 0;
nfrags = 0;
for (i = fragsiz; i <= BUFSIZ; i += fragsiz) {
nfrags++;
wp->data_free = (wp->data_free << 1) | 1;
}
/* complete the hardware initialization */
if (wds_init(wp) != 0)
goto bad;
if (wds_getvers(wp) == -1)
device_printf(dev, "getvers failed\n");
device_printf(dev, "using %d bytes / %d frags for dma buffer\n",
BUFSIZ, nfrags);
devq = cam_simq_alloc(MAXSIMUL);
if (devq == NULL)
goto bad;
sim = cam_sim_alloc(wds_action, wds_poll, "wds", (void *) wp,
wp->unit, &Giant, 1, 1, devq);
if (sim == NULL) {
cam_simq_free(devq);
goto bad;
}
wp->sim = sim;
if (xpt_bus_register(sim, 0) != CAM_SUCCESS) {
cam_sim_free(sim, /* free_devq */ TRUE);
goto bad;
}
if (xpt_create_path(&pathp, /* periph */ NULL,
cam_sim_path(sim), CAM_TARGET_WILDCARD,
CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
xpt_bus_deregister(cam_sim_path(sim));
cam_sim_free(sim, /* free_devq */ TRUE);
goto bad;
}
wp->path = pathp;
return (0);
bad:
wds_free_resources(wp);
if (error)
return (error);
else /* exact error is unknown */
return (ENXIO);
}
/* callback to save the physical address */
static void
wds_alloc_callback(void *arg, bus_dma_segment_t *seg, int nseg, int error)
{
*(bus_addr_t *)arg = seg[0].ds_addr;
}
static void
wds_free_resources(struct wds *wp)
{
/* check every resource and free if not zero */
/* interrupt handler */
if (wp->intr_r) {
bus_teardown_intr(wp->dev, wp->intr_r, wp->intr_cookie);
bus_release_resource(wp->dev, SYS_RES_IRQ, wp->intr_rid,
wp->intr_r);
wp->intr_r = 0;
}
/* all kinds of memory maps we could have allocated */
if (wp->dx_p) {
bus_dmamap_unload(wp->bustag, wp->busmap);
wp->dx_p = 0;
}
if (wp->dx) { /* wp->busmap may be legitimately equal to 0 */
/* the map will also be freed */
bus_dmamem_free(wp->bustag, wp->dx, wp->busmap);
wp->dx = 0;
}
if (wp->bustag) {
bus_dma_tag_destroy(wp->bustag);
wp->bustag = 0;
}
/* release all the bus resources */
if (wp->drq_r) {
bus_release_resource(wp->dev, SYS_RES_DRQ,
wp->drq_rid, wp->drq_r);
wp->drq_r = 0;
}
if (wp->port_r) {
bus_release_resource(wp->dev, SYS_RES_IOPORT,
wp->port_rid, wp->port_r);
wp->port_r = 0;
}
}
/* allocate contiguous fragments from the buffer */
static u_int32_t
frag_alloc(struct wds *wp, int size, u_int8_t **res, u_int32_t *maskp)
{
int i;
u_int32_t mask;
u_int32_t free;
if (size > fragsiz * nfrags)
return (CAM_REQ_TOO_BIG);
mask = 1; /* always allocate at least 1 fragment */
for (i = fragsiz; i < size; i += fragsiz)
mask = (mask << 1) | 1;
free = wp->data_free;
if(free != 0) {
i = ffs(free)-1; /* ffs counts bits from 1 */
for (mask <<= i; i < nfrags; i++) {
if ((free & mask) == mask) {
wp->data_free &= ~mask; /* mark frags as busy */
*maskp = mask;
*res = &wp->dx->data[fragsiz * i];
DBG(DBX "wds%d: allocated buffer mask=0x%x\n",
wp->unit, mask);
return (CAM_REQ_CMP);
}
if (mask & 0x80000000)
break;
mask <<= 1;
}
}
return (CAM_REQUEUE_REQ); /* no free memory now, try later */
}
static void
frag_free(struct wds *wp, u_int32_t mask)
{
wp->data_free |= mask; /* mark frags as free */
DBG(DBX "wds%d: freed buffer mask=0x%x\n", wp->unit, mask);
}
static struct wds_req *
wdsr_alloc(struct wds *wp)
{
struct wds_req *r;
int x;
int i;
r = NULL;
x = splcam();
/* anyway most of the time only 1 or 2 commands will
* be active because SCSI disconnect is not supported
* by hardware, so the search should be fast enough
*/
i = ffs(wp->wdsr_free) - 1;
if(i < 0) {
splx(x);
return (NULL);
}
wp->wdsr_free &= ~ (1<<i);
r = &wp->dx->req[i];
r->flags = 0; /* reset all flags */
r->ombn = i; /* luckily we have one omb per wdsr */
wp->dx->ombs[i].stat = 1;
r->mask = 0;
splx(x);
smallog3('r', i + '0', r->ombn + '0');
return (r);
}
static void
wds_intr(struct wds *wp)
{
struct wds_req *rp;
struct wds_mb *in;
u_int8_t stat;
u_int8_t c;
int addr = wp->addr;
DBG(DBX "wds%d: interrupt [\n", wp->unit);
smallog('[');
if (inb(addr + WDS_STAT) & WDS_IRQ) {
c = inb(addr + WDS_IRQSTAT);
if ((c & WDSI_MASK) == WDSI_MSVC) {
c = c & ~WDSI_MASK;
in = &wp->dx->imbs[c];
rp = cmdtovirt(wp, scsi_3btoul(in->addr));
stat = in->stat;
if (rp != NULL)
wds_done(wp, rp, stat);
else
device_printf(wp->dev,
"got weird command address %p"
"from controller\n", rp);
in->stat = 0;
} else
device_printf(wp->dev,
"weird interrupt, irqstat=0x%x\n", c);
outb(addr + WDS_IRQACK, 0);
} else {
smallog('?');
}
smallog(']');
DBG(DBX "wds%d: ]\n", wp->unit);
}
static void
wds_done(struct wds *wp, struct wds_req *r, u_int8_t stat)
{
struct ccb_hdr *ccb_h;
struct ccb_scsiio *csio;
int status;
smallog('d');
if (r->flags & WR_DONE) {
device_printf(wp->dev,
"request %d reported done twice\n", r->id);
smallog2('x', r->id + '0');
return;
}
smallog(r->id + '0');
ccb_h = &r->ccb->ccb_h;
csio = &r->ccb->csio;
status = CAM_REQ_CMP_ERR;
DBG(DBX "wds%d: %s stat=0x%x c->stat=0x%x c->venderr=0x%x\n", wp->unit,
r->flags & WR_SENSE ? "(sense)" : "",
stat, r->cmd.stat, r->cmd.venderr);
if (r->flags & WR_SENSE) {
if (stat == ICMB_OK || (stat == ICMB_OKERR && r->cmd.stat == 0)) {
DBG(DBX "wds%d: sense 0x%x\n", wp->unit, r->buf[0]);
/* it has the same size now but for future */
bcopy(r->buf, &csio->sense_data,
sizeof(struct scsi_sense_data) > csio->sense_len ?
csio->sense_len : sizeof(struct scsi_sense_data));
if (sizeof(struct scsi_sense_data) >= csio->sense_len)
csio->sense_resid = 0;
else
csio->sense_resid =
csio->sense_len
- sizeof(struct scsi_sense_data);
status = CAM_AUTOSNS_VALID | CAM_SCSI_STATUS_ERROR;
} else {
status = CAM_AUTOSENSE_FAIL;
}
} else {
switch (stat) {
case ICMB_OK:
if (ccb_h) {
csio->resid = 0;
csio->scsi_status = r->cmd.stat;
status = CAM_REQ_CMP;
}
break;
case ICMB_OKERR:
if (ccb_h) {
csio->scsi_status = r->cmd.stat;
if (r->cmd.stat) {
if (ccb_h->flags & CAM_DIS_AUTOSENSE)
status = CAM_SCSI_STATUS_ERROR;
else {
if ( wds_runsense(wp, r) == CAM_REQ_CMP )
return;
/* in case of error continue with freeing of CCB */
}
} else {
csio->resid = 0;
status = CAM_REQ_CMP;
}
}
break;
case ICMB_ETIME:
if (ccb_h)
status = CAM_SEL_TIMEOUT;
break;
case ICMB_ERESET:
case ICMB_ETARCMD:
case ICMB_ERESEL:
case ICMB_ESEL:
case ICMB_EABORT:
case ICMB_ESRESET:
case ICMB_EHRESET:
if (ccb_h)
status = CAM_REQ_CMP_ERR;
break;
}
if (ccb_h && (ccb_h->flags & CAM_DIR_MASK) == CAM_DIR_IN) {
/* we accept only virtual addresses in wds_action() */
bcopy(r->buf, csio->data_ptr, csio->dxfer_len);
}
}
r->flags |= WR_DONE;
wp->dx->ombs[r->ombn].stat = 0;
if (ccb_h) {
wdsr_ccb_done(wp, r, r->ccb, status);
smallog3('-', ccb_h->target_id + '0', ccb_h->target_lun + '0');
} else {
frag_free(wp, r->mask);
if (wp->want_wdsr) {
wp->want_wdsr = 0;
xpt_release_simq(wp->sim, /* run queue */ 1);
}
wp->wdsr_free |= (1 << r->id);
}
DBG(DBX "wds%d: request %p done\n", wp->unit, r);
}
/* command returned bad status, request sense */
static int
wds_runsense(struct wds *wp, struct wds_req *r)
{
u_int8_t c;
struct ccb_hdr *ccb_h;
ccb_h = &r->ccb->ccb_h;
r->flags |= WR_SENSE;
scsi_ulto3b(WDSTOPHYS(wp, &r->cmd),
wp->dx->ombs[r->ombn].addr);
bzero(&r->cmd, sizeof r->cmd);
r->cmd.cmd = WDSX_SCSICMD;
r->cmd.targ = (ccb_h->target_id << 5) |
ccb_h->target_lun;
scsi_ulto3b(0, r->cmd.next);
r->cmd.scb[0] = REQUEST_SENSE;
r->cmd.scb[1] = ccb_h->target_lun << 5;
r->cmd.scb[4] = sizeof(struct scsi_sense_data);
r->cmd.scb[5] = 0;
scsi_ulto3b(WDSTOPHYS(wp, r->buf), r->cmd.data);
scsi_ulto3b(sizeof(struct scsi_sense_data), r->cmd.len);
r->cmd.write = 0x80;
outb(wp->addr + WDS_HCR, WDSH_IRQEN | WDSH_DRQEN);
wp->dx->ombs[r->ombn].stat = 1;
c = WDSC_MSTART(r->ombn);
if (wds_cmd(wp->addr, &c, sizeof c) != 0) {
device_printf(wp->dev, "unable to start outgoing sense mbox\n");
wp->dx->ombs[r->ombn].stat = 0;
wdsr_ccb_done(wp, r, r->ccb, CAM_AUTOSENSE_FAIL);
return CAM_AUTOSENSE_FAIL;
} else {
DBG(DBX "wds%d: enqueued status cmd 0x%x, r=%p\n",
wp->unit, r->cmd.scb[0] & 0xFF, r);
/* don't free CCB yet */
smallog3('*', ccb_h->target_id + '0',
ccb_h->target_lun + '0');
return CAM_REQ_CMP;
}
}
static int
wds_getvers(struct wds *wp)
{
struct wds_req *r;
int base;
u_int8_t c;
int i;
base = wp->addr;
r = wdsr_alloc(wp);
if (!r) {
device_printf(wp->dev, "no request slot available!\n");
return (-1);
}
r->flags &= ~WR_DONE;
r->ccb = NULL;
scsi_ulto3b(WDSTOPHYS(wp, &r->cmd), wp->dx->ombs[r->ombn].addr);
bzero(&r->cmd, sizeof r->cmd);
r->cmd.cmd = WDSX_GETFIRMREV;
outb(base + WDS_HCR, WDSH_DRQEN);
c = WDSC_MSTART(r->ombn);
if (wds_cmd(base, (u_int8_t *) & c, sizeof c)) {
device_printf(wp->dev, "version request failed\n");
wp->wdsr_free |= (1 << r->id);
wp->dx->ombs[r->ombn].stat = 0;
return (-1);
}
while (1) {
i = 0;
while ((inb(base + WDS_STAT) & WDS_IRQ) == 0) {
DELAY(9000);
if (++i == 100) {
device_printf(wp->dev, "getvers timeout\n");
return (-1);
}
}
wds_intr(wp);
if (r->flags & WR_DONE) {
device_printf(wp->dev, "firmware version %d.%02d\n",
r->cmd.targ, r->cmd.scb[0]);
wp->wdsr_free |= (1 << r->id);
return (0);
}
}
}
static void
wdsr_ccb_done(struct wds *wp, struct wds_req *r,
union ccb *ccb, u_int32_t status)
{
ccb->ccb_h.ccb_wdsr = 0;
if (r != NULL) {
/* To implement timeouts we would need to know how to abort the
* command on controller, and this is a great mystery.
* So for now we just pass the responsibility for timeouts
* to the controlles itself, it does that reasonably good.
*/
/* untimeout(_timeout, (caddr_t) hcb, ccb->ccb_h.timeout_ch); */
/* we're about to free a hcb, so the shortage has ended */
frag_free(wp, r->mask);
if (wp->want_wdsr && status != CAM_REQUEUE_REQ) {
wp->want_wdsr = 0;
status |= CAM_RELEASE_SIMQ;
smallog('R');
}
wp->wdsr_free |= (1 << r->id);
}
ccb->ccb_h.status =
status | (ccb->ccb_h.status & ~(CAM_STATUS_MASK | CAM_SIM_QUEUED));
xpt_done(ccb);
}
static void
wds_scsi_io(struct cam_sim * sim, struct ccb_scsiio * csio)
{
int unit = cam_sim_unit(sim);
struct wds *wp;
struct ccb_hdr *ccb_h;
struct wds_req *r;
int base;
u_int8_t c;
int error;
int n;
wp = (struct wds *)cam_sim_softc(sim);
ccb_h = &csio->ccb_h;
DBG(DBX "wds%d: cmd TARG=%d LUN=%d\n", unit, ccb_h->target_id,
ccb_h->target_lun);
if (ccb_h->target_id > 7 || ccb_h->target_id == WDS_HBA_ID) {
ccb_h->status = CAM_TID_INVALID;
xpt_done((union ccb *) csio);
return;
}
if (ccb_h->target_lun > 7) {
ccb_h->status = CAM_LUN_INVALID;
xpt_done((union ccb *) csio);
return;
}
if (csio->dxfer_len > BUFSIZ) {
ccb_h->status = CAM_REQ_TOO_BIG;
xpt_done((union ccb *) csio);
return;
}
if (ccb_h->flags & (CAM_CDB_PHYS | CAM_SCATTER_VALID | CAM_DATA_PHYS)) {
/* don't support these */
ccb_h->status = CAM_REQ_INVALID;
xpt_done((union ccb *) csio);
return;
}
base = wp->addr;
/*
* this check is mostly for debugging purposes,
* "can't happen" normally.
*/
if(wp->want_wdsr) {
DBG(DBX "wds%d: someone already waits for buffer\n", unit);
smallog('b');
n = xpt_freeze_simq(sim, /* count */ 1);
smallog('0'+n);
ccb_h->status = CAM_REQUEUE_REQ;
xpt_done((union ccb *) csio);
return;
}
r = wdsr_alloc(wp);
if (r == NULL) {
device_printf(wp->dev, "no request slot available!\n");
wp->want_wdsr = 1;
n = xpt_freeze_simq(sim, /* count */ 1);
smallog2('f', '0'+n);
ccb_h->status = CAM_REQUEUE_REQ;
xpt_done((union ccb *) csio);
return;
}
ccb_h->ccb_wdsr = (void *) r;
r->ccb = (union ccb *) csio;
switch (error = frag_alloc(wp, csio->dxfer_len, &r->buf, &r->mask)) {
case CAM_REQ_CMP:
break;
case CAM_REQUEUE_REQ:
DBG(DBX "wds%d: no data buffer available\n", unit);
wp->want_wdsr = 1;
n = xpt_freeze_simq(sim, /* count */ 1);
smallog2('f', '0'+n);
wdsr_ccb_done(wp, r, r->ccb, CAM_REQUEUE_REQ);
return;
default:
DBG(DBX "wds%d: request is too big\n", unit);
wdsr_ccb_done(wp, r, r->ccb, error);
break;
}
ccb_h->status |= CAM_SIM_QUEUED;
r->flags &= ~WR_DONE;
scsi_ulto3b(WDSTOPHYS(wp, &r->cmd), wp->dx->ombs[r->ombn].addr);
bzero(&r->cmd, sizeof r->cmd);
r->cmd.cmd = WDSX_SCSICMD;
r->cmd.targ = (ccb_h->target_id << 5) | ccb_h->target_lun;
if (ccb_h->flags & CAM_CDB_POINTER)
bcopy(csio->cdb_io.cdb_ptr, &r->cmd.scb,
csio->cdb_len < 12 ? csio->cdb_len : 12);
else
bcopy(csio->cdb_io.cdb_bytes, &r->cmd.scb,
csio->cdb_len < 12 ? csio->cdb_len : 12);
scsi_ulto3b(csio->dxfer_len, r->cmd.len);
if (csio->dxfer_len > 0
&& (ccb_h->flags & CAM_DIR_MASK) == CAM_DIR_OUT) {
/* we already rejected physical or scattered addresses */
bcopy(csio->data_ptr, r->buf, csio->dxfer_len);
}
scsi_ulto3b(csio->dxfer_len ? WDSTOPHYS(wp, r->buf) : 0, r->cmd.data);
if ((ccb_h->flags & CAM_DIR_MASK) == CAM_DIR_IN)
r->cmd.write = 0x80;
else
r->cmd.write = 0x00;
scsi_ulto3b(0, r->cmd.next);
outb(base + WDS_HCR, WDSH_IRQEN | WDSH_DRQEN);
c = WDSC_MSTART(r->ombn);
if (wds_cmd(base, &c, sizeof c) != 0) {
device_printf(wp->dev, "unable to start outgoing mbox\n");
wp->dx->ombs[r->ombn].stat = 0;
wdsr_ccb_done(wp, r, r->ccb, CAM_RESRC_UNAVAIL);
return;
}
DBG(DBX "wds%d: enqueued cmd 0x%x, r=%p\n", unit,
r->cmd.scb[0] & 0xFF, r);
smallog3('+', ccb_h->target_id + '0', ccb_h->target_lun + '0');
}
static void
wds_action(struct cam_sim * sim, union ccb * ccb)
{
int unit = cam_sim_unit(sim);
int s;
DBG(DBX "wds%d: action 0x%x\n", unit, ccb->ccb_h.func_code);
switch (ccb->ccb_h.func_code) {
case XPT_SCSI_IO:
s = splcam();
DBG(DBX "wds%d: SCSI IO entered\n", unit);
wds_scsi_io(sim, &ccb->csio);
DBG(DBX "wds%d: SCSI IO returned\n", unit);
splx(s);
break;
case XPT_RESET_BUS:
/* how to do it right ? */
printf("wds%d: reset\n", unit);
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
case XPT_ABORT:
ccb->ccb_h.status = CAM_UA_ABORT;
xpt_done(ccb);
break;
case XPT_CALC_GEOMETRY:
{
struct ccb_calc_geometry *ccg;
u_int32_t size_mb;
u_int32_t secs_per_cylinder;
ccg = &ccb->ccg;
size_mb = ccg->volume_size
/ ((1024L * 1024L) / ccg->block_size);
ccg->heads = 64;
ccg->secs_per_track = 16;
secs_per_cylinder = ccg->heads * ccg->secs_per_track;
ccg->cylinders = ccg->volume_size / secs_per_cylinder;
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
}
case XPT_PATH_INQ: /* Path routing inquiry */
{
struct ccb_pathinq *cpi = &ccb->cpi;
cpi->version_num = 1; /* XXX??? */
cpi->hba_inquiry = 0; /* nothing fancy */
cpi->target_sprt = 0;
cpi->hba_misc = 0;
cpi->hba_eng_cnt = 0;
cpi->max_target = 7;
cpi->max_lun = 7;
cpi->initiator_id = WDS_HBA_ID;
cpi->hba_misc = 0;
cpi->bus_id = cam_sim_bus(sim);
cpi->base_transfer_speed = 3300;
strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
strncpy(cpi->hba_vid, "WD/FDC", HBA_IDLEN);
strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
cpi->unit_number = cam_sim_unit(sim);
cpi->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
}
default:
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
}
}
static void
wds_poll(struct cam_sim * sim)
{
wds_intr((struct wds *)cam_sim_softc(sim));
}
/* part of initialization done in probe() */
/* returns 0 if OK, ENXIO if bad */
static int
wds_preinit(struct wds *wp)
{
int base;
int i;
base = wp->addr;
/*
* Sending a command causes the CMDRDY bit to clear.
*/
outb(base + WDS_CMD, WDSC_NOOP);
if (inb(base + WDS_STAT) & WDS_RDY)
return (ENXIO);
/*
* the controller exists. reset and init.
*/
outb(base + WDS_HCR, WDSH_ASCRESET | WDSH_SCSIRESET);
DELAY(30);
outb(base + WDS_HCR, 0);
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 (ENXIO);
}
return (0);
}
/* part of initialization done in attach() */
/* returns 0 if OK, 1 if bad */
static int
wds_init(struct wds *wp)
{
struct wds_setup init;
int base;
int i;
struct wds_cmd wc;
base = wp->addr;
outb(base + WDS_HCR, WDSH_DRQEN);
isa_dmacascade(wp->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 = WDS_HBA_ID;
init.buson_t = 24;
init.busoff_t = 48;
scsi_ulto3b(WDSTOPHYS(wp, &wp->dx->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_int8_t *) & init, sizeof init) != 0) {
device_printf(wp->dev, "wds_cmd init failed\n");
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) {
device_printf(wp->dev, "wds_cmd init2 failed\n");
return (1);
}
return (0);
}
static int
wds_cmd(int base, u_int8_t * p, int l)
{
int s = splcam();
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);
}
static void
wds_wait(int reg, int mask, int val)
{
while ((inb(reg) & mask) != val)
;
}
static struct wds_req *
cmdtovirt(struct wds *wp, u_int32_t phys)
{
char *a;
a = WDSTOVIRT(wp, (uintptr_t)phys);
if( a < (char *)&wp->dx->req[0] || a>= (char *)&wp->dx->req[MAXSIMUL]) {
device_printf(wp->dev, "weird phys address 0x%x\n", phys);
return (NULL);
}
a -= (int)offsetof(struct wds_req, cmd); /* convert cmd to request */
return ((struct wds_req *)a);
}
/* for debugging, print out all the data about the status of devices */
void
wds_print(void)
{
int unit;
int i;
struct wds_req *r;
struct wds *wp;
for (unit = 0; unit < devclass_get_maxunit(wds_devclass); unit++) {
wp = (struct wds *) devclass_get_device(wds_devclass, unit);
if (wp == NULL)
continue;
printf("wds%d: want_wdsr=0x%x stat=0x%x irq=%s irqstat=0x%x\n",
unit, wp->want_wdsr, inb(wp->addr + WDS_STAT) & 0xff,
(inb(wp->addr + WDS_STAT) & WDS_IRQ) ? "ready" : "no",
inb(wp->addr + WDS_IRQSTAT) & 0xff);
for (i = 0; i < MAXSIMUL; i++) {
r = &wp->dx->req[i];
if( wp->wdsr_free & (1 << r->id) ) {
printf("req=%d flg=0x%x ombn=%d ombstat=%d "
"mask=0x%x targ=%d lun=%d cmd=0x%x\n",
i, r->flags, r->ombn,
wp->dx->ombs[r->ombn].stat,
r->mask, r->cmd.targ >> 5,
r->cmd.targ & 7, r->cmd.scb[0]);
}
}
}
}
#if WDS_DEBUG == 2
/* create circular log buffer */
static char *
wds_nextlog(void)
{
int n = logwrite;
if (++logwrite >= NLOGLINES)
logwrite = 0;
if (logread == logwrite)
if (++logread >= NLOGLINES)
logread = 0;
return (wds_log[n]);
}
void
wds_printlog(void)
{
/* print the circular buffer */
int i;
for (i = logread; i != logwrite;) {
printf("%s", wds_log[i]);
if (i == NLOGLINES)
i = 0;
else
i++;
}
}
#endif /* WDS_DEBUG */