diff options
author | Michael Shalayeff <mickey@cvs.openbsd.org> | 2000-09-15 16:56:14 +0000 |
---|---|---|
committer | Michael Shalayeff <mickey@cvs.openbsd.org> | 2000-09-15 16:56:14 +0000 |
commit | c6b64592bddea2746f4f282d4dc8433580029c3e (patch) | |
tree | 1225eb4fcfe1dbeea271740cfdf45a2ae638dda6 /sys/dev/ic | |
parent | 39891618a19d9bb12f27264159b0be60d56b1056 (diff) |
3ware escalade ide raid controllers
Diffstat (limited to 'sys/dev/ic')
-rw-r--r-- | sys/dev/ic/twe.c | 960 | ||||
-rw-r--r-- | sys/dev/ic/twereg.h | 152 | ||||
-rw-r--r-- | sys/dev/ic/twevar.h | 90 |
3 files changed, 1202 insertions, 0 deletions
diff --git a/sys/dev/ic/twe.c b/sys/dev/ic/twe.c new file mode 100644 index 00000000000..5d8082889fa --- /dev/null +++ b/sys/dev/ic/twe.c @@ -0,0 +1,960 @@ +/* $OpenBSD: twe.c,v 1.1 2000/09/15 16:56:13 mickey Exp $ */ + +/* + * Copyright (c) 2000 Michael Shalayeff. All rights reserved. + * + * The SCSI emulation layer is derived from gdt(4) driver, + * Copyright (c) 1999, 2000 Niklas Hallqvist. All rights reserved. + * + * 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 by Michael Shalayeff. + * 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 OR HIS RELATIVES 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 MIND, 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. + */ + +#define TWE_DEBUG + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/buf.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/malloc.h> + +#include <machine/bus.h> + +#include <vm/vm.h> +#include <vm/vm_kern.h> +#include <uvm/uvm_extern.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsi_disk.h> +#include <scsi/scsiconf.h> + +#include <dev/ic/twereg.h> +#include <dev/ic/twevar.h> + +#ifdef TWE_DEBUG +#define TWE_DPRINTF(m,a) if (twe_debug & (m)) printf a +#define TWE_D_CMD 0x0001 +#define TWE_D_INTR 0x0002 +#define TWE_D_MISC 0x0004 +#define TWE_D_DMA 0x0008 +#define TWE_D_AEN 0x0010 +int twe_debug = 0xffff; +#else +#define TWE_DPRINTF(m,a) /* m, a */ +#endif + +int twe_scsi_cmd __P((struct scsi_xfer *)); + +struct cfdriver twe_cd = { + NULL, "twe", DV_DULL +}; + +struct scsi_adapter twe_switch = { + twe_scsi_cmd, tweminphys, 0, 0, +}; + +struct scsi_device twe_dev = { + NULL, NULL, NULL, NULL +}; + +static __inline struct twe_ccb *twe_get_ccb __P((struct twe_softc *sc)); +static __inline void twe_put_ccb __P((struct twe_ccb *ccb)); +int twe_cmd __P((struct twe_ccb *ccb, int flags, int wait)); +int twe_start __P((struct twe_ccb *ccb, int wait)); +void twe_exec_cmd __P((void *v)); +int twe_complete __P((struct twe_ccb *ccb)); +int twe_done __P((struct twe_softc *sc, int idx)); +void twe_copy_internal_data __P((struct scsi_xfer *xs, void *v, size_t size)); + + +static __inline struct twe_ccb * +twe_get_ccb(sc) + struct twe_softc *sc; +{ + struct twe_ccb *ccb; + twe_lock_t lock; + + lock = TWE_LOCK_TWE(sc); + ccb = TAILQ_LAST(&sc->sc_free_ccb, twe_queue_head); + if (ccb) + TAILQ_REMOVE(&sc->sc_free_ccb, ccb, ccb_link); + TWE_UNLOCK_TWE(sc, lock); + return ccb; +} + +static __inline void +twe_put_ccb(ccb) + struct twe_ccb *ccb; +{ + struct twe_softc *sc = ccb->ccb_sc; + twe_lock_t lock; + + lock = TWE_LOCK_TWE(sc); + ccb->ccb_state = TWE_CCB_FREE; + TAILQ_INSERT_TAIL(&sc->sc_free_ccb, ccb, ccb_link); + TWE_UNLOCK_TWE(sc, lock); +} + +int +twe_attach(sc) + struct twe_softc *sc; +{ + /* this includes a buffer for drive config req, and a capacity req */ + u_int8_t param_buf[2 * TWE_SECTOR_SIZE + TWE_ALIGN - 1]; + struct twe_param *pb = (void *) + (((u_long)param_buf + TWE_ALIGN - 1) & ~(TWE_ALIGN - 1)); + struct twe_param *cap = (void *)((u_int8_t *)pb + TWE_SECTOR_SIZE); + struct twe_ccb *ccb; + struct twe_cmd *cmd; + u_int32_t status; + u_int16_t aen; + int error, i, retry, veseen_srst, size; + const char *errstr; + + TAILQ_INIT(&sc->sc_ccb2q); + TAILQ_INIT(&sc->sc_ccbq); + TAILQ_INIT(&sc->sc_free_ccb); + size = round_page(sizeof(struct twe_cmd) * TWE_MAXCMDS); + sc->sc_cmds = (void *)uvm_km_kmemalloc(kmem_map, uvmexp.kmem_object, + size, UVM_KMF_NOWAIT); + if (sc->sc_cmds == NULL) { + printf(": cannot allocate commands\n"); + return (1); + } + error = bus_dmamap_create(sc->dmat, + sizeof(struct twe_cmd) * TWE_MAXCMDS, TWE_MAXCMDS, + sizeof(struct twe_cmd) * TWE_MAXCMDS, 0, + BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_cmdmap); + if (error) { + printf(": cannot create ccb cmd dmamap (%d)\n", error); + return (1); + } + error = bus_dmamap_load(sc->dmat, sc->sc_cmdmap, sc->sc_cmds, + sizeof(struct twe_cmd) * TWE_MAXCMDS, NULL, BUS_DMA_NOWAIT); + if (error) { + printf(": cannot load command dma map (%d)\n", error); + return (1); + } + for (cmd = sc->sc_cmds + sizeof(struct twe_cmd) * (TWE_MAXCMDS - 1); + cmd >= (struct twe_cmd *)sc->sc_cmds; cmd--) { + + cmd->cmd_index = cmd - (struct twe_cmd *)sc->sc_cmds; + ccb = &sc->sc_ccbs[cmd->cmd_index]; + error = bus_dmamap_create(sc->dmat, + TWE_MAXFER, TWE_MAXOFFSETS, TWE_MAXFER, 0, + BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ccb->ccb_dmamap); + if (error) { + printf(": cannot create ccb dmamap (%d)\n", error); + return (1); + } + ccb->ccb_sc = sc; + ccb->ccb_cmd = cmd; + ccb->ccb_state = TWE_CCB_FREE; + ccb->ccb_cmdpa = kvtop((caddr_t)cmd); + TAILQ_INSERT_TAIL(&sc->sc_free_ccb, ccb, ccb_link); + } + + sc->sc_link.adapter_softc = sc; + sc->sc_link.adapter = &twe_switch; + sc->sc_link.adapter_target = TWE_MAX_UNITS; + sc->sc_link.device = &twe_dev; + sc->sc_link.openings = TWE_MAXCMDS; /* XXX or less? */ + sc->sc_link.adapter_buswidth = TWE_MAX_UNITS; + + for (errstr = NULL, retry = 3; retry--; ) { + if (errstr) + TWE_DPRINTF(TWE_D_MISC, ("%s ", errstr)); + + for (i = 60000; i--; DELAY(100)) { + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + if (status & TWE_STAT_CPURDY) + break; + } + + if (!(status & TWE_STAT_CPURDY)) { + errstr = ": card CPU is not ready\n"; + continue; + } + + /* soft reset, disable ints */ + bus_space_write_4(sc->iot, sc->ioh, TWE_CONTROL, + TWE_CTRL_SRST | + TWE_CTRL_CHOSTI | TWE_CTRL_CATTNI | TWE_CTRL_CERR | + TWE_CTRL_MCMDI | TWE_CTRL_MRDYI | + TWE_CTRL_MINT); + + for (i = 45000; i--; DELAY(100)) { + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + if (status & TWE_STAT_ATTNI) + break; + } + + if (!(status & TWE_STAT_ATTNI)) { + errstr = ": cannot get card's attention\n"; + continue; + } + + /* drain aen queue */ + for (veseen_srst = 0, aen = -1; aen != TWE_AEN_QEMPTY; ) { + + if ((ccb = twe_get_ccb(sc)) == NULL) { + errstr = ": out of ccbs\n"; + continue; + } + + ccb->ccb_xs = NULL; + ccb->ccb_data = pb; + ccb->ccb_length = TWE_SECTOR_SIZE; + ccb->ccb_state = TWE_CCB_READY; + cmd = ccb->ccb_cmd; + cmd->cmd_unit_host = TWE_UNITHOST(0, 0); + cmd->cmd_op = TWE_CMD_GPARAM; + cmd->cmd_count = 1; + + pb->table_id = TWE_PARAM_AEN; + pb->param_id = 2; + pb->param_size = 2; + + if (twe_cmd(ccb, BUS_DMA_NOWAIT, 1)) { + errstr = ": error draining attention queue\n"; + break; + } + aen = *(u_int16_t *)pb->data; + TWE_DPRINTF(TWE_D_AEN, ("aen=%x ", aen)); + if (aen == TWE_AEN_SRST) + veseen_srst++; + } + + if (!veseen_srst) { + errstr = ": we don't get it\n"; + continue; + } + + if (status & TWE_STAT_CPUERR) { + errstr = ": card CPU error detected\n"; + continue; + } + + if (status & TWE_STAT_PCIPAR) { + errstr = ": PCI parity error detected\n"; + continue; + } + + if (status & TWE_STAT_QUEUEE ) { + errstr = ": queuing error detected\n"; + continue; + } + + if (status & TWE_STAT_PCIABR) { + errstr = ": PCI abort\n"; + continue; + } + + while (!(status & TWE_STAT_RQE)) { + bus_space_read_4(sc->iot, sc->ioh, TWE_READYQUEUE); + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + } + + break; + } + + if (retry < 0) { + printf(errstr); + return 1; + } + + if ((ccb = twe_get_ccb(sc)) == NULL) { + printf(": out of ccbs\n"); + return 1; + } + + ccb->ccb_xs = NULL; + ccb->ccb_data = pb; + ccb->ccb_length = TWE_SECTOR_SIZE; + ccb->ccb_state = TWE_CCB_READY; + cmd = ccb->ccb_cmd; + cmd->cmd_unit_host = TWE_UNITHOST(0, 0); + cmd->cmd_op = TWE_CMD_GPARAM; + cmd->cmd_count = 1; + + pb->table_id = TWE_PARAM_UC; + pb->param_id = TWE_PARAM_UC; + pb->param_size = TWE_MAX_UNITS; + if (twe_cmd(ccb, BUS_DMA_NOWAIT, 1)) { + printf(": failed to fetch unit parameters\n"); + return 1; + } + + /* we are assuming last read status was good */ + printf(": Escalade V%d.%d\n", TWE_MAJV(status), TWE_MINV(status)); + + for (i = 0; i < TWE_MAX_UNITS; i++) { + if (pb->data[i] == 0) + continue; + + if ((ccb = twe_get_ccb(sc)) == NULL) { + printf(": out of ccbs\n"); + return 1; + } + + ccb->ccb_xs = NULL; + ccb->ccb_data = cap; + ccb->ccb_length = TWE_SECTOR_SIZE; + ccb->ccb_state = TWE_CCB_READY; + cmd = ccb->ccb_cmd; + cmd->cmd_unit_host = TWE_UNITHOST(0, 0); + cmd->cmd_op = TWE_CMD_GPARAM; + cmd->cmd_count = 1; + + cap->table_id = TWE_PARAM_UI + i; + cap->param_id = 4; + cap->param_size = 4; /* 4 bytes */ + if (twe_cmd(ccb, BUS_DMA_NOWAIT, 1)) { + printf("%s: error fetching capacity for unit %d\n", + sc->sc_dev.dv_xname, i); + continue; + } + + sc->sc_hdr[i].hd_present = 1; + sc->sc_hdr[i].hd_devtype = 0; + sc->sc_hdr[i].hd_size = letoh32(*(u_int32_t *)cap->data); + /* this is evil. they never learn */ + if (sc->sc_hdr[i].hd_size > 0x200000) { + sc->sc_hdr[i].hd_secs = 63; + sc->sc_hdr[i].hd_heads = 255; + } else { + sc->sc_hdr[i].hd_secs = 32; + sc->sc_hdr[i].hd_heads = 64; + } + TWE_DPRINTF(TWE_D_MISC, ("twed%d: size=%d secs=%d heads=%d\n", + i, sc->sc_hdr[i].hd_size, sc->sc_hdr[i].hd_secs, + sc->sc_hdr[i].hd_heads)); + } + + /* TODO: fetch & print cache params? */ + + config_found(&sc->sc_dev, &sc->sc_link, scsiprint); + + /* enable interrupts */ + bus_space_write_4(sc->iot, sc->ioh, TWE_CONTROL, TWE_CTRL_EINT | + /*TWE_CTRL_HOSTI |*/ TWE_CTRL_CATTNI | TWE_CTRL_ERDYI); + + return 0; +} + +int +twe_cmd(ccb, flags, wait) + struct twe_ccb *ccb; + int flags, wait; +{ + struct twe_softc *sc = ccb->ccb_sc; + bus_dmamap_t dmap; + struct twe_cmd *cmd; + struct twe_segs *sgp; + int error, i; + + if (ccb->ccb_data && ((u_long)ccb->ccb_data & (TWE_ALIGN - 1))) { + TWE_DPRINTF(TWE_D_DMA, ("data=%p is unaligned ",ccb->ccb_data)); + ccb->ccb_realdata = ccb->ccb_data; + ccb->ccb_data = (void *)uvm_km_kmemalloc(kmem_map, + uvmexp.kmem_object, ccb->ccb_length, UVM_KMF_NOWAIT); + if (!ccb->ccb_data) { + TWE_DPRINTF(TWE_D_DMA, ("2buf alloc failed ")); + twe_put_ccb(ccb); + return (ENOMEM); + } + bcopy(ccb->ccb_realdata, ccb->ccb_data, ccb->ccb_length); + } else + ccb->ccb_realdata = NULL; + + dmap = ccb->ccb_dmamap; + cmd = ccb->ccb_cmd; + cmd->cmd_status = 0; + + if (ccb->ccb_data) { + error = bus_dmamap_load(sc->dmat, dmap, ccb->ccb_data, + ccb->ccb_length, NULL, flags); + if (error) { + if (error == EFBIG) + printf("more than %d dma segs\n", TWE_MAXOFFSETS); + else + printf("error %d loading dma map\n", error); + + twe_put_ccb(ccb); + return error; + } + /* load addresses into command */ + switch (cmd->cmd_op) { + case TWE_CMD_GPARAM: + case TWE_CMD_SPARAM: + sgp = cmd->cmd_param.segs; + break; + case TWE_CMD_READ: + case TWE_CMD_WRITE: + sgp = cmd->cmd_io.segs; + break; + default: + /* no data transfer */ + TWE_DPRINTF(TWE_D_DMA, ("twe_cmd: unknown sgp op=%x\n", + cmd->cmd_op)); + sgp = NULL; + break; + } + TWE_DPRINTF(TWE_D_DMA, ("data=%p<", ccb->ccb_data)); + if (sgp) { + /* + * we know that size is in the upper byte, + * and we do not worry about overflow + */ + cmd->cmd_op += (2 * dmap->dm_nsegs) << 8; + bzero (sgp, TWE_MAXOFFSETS * sizeof(*sgp)); + for (i = 0; i < dmap->dm_nsegs; i++, sgp++) { + sgp->twes_addr = htole32(dmap->dm_segs[i].ds_addr); + sgp->twes_len = htole32(dmap->dm_segs[i].ds_len); + TWE_DPRINTF(TWE_D_DMA, ("%x[%x] ", + dmap->dm_segs[i].ds_addr, + dmap->dm_segs[i].ds_len)); + } + } + TWE_DPRINTF(TWE_D_DMA, ("> ")); + bus_dmamap_sync(sc->dmat, dmap, BUS_DMASYNC_PREWRITE); + } + bus_dmamap_sync(sc->dmat, sc->sc_cmdmap, BUS_DMASYNC_PREWRITE); + + if ((error = twe_start(ccb, wait))) { + bus_dmamap_unload(sc->dmat, dmap); + twe_put_ccb(ccb); + return error; + } + + return wait? twe_complete(ccb) : 0; +} + +int +twe_start(ccb, wait) + struct twe_ccb *ccb; + int wait; +{ + struct twe_softc*sc = ccb->ccb_sc; + struct twe_cmd *cmd = ccb->ccb_cmd; + u_int32_t status; + int i; + + cmd->cmd_op = htole16(cmd->cmd_op); + cmd->cmd_count = htole16(cmd->cmd_count); + + if (!wait) { + + TWE_DPRINTF(TWE_D_CMD, ("prequeue(%d) ", cmd->cmd_index)); + ccb->ccb_state = TWE_CCB_PREQUEUED; + TAILQ_INSERT_TAIL(&sc->sc_ccb2q, ccb, ccb_link); + bus_space_write_4(sc->iot, sc->ioh, TWE_CONTROL, + TWE_CTRL_ECMDI); + return 0; + } + + for (i = 1000; i--; DELAY(10)) { + + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + if (!(status & TWE_STAT_CQF)) + break; + TWE_DPRINTF(TWE_D_CMD, ("twe_start stat=%b ", + status & TWE_STAT_FLAGS, TWE_STAT_BITS)); + } + + if (!(status & TWE_STAT_CQF)) { + bus_space_write_4(sc->iot, sc->ioh, TWE_COMMANDQUEUE, + ccb->ccb_cmdpa); + + TWE_DPRINTF(TWE_D_CMD, ("queue(%d) ", cmd->cmd_index)); + ccb->ccb_state = TWE_CCB_QUEUED; + TAILQ_INSERT_TAIL(&sc->sc_ccbq, ccb, ccb_link); + return 0; + + } else { + + printf("%s: twe_start(%d) timed out\n", + sc->sc_dev.dv_xname, cmd->cmd_index); + + return 1; + } +} + +int +twe_complete(ccb) + struct twe_ccb *ccb; +{ + struct twe_softc *sc = ccb->ccb_sc; + u_int32_t status; + int i; + + for (i = 100000; i--; DELAY(10)) { + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + /* TWE_DPRINTF(TWE_D_CMD, ("twe_intr stat=%b ", + status & TWE_STAT_FLAGS, TWE_STAT_BITS)); */ + + while (!(status & TWE_STAT_RQE)) { + u_int32_t ready; + + ready = bus_space_read_4(sc->iot, sc->ioh, + TWE_READYQUEUE); + + TWE_DPRINTF(TWE_D_CMD, ("ready=%x ", ready)); + + if (!twe_done(sc, TWE_READYID(ready)) && + ccb->ccb_state == TWE_CCB_FREE) { + TWE_DPRINTF(TWE_D_CMD, ("complete\n")); + return 0; + } + + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + /* TWE_DPRINTF(TWE_D_CMD, ("twe_intr stat=%b ", + status & TWE_STAT_FLAGS, TWE_STAT_BITS)); */ + } + } + + return 1; +} + +int +twe_done(sc, idx) + struct twe_softc *sc; + int idx; +{ + struct scsi_xfer *xs; + struct twe_ccb *ccb = &sc->sc_ccbs[idx]; + struct twe_cmd *cmd = ccb->ccb_cmd; + + TWE_DPRINTF(TWE_D_CMD, ("done(%d) ", idx)); + + xs = ccb->ccb_xs; + + if (ccb->ccb_state != TWE_CCB_QUEUED) { + printf("%s: unqueued ccb %d ready\n", + sc->sc_dev.dv_xname, idx); + return 1; + } + + if (xs) { + if (xs->cmd->opcode != PREVENT_ALLOW && + xs->cmd->opcode != SYNCHRONIZE_CACHE) { + bus_dmamap_sync(sc->dmat, ccb->ccb_dmamap, + (xs->flags & SCSI_DATA_IN) ? + BUS_DMASYNC_POSTREAD : + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->dmat, ccb->ccb_dmamap); + } + } else { + switch (cmd->cmd_op) { + case TWE_CMD_GPARAM: + case TWE_CMD_READ: + bus_dmamap_sync(sc->dmat, ccb->ccb_dmamap, + BUS_DMASYNC_POSTREAD); + bus_dmamap_unload(sc->dmat, ccb->ccb_dmamap); + break; + case TWE_CMD_SPARAM: + case TWE_CMD_WRITE: + bus_dmamap_sync(sc->dmat, ccb->ccb_dmamap, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->dmat, ccb->ccb_dmamap); + break; + default: + /* no data */ + } + } + + if (ccb->ccb_realdata) { + bcopy(ccb->ccb_data, ccb->ccb_realdata, ccb->ccb_length); + uvm_km_free(kmem_map, (vaddr_t)ccb->ccb_data, ccb->ccb_length); + ccb->ccb_data = ccb->ccb_realdata; + ccb->ccb_realdata = NULL; + } + + TAILQ_REMOVE(&sc->sc_ccbq, ccb, ccb_link); + twe_put_ccb(ccb); + + if (xs) { + xs->resid = 0; + xs->flags |= ITSDONE; + scsi_done(xs); + } + + return 0; +} +void +tweminphys(bp) + struct buf *bp; +{ + if (bp->b_bcount > TWE_MAXFER) + bp->b_bcount = TWE_MAXFER; + minphys(bp); +} + +void +twe_copy_internal_data(xs, v, size) + struct scsi_xfer *xs; + void *v; + size_t size; +{ + size_t copy_cnt; + + TWE_DPRINTF(TWE_D_MISC, ("twe_copy_internal_data ")); + + if (!xs->datalen) + printf("uio move not yet supported\n"); + else { + copy_cnt = MIN(size, xs->datalen); + bcopy(v, xs->data, copy_cnt); + } +} + +int +twe_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct scsi_link *link = xs->sc_link; + struct twe_softc *sc = link->adapter_softc; + struct twe_ccb *ccb; + struct twe_cmd *cmd; + struct scsi_inquiry_data inq; + struct scsi_sense_data sd; + struct { + struct scsi_mode_header hd; + struct scsi_blk_desc bd; + union scsi_disk_pages dp; + } mpd; + struct scsi_read_cap_data rcd; + u_int8_t target = link->target; + u_int32_t blockno, blockcnt; + struct scsi_rw *rw; + struct scsi_rw_big *rwb; + int error, op; + twe_lock_t lock; + + + if (target >= TWE_MAX_UNITS || !sc->sc_hdr[target].hd_present || + link->lun != 0) { + xs->error = XS_DRIVER_STUFFUP; + return (COMPLETE); + } + + TWE_DPRINTF(TWE_D_CMD, ("twe_scsi_cmd ")); + + xs->error = XS_NOERROR; + + switch (xs->cmd->opcode) { + case TEST_UNIT_READY: + case START_STOP: +#if 0 + case VERIFY: +#endif + TWE_DPRINTF(TWE_D_CMD, ("opc %d tgt %d ", xs->cmd->opcode, + target)); + break; + + case REQUEST_SENSE: + TWE_DPRINTF(TWE_D_CMD, ("REQUEST SENSE tgt %d ", target)); + bzero(&sd, sizeof sd); + sd.error_code = 0x70; + sd.segment = 0; + sd.flags = SKEY_NO_SENSE; + *(u_int32_t*)sd.info = htole32(0); + sd.extra_len = 0; + twe_copy_internal_data(xs, &sd, sizeof sd); + break; + + case INQUIRY: + TWE_DPRINTF(TWE_D_CMD, ("INQUIRY tgt %d devtype %x ", target, + sc->sc_hdr[target].hd_devtype)); + bzero(&inq, sizeof inq); + inq.device = + (sc->sc_hdr[target].hd_devtype & 4) ? T_CDROM : T_DIRECT; + inq.dev_qual2 = + (sc->sc_hdr[target].hd_devtype & 1) ? SID_REMOVABLE : 0; + inq.version = 2; + inq.response_format = 2; + inq.additional_length = 32; + strcpy(inq.vendor, "3WARE "); + sprintf(inq.product, "Host drive #%02d", target); + strcpy(inq.revision, " "); + twe_copy_internal_data(xs, &inq, sizeof inq); + break; + + case MODE_SENSE: + TWE_DPRINTF(TWE_D_CMD, ("MODE SENSE tgt %d ", target)); + + bzero(&mpd, sizeof mpd); + switch (((struct scsi_mode_sense *)xs->cmd)->page) { + case 4: + /* scsi_disk.h says this should be 0x16 */ + mpd.dp.rigid_geometry.pg_length = 0x16; + mpd.hd.data_length = sizeof mpd.hd + sizeof mpd.bd + + mpd.dp.rigid_geometry.pg_length; + mpd.hd.blk_desc_len = sizeof mpd.bd; + + /* XXX */ + mpd.hd.dev_spec = + (sc->sc_hdr[target].hd_devtype & 2) ? 0x80 : 0; + _lto3b(TWE_SECTOR_SIZE, mpd.bd.blklen); + mpd.dp.rigid_geometry.pg_code = 4; + _lto3b(sc->sc_hdr[target].hd_size / + sc->sc_hdr[target].hd_heads / + sc->sc_hdr[target].hd_secs, + mpd.dp.rigid_geometry.ncyl); + mpd.dp.rigid_geometry.nheads = + sc->sc_hdr[target].hd_heads; + twe_copy_internal_data(xs, (u_int8_t *)&mpd, + sizeof mpd); + break; + + default: + printf("%s: mode sense page %d not simulated\n", + sc->sc_dev.dv_xname, + ((struct scsi_mode_sense *)xs->cmd)->page); + xs->error = XS_DRIVER_STUFFUP; + return (TRY_AGAIN_LATER); + } + break; + + case READ_CAPACITY: + TWE_DPRINTF(TWE_D_CMD, ("READ CAPACITY tgt %d ", target)); + bzero(&rcd, sizeof rcd); + _lto4b(sc->sc_hdr[target].hd_size - 1, rcd.addr); + _lto4b(TWE_SECTOR_SIZE, rcd.length); + twe_copy_internal_data(xs, &rcd, sizeof rcd); + break; + + case PREVENT_ALLOW: + TWE_DPRINTF(TWE_D_CMD, ("PREVENT/ALLOW ")); + return (COMPLETE); + + case READ_COMMAND: + case READ_BIG: + case WRITE_COMMAND: + case WRITE_BIG: + case SYNCHRONIZE_CACHE: + lock = TWE_LOCK_TWE(sc); + + if (xs->cmd->opcode != SYNCHRONIZE_CACHE) { + /* A read or write operation. */ + if (xs->cmdlen == 6) { + rw = (struct scsi_rw *)xs->cmd; + blockno = _3btol(rw->addr) & + (SRW_TOPADDR << 16 | 0xffff); + blockcnt = rw->length ? rw->length : 0x100; + } else { + rwb = (struct scsi_rw_big *)xs->cmd; + blockno = _4btol(rwb->addr); + blockcnt = _2btol(rwb->length); + } + if (blockno >= sc->sc_hdr[target].hd_size || + blockno + blockcnt > sc->sc_hdr[target].hd_size) { + TWE_UNLOCK_TWE(sc, lock); + printf("%s: out of bounds %u-%u >= %u\n", + sc->sc_dev.dv_xname, blockno, blockcnt, + sc->sc_hdr[target].hd_size); + scsi_done(xs); + xs->error = XS_DRIVER_STUFFUP; + return (COMPLETE); + } + } + + switch (xs->cmd->opcode) { + case READ_COMMAND: op = TWE_CMD_READ; break; + case READ_BIG: op = TWE_CMD_READ; break; + case WRITE_COMMAND: op = TWE_CMD_WRITE; break; + case WRITE_BIG: op = TWE_CMD_WRITE; break; + default: op = TWE_CMD_NOP; break; + } + + if ((ccb = twe_get_ccb(sc)) == NULL) { + scsi_done(xs); + xs->error = XS_DRIVER_STUFFUP; + return (COMPLETE); + } + + ccb->ccb_xs = xs; + ccb->ccb_data = xs->data; + ccb->ccb_length = xs->datalen; + ccb->ccb_state = TWE_CCB_READY; + cmd = ccb->ccb_cmd; + cmd->cmd_unit_host = TWE_UNITHOST(target, 0); /* XXX why 0? */ + cmd->cmd_op = op; + cmd->cmd_count = blockcnt; + cmd->cmd_io.lba = blockno; + + if ((error = twe_cmd(ccb, ((xs->flags & SCSI_NOSLEEP)? + BUS_DMA_NOWAIT : BUS_DMA_WAITOK), xs->flags & SCSI_POLL))) { + + TWE_UNLOCK_TWE(sc, lock); + TWE_DPRINTF(TWE_D_CMD, ("failed %p ", xs)); + if (xs->flags & SCSI_POLL) { + xs->error = XS_TIMEOUT; + return (TRY_AGAIN_LATER); + } else { + scsi_done(xs); + xs->error = XS_DRIVER_STUFFUP; + return (COMPLETE); + } + } + + TWE_UNLOCK_TWE(sc, lock); + + if (xs->flags & SCSI_POLL) { + scsi_done(xs); + return (COMPLETE); + } + return (SUCCESSFULLY_QUEUED); + + default: + TWE_DPRINTF(TWE_D_CMD, ("unknown opc %d ", xs->cmd->opcode)); + xs->error = XS_DRIVER_STUFFUP; + } + + return (COMPLETE); +} + +int +twe_intr(v) + void *v; +{ + struct twe_softc *sc = v; + struct twe_ccb *ccb; + struct twe_cmd *cmd; + u_int32_t status; + twe_lock_t lock; + int rv = 0; + + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + TWE_DPRINTF(TWE_D_INTR, ("twe_intr stat=%b ", + status & TWE_STAT_FLAGS, TWE_STAT_BITS)); +#if 0 + if (status & TWE_STAT_HOSTI) { + + bus_space_write_4(sc->iot, sc->ioh, TWE_CONTROL, + TWE_CTRL_CHOSTI); + } +#endif + + if (status & (TWE_STAT_CMDI | TWE_STAT_CQF)) { + + lock = TWE_LOCK_TWE(sc); + while (!(status & TWE_STAT_CQF) && + !TAILQ_EMPTY(&sc->sc_ccb2q)) { + + ccb = TAILQ_LAST(&sc->sc_ccb2q, twe_queue_head); + TAILQ_REMOVE(&sc->sc_ccb2q, ccb, ccb_link); + + ccb->ccb_state = TWE_CCB_QUEUED; + TAILQ_INSERT_TAIL(&sc->sc_ccbq, ccb, ccb_link); + + bus_space_write_4(sc->iot, sc->ioh, TWE_COMMANDQUEUE, + ccb->ccb_cmdpa); + + rv++; + + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + TWE_DPRINTF(TWE_D_INTR, ("twe_intr stat=%b ", + status & TWE_STAT_FLAGS, TWE_STAT_BITS)); + } + + if (TAILQ_EMPTY(&sc->sc_ccb2q)) + bus_space_write_4(sc->iot, sc->ioh, TWE_CONTROL, + TWE_CTRL_MCMDI); + + TWE_UNLOCK_TWE(sc, lock); + } + + if (status & TWE_STAT_RDYI) { + + lock = TWE_LOCK_TWE(sc); + while (!(status & TWE_STAT_RQE)) { + + u_int32_t ready; + + /* + * it seems that reading ready queue + * we get all the status bits in each ready word. + * i wonder if it's legal to use those for + * status and avoid extra read below + */ + ready = bus_space_read_4(sc->iot, sc->ioh, + TWE_READYQUEUE); + + if (!twe_done(sc, TWE_READYID(ready))) + rv++; + + status = bus_space_read_4(sc->iot, sc->ioh, TWE_STATUS); + TWE_DPRINTF(TWE_D_INTR, ("twe_intr stat=%b ", + status & TWE_STAT_FLAGS, TWE_STAT_BITS)); + } + TWE_UNLOCK_TWE(sc, lock); + } + + if (status & TWE_STAT_ATTNI) { + u_int16_t aen; + + /* + * we no attentions of interest right now. + * one of those would be mirror degradation i think. + * or, what else exist in there? maybe 3ware can answer that. + */ + bus_space_write_4(sc->iot, sc->ioh, TWE_CONTROL, + TWE_CTRL_CATTNI); + + for (aen = -1; aen != TWE_AEN_QEMPTY; ) { + u_int8_t param_buf[2 * TWE_SECTOR_SIZE + TWE_ALIGN - 1]; + struct twe_param *pb = (void *) (((u_long)param_buf + + TWE_ALIGN - 1) & ~(TWE_ALIGN - 1)); + + if ((ccb = twe_get_ccb(sc)) == NULL) + break; + + ccb->ccb_xs = NULL; + ccb->ccb_data = pb; + ccb->ccb_length = TWE_SECTOR_SIZE; + ccb->ccb_state = TWE_CCB_READY; + cmd = ccb->ccb_cmd; + cmd->cmd_unit_host = TWE_UNITHOST(0, 0); + cmd->cmd_op = TWE_CMD_GPARAM; + cmd->cmd_count = 1; + + pb->table_id = TWE_PARAM_AEN; + pb->param_id = 2; + pb->param_size = 2; + if (twe_cmd(ccb, BUS_DMA_NOWAIT, 1)) { + printf(": error draining attention queue\n"); + break; + } + aen = *(u_int16_t *)pb->data; + TWE_DPRINTF(TWE_D_AEN, ("aen=%x ", aen)); + } + } + + return rv; +} + diff --git a/sys/dev/ic/twereg.h b/sys/dev/ic/twereg.h new file mode 100644 index 00000000000..38a85163ad2 --- /dev/null +++ b/sys/dev/ic/twereg.h @@ -0,0 +1,152 @@ +/* $OpenBSD: twereg.h,v 1.1 2000/09/15 16:56:13 mickey Exp $ */ + +/* + * Copyright (c) 2000 Michael Shalayeff + * All rights reserved. + * + * 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 by Michael Shalayeff. + * 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 OR HIS RELATIVES 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 MIND, 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. + */ +/* + * most of the meaning for registers were taken from + * freebsd driver, which in turn got 'em from linux. + * it seems those got 'em from windows driver, in it's turn. + */ + + +/* general parameters */ +#define TWE_MAX_UNITS 16 +#define TWE_MAXOFFSETS 62 +#define TWE_MAXCMDS 255 +#define TWE_SECTOR_SIZE 512 + /* XXX freebsd aligns at 64, but it does not work */ +#define TWE_ALIGN 512 +#define TWE_MAXFER (TWE_MAXOFFSETS * PAGE_SIZE) + +/* registers */ +#define TWE_CONTROL 0x00 +#define TWE_CTRL_CHOSTI 0x00080000 /* clear host int */ +#define TWE_CTRL_CATTNI 0x00040000 /* clear attention int */ +#define TWE_CTRL_MCMDI 0x00020000 /* mask cmd int */ +#define TWE_CTRL_MRDYI 0x00010000 /* mask ready int */ +#define TWE_CTRL_ECMDI 0x00008000 /* enable cmd int */ +#define TWE_CTRL_ERDYI 0x00004000 /* enable ready int */ +#define TWE_CTRL_CERR 0x00000200 /* clear error status */ +#define TWE_CTRL_SRST 0x00000100 /* soft reset */ +#define TWE_CTRL_EINT 0x00000080 /* enable ints */ +#define TWE_CTRL_MINT 0x00000040 /* mask ints */ +#define TWE_CTRL_HOSTI 0x00000020 /* generate host int */ +#define TWE_STATUS 0x04 +#define TWE_STAT_MAJV 0xf0000000 +#define TWE_MAJV(st) (((st) >> 28) & 0xf) +#define TWE_STAT_MINV 0x0f000000 +#define TWE_MINV(st) (((st) >> 24) & 0xf) +#define TWE_STAT_PCIPAR 0x00800000 +#define TWE_STAT_QUEUEE 0x00400000 +#define TWE_STAT_CPUERR 0x00200000 +#define TWE_STAT_PCIABR 0x00100000 +#define TWE_STAT_HOSTI 0x00080000 +#define TWE_STAT_ATTNI 0x00040000 +#define TWE_STAT_CMDI 0x00020000 +#define TWE_STAT_RDYI 0x00010000 +#define TWE_STAT_CQF 0x00008000 /* cmd queue full */ +#define TWE_STAT_RQE 0x00004000 /* ready queue empty */ +#define TWE_STAT_CPURDY 0x00002000 /* cpu ready */ +#define TWE_STAT_CQR 0x00001000 /* cmd queue ready */ +#define TWE_STAT_FLAGS 0x00fff000 /* mask out other stuff */ +#define TWE_STAT_BITS "\020\015cqr\016cpurdy\017rqe\20cqf" \ + "\021rdyi\022cmdi\023attni\024hosti\025pciabr\026cpuerr\027queuee\030pcipar" +#define TWE_COMMANDQUEUE 0x08 + /* + * the segs offset is encoded into upper 3 bits of the opcode. + * i bet other bits mean something too + * upper 8 bits is the command size in 32bit words. + */ +#define TWE_CMD_NOP 0x0200 +#define TWE_CMD_INIT 0x0301 +#define TWE_CMD_READ 0x0362 +#define TWE_CMD_WRITE 0x0363 +#define TWE_CMD_VERIFY 0x0364 +#define TWE_CMD_GPARAM 0x0252 +#define TWE_CMD_SPARAM 0x0253 +#define TWE_CMD_SECINF 0x021a +#define TWE_CMD_AEN 0x021c +#define TWE_READYQUEUE 0x0c +#define TWE_READYID(u) (((u) >> 4) & 0xff) + +/* get/set param table ids */ +#define TWE_PARAM_UC 0x003 /* unit config */ +#define TWE_PARAM_UI 0x300 /* + 16 -- unit information */ +#define TWE_PARAM_AEN 0x401 + +#define TWE_AEN_QEMPTY 0x0000 +#define TWE_AEN_SRST 0x0001 +#define TWE_AEN_DMIRROR 0x0002 /* degraded mirror */ +#define TWE_AEN_CERROR 0x0003 /* controller error */ +#define TWE_AEN_RBFAIL 0x0004 /* rebuild failed */ +#define TWE_AEN_RBDONE 0x0005 +#define TWE_AEN_QFULL 0x00ff +#define TWE_AEN_TUN 0x0015 /* table undefined */ + +/* struct definitions */ +struct twe_param { + u_int16_t table_id; + u_int8_t param_id; + u_int8_t param_size; + u_int8_t data[1]; +} __attribute__ ((packed)); + +struct twe_segs { + u_int32_t twes_addr; + u_int32_t twes_len; +}; + +struct twe_cmd { + u_int16_t cmd_op; + u_int8_t cmd_index; + u_int8_t cmd_unit_host; +#define TWE_UNITHOST(u, h) (((u) & 0xf) | ((h) << 4)) + u_int8_t cmd_status; + u_int8_t cmd_flags; + u_int16_t cmd_count; + union { + struct { + u_int32_t lba; + struct twe_segs segs[TWE_MAXOFFSETS]; + u_int32_t pad; + } _cmd_io; +#define cmd_io _._cmd_io + struct { + struct twe_segs segs[TWE_MAXOFFSETS]; + } _cmd_param; +#define cmd_param _._cmd_param + struct { + u_int32_t rdy_q_ptr; + } _cmd_init; +#define cmd_init _._cmd_init + } _; +} __attribute__ ((packed)); /* 512 bytes */ + diff --git a/sys/dev/ic/twevar.h b/sys/dev/ic/twevar.h new file mode 100644 index 00000000000..98d75ed75d8 --- /dev/null +++ b/sys/dev/ic/twevar.h @@ -0,0 +1,90 @@ +/* $OpenBSD: twevar.h,v 1.1 2000/09/15 16:56:13 mickey Exp $ */ + +/* + * Copyright (c) 2000 Michael Shalayeff + * All rights reserved. + * + * 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 by Michael Shalayeff. + * 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 OR HIS RELATIVES 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 MIND, 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. + */ + +struct twe_softc; + +struct twe_ccb { + struct twe_softc *ccb_sc; + struct twe_cmd *ccb_cmd; + struct scsi_xfer *ccb_xs; + paddr_t ccb_cmdpa; + TAILQ_ENTRY(twe_ccb) ccb_link; + enum { + TWE_CCB_FREE, TWE_CCB_READY, TWE_CCB_QUEUED, TWE_CCB_PREQUEUED + } ccb_state; + int ccb_length; + void *ccb_data; + void *ccb_realdata; + bus_dmamap_t ccb_dmamap; +}; + +typedef TAILQ_HEAD(twe_queue_head, twe_ccb) twe_queue_head; + +struct twe_softc { + struct device sc_dev; + void *sc_ih; + struct scsi_link sc_link; + + bus_space_tag_t iot; + bus_space_handle_t ioh; + bus_dma_tag_t dmat; + + void *sc_cmds; + bus_dmamap_t sc_cmdmap; + struct twe_ccb sc_ccbs[TWE_MAXCMDS]; + twe_queue_head sc_free_ccb; + twe_queue_head sc_ccbq; + twe_queue_head sc_ccb2q; + + struct timeout sc_enqueue_tmo; + + struct { + int hd_present; + int hd_devtype; + int hd_lock; + int hd_secs; + int hd_heads; + int hd_size; + } sc_hdr[TWE_MAX_UNITS]; +}; + +/* XXX These have to become spinlocks in case of SMP */ +#define TWE_LOCK_TWE(sc) splbio() +#define TWE_UNLOCK_TWE(sc, lock) splx(lock) +typedef int twe_lock_t; + +void tweminphys __P((struct buf *bp)); +int twe_attach __P((struct twe_softc *)); +int twe_intr __P((void *)); + + |