diff options
Diffstat (limited to 'sys')
-rw-r--r-- | sys/arch/aviion/conf/GENERIC | 14 | ||||
-rw-r--r-- | sys/arch/aviion/conf/files.aviion | 5 | ||||
-rw-r--r-- | sys/arch/aviion/dev/oaic_syscon.c | 151 | ||||
-rw-r--r-- | sys/conf/files | 6 | ||||
-rw-r--r-- | sys/dev/ic/aic6250.c | 1868 | ||||
-rw-r--r-- | sys/dev/ic/aic6250reg.h | 150 | ||||
-rw-r--r-- | sys/dev/ic/aic6250var.h | 228 |
7 files changed, 2412 insertions, 10 deletions
diff --git a/sys/arch/aviion/conf/GENERIC b/sys/arch/aviion/conf/GENERIC index d42322bdc85..77bc89a06a0 100644 --- a/sys/arch/aviion/conf/GENERIC +++ b/sys/arch/aviion/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.9 2013/09/24 20:14:36 miod Exp $ +# $OpenBSD: GENERIC,v 1.10 2013/10/15 01:41:46 miod Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -41,17 +41,19 @@ mainbus0 at root syscon0 at mainbus0 addr 0xfff00000 nvram0 at syscon? offset 0x80000 dart0 at syscon? offset 0x82000 # serial ports -#avkbd0 at syscon? offset 0x82800 +#avkbc0 at syscon? offset 0x82800 +#pckbd* at avkbc? #wskbd* at avkbd? +vme0 at syscon? offset 0x85000 # VME bridge #avfb0 at syscon? offset 0x89000 +#wsdisplay* at avfb? # # AV400 onboard devices # dart1 at syscon? offset 0x82c00 -vme0 at syscon? offset 0x85000 # VME bridge -#aic0 at syscon? offset 0x8a000 +oaic0 at syscon? offset 0x8a000 le0 at syscon? offset 0x8c000 # on-board ethernet # @@ -75,14 +77,10 @@ le* at vme? a32 0x55540000 a16 0x8600 ipl 3 # artwork X0B factory settings le* at vme? a32 0x55900000 a16 0x4000 ipl 3 le* at vme? a32 0x55980000 a16 0x5000 ipl 3 -# VDA/255 Serial Host Adapter (on-board on model 4300) -#vda0 at vme? a32 0x60000000 - # # SCSI devices # - scsibus* at scsi? sd* at scsibus? st* at scsibus? diff --git a/sys/arch/aviion/conf/files.aviion b/sys/arch/aviion/conf/files.aviion index 1be78251e22..5685b4e468a 100644 --- a/sys/arch/aviion/conf/files.aviion +++ b/sys/arch/aviion/conf/files.aviion @@ -1,4 +1,4 @@ -# $OpenBSD: files.aviion,v 1.12 2013/09/24 20:14:36 miod Exp $ +# $OpenBSD: files.aviion,v 1.13 2013/10/15 01:41:46 miod Exp $ # maxpartitions 16 @@ -26,6 +26,9 @@ file arch/aviion/dev/dart.c dart needs-count attach le at syscon with le_syscon: le24, le32 file arch/aviion/dev/if_le_syscon.c le_syscon +attach oaic at syscon with oaic_syscon +file arch/aviion/dev/oaic_syscon.c oaic_syscon + attach oosiop at syscon with oosiop_syscon file arch/aviion/dev/oosiop_syscon.c oosiop_syscon diff --git a/sys/arch/aviion/dev/oaic_syscon.c b/sys/arch/aviion/dev/oaic_syscon.c new file mode 100644 index 00000000000..83294ae47ce --- /dev/null +++ b/sys/arch/aviion/dev/oaic_syscon.c @@ -0,0 +1,151 @@ +/* $OpenBSD: oaic_syscon.c,v 1.1 2013/10/15 01:41:44 miod Exp $ */ + +/* + * Copyright (c) 2013 Miodrag Vallat. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/buf.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> + +#include <machine/autoconf.h> +#include <machine/board.h> +#include <machine/bus.h> +#include <machine/av400.h> + +#include <aviion/dev/sysconvar.h> + +#include <dev/ic/aic6250reg.h> +#include <dev/ic/aic6250var.h> + +int oaic_syscon_match(struct device *, void *, void *); +void oaic_syscon_attach(struct device *, struct device *, void *); + +struct oaic_syscon_softc { + struct aic6250_softc sc_base; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + struct intrhand sc_ih; +}; + +const struct cfattach oaic_syscon_ca = { + sizeof(struct oaic_syscon_softc), + oaic_syscon_match, oaic_syscon_attach +}; + +uint8_t oaic_read(struct aic6250_softc *, uint); +void oaic_write(struct aic6250_softc *, uint, uint8_t); + +int +oaic_syscon_match(struct device *parent, void *match, void *aux) +{ + struct confargs *ca = aux; + + switch (cpuid) { +#ifdef AV400 + case AVIION_300_310: + case AVIION_400_4000: + case AVIION_410_4100: + case AVIION_300C_310C: + case AVIION_300CD_310CD: + case AVIION_300D_310D: + case AVIION_4300_25: + case AVIION_4300_20: + case AVIION_4300_16: + switch (ca->ca_paddr) { + case AV400_SCSI: + break; + default: + return 0; + } + break; +#endif + default: + return 0; + } + + return 1; +} + +void +oaic_syscon_attach(struct device *parent, struct device *self, void *aux) +{ + struct oaic_syscon_softc *ssc = (struct oaic_syscon_softc *)self; + struct aic6250_softc *sc = (struct aic6250_softc *)self; + struct confargs *ca = aux; + bus_space_handle_t ioh; + int intsrc; + + if (bus_space_map(ca->ca_iot, ca->ca_paddr, AIC_NREG << 2, 0, + &ioh) != 0) { + printf(": can't map registers\n"); + return; + } + ssc->sc_iot = ca->ca_iot; + ssc->sc_ioh = ioh; + + /* + * Do NOT ask any question about this. + */ + *(volatile uint32_t *)0xfff840c0 = 0x6e; + + /* + * According to the hardware manual (chapter 10 ``Programming the + * Small Computer System Interface'', page 10-2), the ``Clock + * Frequency Mode'' bit in control register #1 must be clear. This + * hints the AIC6250 runs at 10MHz. + */ + sc->sc_freq = 10; + sc->sc_initiator = 7; + + /* port A is an output port, single-ended mode */ + sc->sc_cr0 = AIC_CR0_EN_PORT_A; + /* port B used as the upper 8 bits of the 16-bit DMA path */ + sc->sc_cr1 = AIC_CR1_ENABLE_16BIT_MEM_BUS; + + sc->sc_read = oaic_read; + sc->sc_write = oaic_write; + + aic6250_attach(sc); + + ssc->sc_ih.ih_fn = (int(*)(void *))aic6250_intr; + ssc->sc_ih.ih_arg = sc; + ssc->sc_ih.ih_flags = 0; + ssc->sc_ih.ih_ipl = IPL_BIO; + intsrc = INTSRC_SCSI1; + sysconintr_establish(intsrc, &ssc->sc_ih, self->dv_xname); +} + +uint8_t +oaic_read(struct aic6250_softc *sc, uint reg) +{ + struct oaic_syscon_softc *ssc = (struct oaic_syscon_softc *)sc; + uint32_t rc; + + rc = bus_space_read_4(ssc->sc_iot, ssc->sc_ioh, reg << 2); + return rc & 0xff; +} + +void +oaic_write(struct aic6250_softc *sc, uint reg, uint8_t val) +{ + struct oaic_syscon_softc *ssc = (struct oaic_syscon_softc *)sc; + + bus_space_write_4(ssc->sc_iot, ssc->sc_ioh, reg << 2, val); +} diff --git a/sys/conf/files b/sys/conf/files index 94292f0db0a..ec4484c6dbb 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.556 2013/10/13 10:10:02 reyk Exp $ +# $OpenBSD: files,v 1.557 2013/10/15 01:41:45 miod Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -514,6 +514,10 @@ file dev/softraid_concat.c softraid device spdmem file dev/spdmem.c spdmem +# AIC-6250 SCSI +device oaic: scsi +file dev/ic/aic6250.c oaic + # legitimate pseudo-devices pseudo-device vnd: disk pseudo-device rd: disk diff --git a/sys/dev/ic/aic6250.c b/sys/dev/ic/aic6250.c new file mode 100644 index 00000000000..c0c4f5c88a2 --- /dev/null +++ b/sys/dev/ic/aic6250.c @@ -0,0 +1,1868 @@ +/* $OpenBSD: aic6250.c,v 1.1 2013/10/15 01:41:45 miod Exp $ */ + +/* + * Copyright (c) 2010, 2013 Miodrag Vallat. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Derived from sys/dev/ic/aic6360.c under the following licence terms: + */ +/* OpenBSD: aic6360.c,v 1.26 2011/04/03 12:42:36 krw Exp */ +/* $NetBSD: aic6360.c,v 1.52 1996/12/10 21:27:51 thorpej Exp $ */ +/* + * Copyright (c) 1994, 1995, 1996 Charles Hannum. 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 Charles M. Hannum. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Copyright (c) 1994 Jarle Greipsland + * 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. 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. + */ + +/* + * Acknowledgements: Many of the algorithms used in this driver are + * inspired by the work of Julian Elischer (julian@tfs.com) and + * Charles Hannum (mycroft@duality.gnu.ai.mit.edu). Thanks a million! + */ + +/* TODO list: + * 1) Get the DMA stuff working. + * 2) Get the synch stuff working (requires DMA first). + */ + +/* + * A few customizable items: + */ + +/* Synchronous data transfers? */ +#define AIC_USE_SYNCHRONOUS 0 +#define AIC_SYNC_REQ_ACK_OFS 8 + +/* Wide data transfers? */ +#define AIC_USE_WIDE 0 +#define AIC_MAX_WIDTH 0 + +/* Include debug functions? At the end of this file there are a bunch of + * functions that will print out various information regarding queued SCSI + * commands, driver state and chip contents. You can call them from the + * kernel debugger. If you set AIC_DEBUG to 0 they are not included (the + * kernel uses less memory) but you lose the debugging facilities. + */ +#ifndef SMALL_KERNEL +#define AIC_DEBUG 1 +#endif + +#define AIC_ABORT_TIMEOUT 2000 /* time to wait for abort */ + +/* End of customizable parameters */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/device.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/queue.h> + +#include <machine/intr.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsi_message.h> +#include <scsi/scsiconf.h> + +#include <dev/ic/aic6250reg.h> +#include <dev/ic/aic6250var.h> + +#ifndef DDB +#define Debugger() panic("should call debugger here (aic6250.c)") +#endif /* ! DDB */ + +#ifdef AIC_DEBUG +int aic6250_debug = 0x00; /* AIC_SHOWSTART|AIC_SHOWMISC|AIC_SHOWTRACE; */ +#endif + +void aic6250_minphys(struct buf *, struct scsi_link *); +void aic6250_init(struct aic6250_softc *); +void aic6250_done(struct aic6250_softc *, struct aic6250_acb *); +void aic6250_dequeue(struct aic6250_softc *, struct aic6250_acb *); +void aic6250_scsi_cmd(struct scsi_xfer *); +int aic6250_poll(struct aic6250_softc *, struct scsi_xfer *, int); +void aic6250_sched_msgout(struct aic6250_softc *, uint8_t); +void aic6250_setsync(struct aic6250_softc *, struct aic6250_tinfo *); +void aic6250_select(struct aic6250_softc *, struct aic6250_acb *); +void aic6250_seltimeout(void *); +void aic6250_timeout(void *); +void aic6250_sched(struct aic6250_softc *); +void aic6250_scsi_reset(struct aic6250_softc *); +void aic6250_reset(struct aic6250_softc *); +void aic6250_acb_free(void *, void *); +void *aic6250_acb_alloc(void *); +int aic6250_reselect(struct aic6250_softc *, int); +void aic6250_sense(struct aic6250_softc *, struct aic6250_acb *); +void aic6250_msgin(struct aic6250_softc *); +void aic6250_abort(struct aic6250_softc *, struct aic6250_acb *); +void aic6250_msgout(struct aic6250_softc *); +void aic6250_ack(struct aic6250_softc *); +int aic6250_dataout_pio(struct aic6250_softc *, uint8_t *, int, int); +int aic6250_datain_pio(struct aic6250_softc *, uint8_t *, int, int); +#ifdef AIC_DEBUG +void aic6250_print_acb(struct aic6250_acb *); +void aic6250_dump_driver(struct aic6250_softc *); +void aic6250_show_scsi_cmd(struct aic6250_acb *); +void aic6250_print_active_acb(void); +#endif + +struct cfdriver oaic_cd = { + NULL, "oaic", DV_DULL +}; + +struct scsi_adapter aic6250_switch = { + .scsi_cmd = aic6250_scsi_cmd, +#ifdef notyet + .scsi_minphys = aic6250_minphys, +#else + .scsi_minphys = scsi_minphys, +#endif +}; + +/* + * Attach the AIC6250, fill out some high and low level data structures + */ +void +aic6250_attach(struct aic6250_softc *sc) +{ + struct scsibus_attach_args saa; + AIC_TRACE(("aic6250_attach ")); + + printf(": revision %d\n", + (*sc->sc_read)(sc, AIC_REV_CNTRL) & AIC_RC_MASK); + sc->sc_state = AIC_INIT; + + if (sc->sc_freq >= 20) + sc->sc_cr1 |= AIC_CR1_CLK_FREQ_MODE; + + /* + * These are the bounds of the sync period, based on the frequency of + * the chip's clock input and the size and offset of the sync period + * register. + * + * For a 20MHz clock, this gives us 25, or 100nS, or 10MB/s, as a + * maximum transfer rate, and 112.5, or 450nS, or 2.22MB/s, as a + * minimum transfer rate. + */ + sc->sc_minsync = (2 * 250) / sc->sc_freq; + sc->sc_maxsync = (9 * 250) / sc->sc_freq; + + timeout_set(&sc->sc_seltimeout, aic6250_seltimeout, sc); + + aic6250_init(sc); /* init chip and driver */ + + /* + * Fill in the prototype scsi_link + */ + sc->sc_link.adapter_softc = sc; + sc->sc_link.adapter_target = sc->sc_initiator; + sc->sc_link.adapter = &aic6250_switch; + sc->sc_link.openings = 2; + sc->sc_link.pool = &sc->sc_iopool; + + bzero(&saa, sizeof(saa)); + saa.saa_sc_link = &sc->sc_link; + + config_found(&sc->sc_dev, &saa, scsiprint); +} + +/* + * Initialize AIC6250 chip itself. + */ +void +aic6250_reset(struct aic6250_softc *sc) +{ + /* reset chip */ + (*sc->sc_write)(sc, AIC_CONTROL_REG1, AIC_CR1_CHIP_SW_RESET); + delay(200); + (*sc->sc_write)(sc, AIC_CONTROL_REG1, 0); + + (*sc->sc_write)(sc, AIC_CONTROL_REG1, sc->sc_cr1); + (*sc->sc_write)(sc, AIC_CONTROL_REG0, sc->sc_cr0 | sc->sc_initiator); + /* asynchronous operation */ + (*sc->sc_write)(sc, AIC_OFFSET_CNTRL, 0); + + sc->sc_imr0 = sc->sc_imr1 = 0; + (*sc->sc_write)(sc, AIC_INT_MSK_REG0, sc->sc_imr0); + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + + (*sc->sc_write)(sc, AIC_DMA_BYTE_COUNT_L, 0); + (*sc->sc_write)(sc, AIC_DMA_BYTE_COUNT_M, 0); + (*sc->sc_write)(sc, AIC_DMA_BYTE_COUNT_H, 0); + (*sc->sc_write)(sc, AIC_DMA_CNTRL, 0); + (*sc->sc_write)(sc, AIC_PORT_A, 0); + (*sc->sc_write)(sc, AIC_PORT_B, 0); +} + +/* Pull the SCSI RST line for 500 us */ +void +aic6250_scsi_reset(struct aic6250_softc *sc) +{ + /* reset SCSI bus */ + (*sc->sc_write)(sc, AIC_CONTROL_REG1, + sc->sc_cr1 | AIC_CR1_SCSI_RST_OUT); + delay(500); + (*sc->sc_write)(sc, AIC_CONTROL_REG1, sc->sc_cr1); + delay(50); +} + +/* + * Initialize aic SCSI driver. + */ +void +aic6250_init(struct aic6250_softc *sc) +{ + struct aic6250_acb *acb; + int r; + + aic6250_reset(sc); + aic6250_scsi_reset(sc); + aic6250_reset(sc); + + if (sc->sc_state == AIC_INIT) { + /* First time through; initialize. */ + TAILQ_INIT(&sc->ready_list); + TAILQ_INIT(&sc->nexus_list); + TAILQ_INIT(&sc->free_list); + mtx_init(&sc->sc_acb_mtx, IPL_BIO); + scsi_iopool_init(&sc->sc_iopool, sc, aic6250_acb_alloc, + aic6250_acb_free); + sc->sc_nexus = NULL; + acb = sc->sc_acb; + bzero(acb, sizeof(sc->sc_acb)); + for (r = 0; r < sizeof(sc->sc_acb) / sizeof(*acb); r++) { + TAILQ_INSERT_TAIL(&sc->free_list, acb, chain); + acb++; + } + bzero(&sc->sc_tinfo, sizeof(sc->sc_tinfo)); + } else { + /* Cancel any active commands. */ + timeout_del(&sc->sc_seltimeout); + sc->sc_state = AIC_CLEANING; + if ((acb = sc->sc_nexus) != NULL) { + acb->xs->error = XS_DRIVER_STUFFUP; + timeout_del(&acb->xs->stimeout); + aic6250_done(sc, acb); + } + while ((acb = TAILQ_FIRST(&sc->nexus_list)) != NULL) { + acb->xs->error = XS_DRIVER_STUFFUP; + timeout_del(&acb->xs->stimeout); + aic6250_done(sc, acb); + } + } + + sc->sc_prevphase = PH_INVALID; + for (r = 0; r < 8; r++) { + struct aic6250_tinfo *ti = &sc->sc_tinfo[r]; + + ti->flags = 0; +#if AIC_USE_SYNCHRONOUS + ti->flags |= DO_SYNC; + ti->period = sc->sc_minsync; + ti->offset = AIC_SYNC_REQ_ACK_OFS; +#else + ti->period = ti->offset = 0; +#endif +#if AIC_USE_WIDE + ti->flags |= DO_WIDE; + ti->width = AIC_MAX_WIDTH; +#else + ti->width = 0; +#endif + } + + sc->sc_state = AIC_IDLE; + sc->sc_imr0 = AIC_IMR_EN_ERROR_INT; + sc->sc_imr1 = AIC_IMR1_EN_SCSI_RST_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG0, sc->sc_imr0); + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); +} + +void +aic6250_acb_free(void *xsc, void *xacb) +{ + struct aic6250_softc *sc = xsc; + struct aic6250_acb *acb = xacb; + + mtx_enter(&sc->sc_acb_mtx); + acb->flags = 0; + TAILQ_INSERT_HEAD(&sc->free_list, acb, chain); + mtx_leave(&sc->sc_acb_mtx); +} + +void * +aic6250_acb_alloc(void *xsc) +{ + struct aic6250_softc *sc = xsc; + struct aic6250_acb *acb; + + mtx_enter(&sc->sc_acb_mtx); + acb = TAILQ_FIRST(&sc->free_list); + if (acb) { + TAILQ_REMOVE(&sc->free_list, acb, chain); + acb->flags |= ACB_ALLOC; + } + mtx_leave(&sc->sc_acb_mtx); + + return acb; +} + +/* + * DRIVER FUNCTIONS CALLABLE FROM HIGHER LEVEL DRIVERS + */ + +/* + * Expected sequence: + * 1) Command inserted into ready list + * 2) Command selected for execution + * 3) Command won arbitration and has selected target device + * 4) Send message out (identify message, eventually also sync.negotiations) + * 5) Send command + * 5a) Receive disconnect message, disconnect. + * 5b) Reselected by target + * 5c) Receive identify message from target. + * 6) Send or receive data + * 7) Receive status + * 8) Receive message (command complete etc.) + * 9) If status == SCSI_CHECK construct a synthetic request sense SCSI cmd. + * Repeat 2-8 (no disconnects please...) + */ + +/* + * Start a SCSI-command + * This function is called by the higher level SCSI-driver to queue/run + * SCSI-commands. + */ +void +aic6250_scsi_cmd(struct scsi_xfer *xs) +{ + struct scsi_link *sc_link = xs->sc_link; + struct aic6250_softc *sc = sc_link->adapter_softc; + struct aic6250_acb *acb; + int s, flags; + + AIC_TRACE(("aic6250_scsi_cmd ")); + AIC_CMDS(("[0x%x, %d]->%d ", (int)xs->cmd->opcode, xs->cmdlen, + sc_link->target)); + + flags = xs->flags; + acb = xs->io; + + /* Initialize acb */ + acb->xs = xs; + acb->timeout = xs->timeout; + timeout_set(&xs->stimeout, aic6250_timeout, acb); + + if (xs->flags & SCSI_RESET) { + acb->flags |= ACB_RESET; + acb->scsi_cmd_length = 0; + acb->data_length = 0; + } else { + bcopy(xs->cmd, &acb->scsi_cmd, xs->cmdlen); + acb->scsi_cmd_length = xs->cmdlen; + acb->data_addr = xs->data; + acb->data_length = xs->datalen; + } + acb->target_stat = 0; + + s = splbio(); + + TAILQ_INSERT_TAIL(&sc->ready_list, acb, chain); + if (sc->sc_state == AIC_IDLE) + aic6250_sched(sc); + + splx(s); + + if ((flags & SCSI_POLL) == 0) + return; + + /* Not allowed to use interrupts, use polling instead */ + if (aic6250_poll(sc, xs, acb->timeout)) { + aic6250_timeout(acb); + if (aic6250_poll(sc, xs, acb->timeout)) + aic6250_timeout(acb); + } +} + +#ifdef notyet +/* + * Adjust transfer size in buffer structure + */ +void +aic6250_minphys(struct buf *bp, struct scsi_link *sl) +{ + + AIC_TRACE(("aic6250_minphys ")); + if (bp->b_bcount > (AIC_NSEG << PGSHIFT)) + bp->b_bcount = (AIC_NSEG << PGSHIFT); + minphys(bp); +} +#endif + +/* + * Used when interrupt driven I/O isn't allowed, e.g. during boot. + */ +int +aic6250_poll(struct aic6250_softc *sc, struct scsi_xfer *xs, int count) +{ + int s; + uint8_t sr0, sr1, sr0mask, sr1mask; + + AIC_TRACE(("aic6250_poll ")); + while (count) { + /* + * If we had interrupts enabled, would we + * have got an interrupt? + */ + sr0mask = 0; + sr1mask = 0; + + if (sc->sc_imr0 & AIC_IMR_EN_ERROR_INT) + sr1mask |= AIC_SR1_ERROR; + if (sc->sc_imr0 & AIC_IMR_EN_CMD_DONE_INT) + sr1mask |= AIC_SR1_CMD_DONE; + if (sc->sc_imr0 & AIC_IMR_EN_SEL_OUT_INT) + sr1mask |= AIC_SR1_SEL_OUT; + if (sc->sc_imr0 & AIC_IMR_EN_RESEL_INT) + sr1mask |= AIC_SR1_RESELECTED; + if (sc->sc_imr0 & AIC_IMR_EN_SELECT_INT) + sr1mask |= AIC_SR1_SELECTED; + + if (sc->sc_imr1 & AIC_IMR1_EN_SCSI_RST_INT) + sr0mask |= AIC_SR0_SCSI_RST_OCCURED; +#if 0 /* these bits are never set */ + if (sc->sc_imr1 & AIC_IMR1_EN_MEM_PARITY_ERR_INT) + sr0mask |= AIC_SR0_MEMORY_PARITY_ERR; + if (sc->sc_imr1 & AIC_IMR1_EN_PHASE_MISMATCH_INT) + sr0mask |= AIC_SR0_PHASE_MISMATCH_ERR; +#endif + if (sc->sc_imr1 & AIC_IMR1_EN_BUS_FREE_DETECT_INT) + sr0mask |= AIC_SR0_BUS_FREE_DETECT; + if (sc->sc_imr1 & AIC_IMR1_EN_SCSI_PARITY_ERR_INT) + sr0mask |= AIC_SR0_SCSI_PARITY_ERR; + if (sc->sc_imr1 & AIC_IMR1_EN_PHASE_CHANGE_INT) + sr0mask |= AIC_SR0_SCSI_PHASE_CHG_ATTN; + + sr0 = (*sc->sc_read)(sc, AIC_STATUS_REG0); + sr1 = (*sc->sc_read)(sc, AIC_STATUS_REG1); + + if ((sr0 & sr0mask) != 0 || (sr1 & sr1mask) != 0) { + s = splbio(); + aic6250_intr(sc); + splx(s); + } + if ((xs->flags & ITSDONE) != 0) + return 0; + delay(1000); + count--; + + /* process the selection timeout timer as well if necessary */ + if (sc->sc_selto != 0) { + sc->sc_selto--; + if (sc->sc_selto == 0) { + aic6250_seltimeout(sc); + } + } + } + return 1; +} + +/* + * LOW LEVEL SCSI UTILITIES + */ + +void +aic6250_ack(struct aic6250_softc *sc) +{ + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, + (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG) | AIC_SS_ACK_OUT); + while (((*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG) & AIC_SS_REQ_IN) != 0) + continue; + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, + (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG) & ~AIC_SS_ACK_OUT); +} + +void +aic6250_sched_msgout(struct aic6250_softc *sc, uint8_t m) +{ + if (sc->sc_msgpriq == 0) + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, + sc->sc_phase | AIC_SS_ATN_OUT); + sc->sc_msgpriq |= m; +} + +/* + * Set synchronous transfer offset and period. + */ +void +aic6250_setsync(struct aic6250_softc *sc, struct aic6250_tinfo *ti) +{ +#if AIC_USE_SYNCHRONOUS + if (ti->offset != 0) + (*sc->sc_write)(sc, AIC_OFFSET_CNTRL, + ((((ti->period * sc->sc_freq) / 250 - 2) << + AIC_OC_SYNC_XFER_SHIFT) & AIC_OC_SYNC_XFER_MASK) | + ti->offset); + else + (*sc->sc_write)(sc, AIC_OFFSET_CNTRL, 0); +#endif +} + +/* + * Start a selection. This is used by aic6250_sched() to select an idle target, + * and by aic6250_done() to immediately reselect a target to get sense + * information. + */ +void +aic6250_select(struct aic6250_softc *sc, struct aic6250_acb *acb) +{ + struct scsi_link *sc_link = acb->xs->sc_link; + int target = sc_link->target; + struct aic6250_tinfo *ti = &sc->sc_tinfo[target]; + + (*sc->sc_write)(sc, AIC_SCSI_ID_DATA, + (1 << sc->sc_initiator) | (1 << target)); + aic6250_setsync(sc, ti); + + /* Always enable reselections. */ + sc->sc_imr1 |= AIC_IMR1_EN_SCSI_RST_INT; + sc->sc_imr1 &= + ~(AIC_IMR1_EN_SCSI_REQ_ON_INT | AIC_IMR1_EN_SCSI_PARITY_ERR_INT | + AIC_IMR1_EN_BUS_FREE_DETECT_INT | AIC_IMR1_EN_PHASE_CHANGE_INT); + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + sc->sc_imr0 = AIC_IMR_ARB_SEL_START | AIC_IMR_EN_ERROR_INT | + AIC_IMR_EN_CMD_DONE_INT | AIC_IMR_EN_SEL_OUT_INT | + AIC_IMR_EN_RESEL_INT | AIC_IMR_EN_SELECT_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG0, sc->sc_imr0); + + sc->sc_state = AIC_SELECTING; +} + +int +aic6250_reselect(struct aic6250_softc *sc, int message) +{ + uint8_t selid, target, lun; + struct aic6250_acb *acb; + struct scsi_link *sc_link; + struct aic6250_tinfo *ti; + + /* + * The SCSI chip made a snapshot of the data bus while the reselection + * was being negotiated. This enables us to determine which target did + * the reselect. + */ + selid = sc->sc_selid & ~(1 << sc->sc_initiator); + if (selid & (selid - 1)) { + printf("%s: reselect with invalid selid %02x; ", + sc->sc_dev.dv_xname, selid); + printf("sending DEVICE RESET\n"); + AIC_BREAK(); + goto reset; + } + + /* Search wait queue for disconnected cmd + * The list should be short, so I haven't bothered with + * any more sophisticated structures than a simple + * singly linked list. + */ + target = ffs(selid) - 1; + lun = message & 0x07; + TAILQ_FOREACH(acb, &sc->nexus_list, chain) { + sc_link = acb->xs->sc_link; + if (sc_link->target == target && sc_link->lun == lun) + break; + } + if (acb == NULL) { + printf("%s: reselect from target %d lun %d with no nexus; ", + sc->sc_dev.dv_xname, target, lun); + printf("sending ABORT\n"); + AIC_BREAK(); + goto abort; + } + + /* Make this nexus active again. */ + TAILQ_REMOVE(&sc->nexus_list, acb, chain); + sc->sc_state = AIC_CONNECTED; + sc->sc_nexus = acb; + ti = &sc->sc_tinfo[target]; + ti->lubusy |= (1 << lun); + aic6250_setsync(sc, ti); + + if (acb->flags & ACB_RESET) + aic6250_sched_msgout(sc, SEND_DEV_RESET); + else if (acb->flags & ACB_ABORT) + aic6250_sched_msgout(sc, SEND_ABORT); + + /* Do an implicit RESTORE POINTERS. */ + sc->sc_dp = acb->data_addr; + sc->sc_dleft = acb->data_length; + sc->sc_cp = (uint8_t *)&acb->scsi_cmd; + sc->sc_cleft = acb->scsi_cmd_length; + + return (0); + +reset: + aic6250_sched_msgout(sc, SEND_DEV_RESET); + return (1); + +abort: + aic6250_sched_msgout(sc, SEND_ABORT); + return (1); +} + +/* + * Schedule a SCSI operation. This has now been pulled out of the interrupt + * handler so that we may call it from aic6250_scsi_cmd and aic6250_done. + * This may save us an unnecessary interrupt just to get things going. + * Should only be called when state == AIC_IDLE and at bio pl. + */ +void +aic6250_sched(struct aic6250_softc *sc) +{ + struct aic6250_acb *acb; + struct scsi_link *sc_link; + struct aic6250_tinfo *ti; + + /* + * Find first acb in ready queue that is for a target/lunit pair that + * is not busy. + */ + TAILQ_FOREACH(acb, &sc->ready_list, chain) { + sc_link = acb->xs->sc_link; + ti = &sc->sc_tinfo[sc_link->target]; + if ((ti->lubusy & (1 << sc_link->lun)) == 0) { + AIC_MISC(("selecting %d:%d ", + sc_link->target, sc_link->lun)); + TAILQ_REMOVE(&sc->ready_list, acb, chain); + sc->sc_nexus = acb; + aic6250_select(sc, acb); + return; + } else + AIC_MISC(("%d:%d busy\n", + sc_link->target, sc_link->lun)); + } + AIC_MISC(("idle ")); + /* Nothing to start; just enable reselections and wait. */ + sc->sc_imr1 |= AIC_IMR1_EN_SCSI_RST_INT; + sc->sc_imr1 &= + ~(AIC_IMR1_EN_SCSI_REQ_ON_INT | AIC_IMR1_EN_SCSI_PARITY_ERR_INT | + AIC_IMR1_EN_BUS_FREE_DETECT_INT | AIC_IMR1_EN_PHASE_CHANGE_INT); + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + sc->sc_imr0 = AIC_IMR_EN_ERROR_INT | + AIC_IMR_EN_RESEL_INT | AIC_IMR_EN_SELECT_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG0, sc->sc_imr0); +} + +void +aic6250_sense(struct aic6250_softc *sc, struct aic6250_acb *acb) +{ + struct scsi_xfer *xs = acb->xs; + struct scsi_link *sc_link = xs->sc_link; + struct aic6250_tinfo *ti = &sc->sc_tinfo[sc_link->target]; + struct scsi_sense *ss = (void *)&acb->scsi_cmd; + + AIC_MISC(("requesting sense ")); + /* Next, setup a request sense command block */ + bzero(ss, sizeof(*ss)); + ss->opcode = REQUEST_SENSE; + ss->byte2 = sc_link->lun << 5; + ss->length = sizeof(struct scsi_sense_data); + acb->scsi_cmd_length = sizeof(*ss); + acb->data_addr = (char *)&xs->sense; + acb->data_length = sizeof(struct scsi_sense_data); + acb->flags |= ACB_SENSE; + ti->senses++; + if (acb->flags & ACB_NEXUS) + ti->lubusy &= ~(1 << sc_link->lun); + if (acb == sc->sc_nexus) { + aic6250_select(sc, acb); + } else { + aic6250_dequeue(sc, acb); + TAILQ_INSERT_HEAD(&sc->ready_list, acb, chain); + if (sc->sc_state == AIC_IDLE) + aic6250_sched(sc); + } +} + +/* + * POST PROCESSING OF SCSI_CMD (usually current) + */ +void +aic6250_done(struct aic6250_softc *sc, struct aic6250_acb *acb) +{ + struct scsi_xfer *xs = acb->xs; + struct scsi_link *sc_link = xs->sc_link; + struct aic6250_tinfo *ti = &sc->sc_tinfo[sc_link->target]; + + AIC_TRACE(("aic6250_done ")); + + /* + * Now, if we've come here with no error code, i.e. we've kept the + * initial XS_NOERROR, and the status code signals that we should + * check sense, we'll need to set up a request sense cmd block and + * push the command back into the ready queue *before* any other + * commands for this target/lunit, else we lose the sense info. + * We don't support chk sense conditions for the request sense cmd. + */ + if (xs->error == XS_NOERROR) { + if (acb->flags & ACB_ABORT) { + xs->error = XS_DRIVER_STUFFUP; + } else if (acb->flags & ACB_SENSE) { + xs->error = XS_SENSE; + } else if (acb->target_stat == SCSI_CHECK) { + /* First, save the return values */ + xs->resid = acb->data_length; + xs->status = acb->target_stat; + aic6250_sense(sc, acb); + return; + } else { + xs->resid = acb->data_length; + } + } + +#ifdef AIC_DEBUG + if ((aic6250_debug & AIC_SHOWMISC) != 0) { + if (xs->resid != 0) + printf("resid=%lu ", (u_long)xs->resid); + if (xs->error == XS_SENSE) + printf("sense=0x%02x\n", xs->sense.error_code); + else + printf("error=%d\n", xs->error); + } +#endif + + /* + * Remove the ACB from whatever queue it happens to be on. + */ + if (acb->flags & ACB_NEXUS) + ti->lubusy &= ~(1 << sc_link->lun); + if (acb == sc->sc_nexus) { + sc->sc_nexus = NULL; + sc->sc_state = AIC_IDLE; + aic6250_sched(sc); + } else + aic6250_dequeue(sc, acb); + + ti->cmds++; + scsi_done(xs); +} + +void +aic6250_dequeue(struct aic6250_softc *sc, struct aic6250_acb *acb) +{ + + if (acb->flags & ACB_NEXUS) { + TAILQ_REMOVE(&sc->nexus_list, acb, chain); + } else { + TAILQ_REMOVE(&sc->ready_list, acb, chain); + } +} + +/* + * INTERRUPT/PROTOCOL ENGINE + */ + +/* + * Precondition: + * The SCSI bus is already in the MSGI phase and there is a message byte + * on the bus, along with an asserted REQ signal. + */ +void +aic6250_msgin(struct aic6250_softc *sc) +{ + uint8_t sr0, scsisig; + int n; + uint8_t msgbyte; + + AIC_TRACE(("aic6250_msgin ")); + + if (sc->sc_prevphase == PH_MSGIN) { + /* This is a continuation of the previous message. */ + n = sc->sc_imp - sc->sc_imess; + goto nextbyte; + } + + /* This is a new MESSAGE IN phase. Clean up our state. */ + sc->sc_flags &= ~AIC_DROP_MSGIN; + +nextmsg: + n = 0; + sc->sc_imp = &sc->sc_imess[n]; + +nextbyte: + /* + * Read a whole message, but don't ack the last byte. If we reject the + * message, we have to assert ATN during the message transfer phase + * itself. + */ + for (;;) { + for (;;) { + scsisig = (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG); + if ((scsisig & PH_MASK) != PH_MSGIN) { + /* + * Target left MESSAGE IN, probably because it + * a) noticed our ATN signal, or + * b) ran out of messages. + */ + goto out; + } + if ((scsisig & AIC_SS_REQ_IN) != 0) + break; + } + + /* If parity error, just dump everything on the floor. */ + sr0 = (*sc->sc_read)(sc, AIC_STATUS_REG0); + if ((sr0 & AIC_SR0_SCSI_PARITY_ERR) != 0) { + sc->sc_flags |= AIC_DROP_MSGIN; + aic6250_sched_msgout(sc, SEND_PARITY_ERROR); + } + + /* Gather incoming message bytes if needed. */ + msgbyte = (*sc->sc_read)(sc, AIC_SCSI_ID_DATA); + if ((sc->sc_flags & AIC_DROP_MSGIN) == 0) { + if (n >= AIC_MAX_MSG_LEN) { + sc->sc_flags |= AIC_DROP_MSGIN; + aic6250_sched_msgout(sc, SEND_REJECT); + } else { + *sc->sc_imp++ = msgbyte; + n++; + + /* + * This testing is suboptimal, but most + * messages will be of the one byte variety, so + * it should not affect performance + * significantly. + */ + if (n == 1 && IS1BYTEMSG(sc->sc_imess[0])) + break; + if (n == 2 && IS2BYTEMSG(sc->sc_imess[0])) + break; + if (n >= 3 && ISEXTMSG(sc->sc_imess[0]) && + n == sc->sc_imess[1] + 2) + break; + } + } + + /* + * If we reach this spot we're either: + * a) in the middle of a multi-byte message, or + * b) dropping bytes. + */ + aic6250_ack(sc); + } + + AIC_MISC(("n=%d imess=0x%02x ", n, sc->sc_imess[0])); + + /* We now have a complete message. Parse it. */ + switch (sc->sc_state) { + struct aic6250_acb *acb; + struct scsi_link *sc_link; + struct aic6250_tinfo *ti; + + case AIC_CONNECTED: + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + ti = &sc->sc_tinfo[acb->xs->sc_link->target]; + + switch (sc->sc_imess[0]) { + case MSG_CMDCOMPLETE: + if ((long)sc->sc_dleft < 0) { + sc_link = acb->xs->sc_link; + printf("%s: %lu extra bytes from %d:%d\n", + sc->sc_dev.dv_xname, (u_long)-sc->sc_dleft, + sc_link->target, sc_link->lun); + acb->data_length = 0; + } + acb->xs->resid = acb->data_length = sc->sc_dleft; + sc->sc_state = AIC_CMDCOMPLETE; + break; + + case MSG_PARITY_ERROR: + /* Resend the last message. */ + aic6250_sched_msgout(sc, sc->sc_lastmsg); + break; + + case MSG_MESSAGE_REJECT: + AIC_MISC(("message rejected %02x ", sc->sc_lastmsg)); + switch (sc->sc_lastmsg) { +#if AIC_USE_SYNCHRONOUS + AIC_USE_WIDE + case SEND_IDENTIFY: + ti->flags &= ~(DO_SYNC | DO_WIDE); + ti->period = ti->offset = 0; + aic6250_setsync(sc, ti); + ti->width = 0; + break; +#endif +#if AIC_USE_SYNCHRONOUS + case SEND_SDTR: + ti->flags &= ~DO_SYNC; + ti->period = ti->offset = 0; + aic6250_setsync(sc, ti); + break; +#endif +#if AIC_USE_WIDE + case SEND_WDTR: + ti->flags &= ~DO_WIDE; + ti->width = 0; + break; +#endif + case SEND_INIT_DET_ERR: + aic6250_sched_msgout(sc, SEND_ABORT); + break; + } + break; + + case MSG_NOOP: + break; + + case MSG_DISCONNECT: + ti->dconns++; + sc->sc_state = AIC_DISCONNECT; + break; + + case MSG_SAVEDATAPOINTER: + acb->data_addr = sc->sc_dp; + acb->data_length = sc->sc_dleft; + break; + + case MSG_RESTOREPOINTERS: + sc->sc_dp = acb->data_addr; + sc->sc_dleft = acb->data_length; + sc->sc_cp = (uint8_t *)&acb->scsi_cmd; + sc->sc_cleft = acb->scsi_cmd_length; + break; + + case MSG_EXTENDED: + switch (sc->sc_imess[2]) { +#if AIC_USE_SYNCHRONOUS + case MSG_EXT_SDTR: + if (sc->sc_imess[1] != 3) + goto reject; + ti->period = sc->sc_imess[3]; + ti->offset = sc->sc_imess[4]; + ti->flags &= ~DO_SYNC; + if (ti->offset == 0) { + } else if (ti->period < sc->sc_minsync || + ti->period > sc->sc_maxsync || + ti->offset > 8) { + ti->period = ti->offset = 0; + aic6250_sched_msgout(sc, SEND_SDTR); + } else { + sc_print_addr(acb->xs->sc_link); + printf("sync, offset %d, ", + ti->offset); + printf("period %dnsec\n", + ti->period * 4); + } + aic6250_setsync(sc, ti); + break; +#endif + +#if AIC_USE_WIDE + case MSG_EXT_WDTR: + if (sc->sc_imess[1] != 2) + goto reject; + ti->width = sc->sc_imess[3]; + ti->flags &= ~DO_WIDE; + if (ti->width == 0) { + } else if (ti->width > AIC_MAX_WIDTH) { + ti->width = 0; + aic6250_sched_msgout(sc, SEND_WDTR); + } else { + sc_print_addr(acb->xs->sc_link); + printf("wide, width %d\n", + 1 << (3 + ti->width)); + } + break; +#endif + + default: + printf("%s: unrecognized MESSAGE EXTENDED; ", + sc->sc_dev.dv_xname); + printf("sending REJECT\n"); + AIC_BREAK(); + goto reject; + } + break; + + default: + printf("%s: unrecognized MESSAGE; sending REJECT\n", + sc->sc_dev.dv_xname); + AIC_BREAK(); + reject: + aic6250_sched_msgout(sc, SEND_REJECT); + break; + } + break; + + case AIC_RESELECTED: + if (!MSG_ISIDENTIFY(sc->sc_imess[0])) { + printf("%s: reselect without IDENTIFY; ", + sc->sc_dev.dv_xname); + printf("sending DEVICE RESET\n"); + AIC_BREAK(); + goto reset; + } + + (void) aic6250_reselect(sc, sc->sc_imess[0]); + break; + + default: + printf("%s: unexpected MESSAGE IN; sending DEVICE RESET\n", + sc->sc_dev.dv_xname); + AIC_BREAK(); + reset: + aic6250_sched_msgout(sc, SEND_DEV_RESET); + break; + +#ifdef notdef + abort: + aic6250_sched_msgout(sc, SEND_ABORT); + break; +#endif + } + + aic6250_ack(sc); + + /* Go get the next message, if any. */ + goto nextmsg; + +out: + AIC_MISC(("n=%d imess=0x%02x ", n, sc->sc_imess[0])); + + /* + * We need to explicitely un-busy. + */ + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, + (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG) & + ~(AIC_SS_SEL_OUT | AIC_SS_BSY_OUT | AIC_SS_ACK_OUT)); +} + +/* + * Send the highest priority, scheduled message. + */ +void +aic6250_msgout(struct aic6250_softc *sc) +{ +#if AIC_USE_SYNCHRONOUS + struct aic6250_tinfo *ti; +#endif + uint8_t scsisig; + int n; + + AIC_TRACE(("aic6250_msgout ")); + + if (sc->sc_prevphase == PH_MSGOUT) { + if (sc->sc_omp == sc->sc_omess) { + /* + * This is a retransmission. + * + * We get here if the target stayed in MESSAGE OUT + * phase. Section 5.1.9.2 of the SCSI 2 spec indicates + * that all of the previously transmitted messages must + * be sent again, in the same order. Therefore, we + * requeue all the previously transmitted messages, and + * start again from the top. Our simple priority + * scheme keeps the messages in the right order. + */ + AIC_MISC(("retransmitting ")); + sc->sc_msgpriq |= sc->sc_msgoutq; + /* + * Set ATN. If we're just sending a trivial 1-byte + * message, we'll clear ATN later on anyway. + */ + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, + PH_MSGOUT | AIC_SS_ATN_OUT); + } else { + /* This is a continuation of the previous message. */ + n = sc->sc_omp - sc->sc_omess; + goto nextbyte; + } + } + + /* No messages transmitted so far. */ + sc->sc_msgoutq = 0; + sc->sc_lastmsg = 0; + +nextmsg: + /* Pick up highest priority message. */ + sc->sc_currmsg = sc->sc_msgpriq & -sc->sc_msgpriq; + sc->sc_msgpriq &= ~sc->sc_currmsg; + sc->sc_msgoutq |= sc->sc_currmsg; + + /* Build the outgoing message data. */ + switch (sc->sc_currmsg) { + case SEND_IDENTIFY: + AIC_ASSERT(sc->sc_nexus != NULL); + sc->sc_omess[0] = + MSG_IDENTIFY(sc->sc_nexus->xs->sc_link->lun, 1); + n = 1; + break; + +#if AIC_USE_SYNCHRONOUS + case SEND_SDTR: + AIC_ASSERT(sc->sc_nexus != NULL); + ti = &sc->sc_tinfo[sc->sc_nexus->xs->sc_link->target]; + sc->sc_omess[4] = MSG_EXTENDED; + sc->sc_omess[3] = 3; + sc->sc_omess[2] = MSG_EXT_SDTR; + sc->sc_omess[1] = ti->period >> 2; + sc->sc_omess[0] = ti->offset; + n = 5; + break; +#endif + +#if AIC_USE_WIDE + case SEND_WDTR: + AIC_ASSERT(sc->sc_nexus != NULL); + ti = &sc->sc_tinfo[sc->sc_nexus->xs->sc_link->target]; + sc->sc_omess[3] = MSG_EXTENDED; + sc->sc_omess[2] = 2; + sc->sc_omess[1] = MSG_EXT_WDTR; + sc->sc_omess[0] = ti->width; + n = 4; + break; +#endif + + case SEND_DEV_RESET: + sc->sc_flags |= AIC_ABORTING; + sc->sc_omess[0] = MSG_BUS_DEV_RESET; + n = 1; + break; + + case SEND_REJECT: + sc->sc_omess[0] = MSG_MESSAGE_REJECT; + n = 1; + break; + + case SEND_PARITY_ERROR: + sc->sc_omess[0] = MSG_PARITY_ERROR; + n = 1; + break; + + case SEND_INIT_DET_ERR: + sc->sc_omess[0] = MSG_INITIATOR_DET_ERR; + n = 1; + break; + + case SEND_ABORT: + sc->sc_flags |= AIC_ABORTING; + sc->sc_omess[0] = MSG_ABORT; + n = 1; + break; + + default: + printf("%s: unexpected MESSAGE OUT; sending NOOP\n", + sc->sc_dev.dv_xname); + AIC_BREAK(); + sc->sc_omess[0] = MSG_NOOP; + n = 1; + break; + } + sc->sc_omp = &sc->sc_omess[n]; + +nextbyte: + /* Send message bytes. */ + for (;;) { + for (;;) { + scsisig = (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG); + if ((scsisig & PH_MASK) != PH_MSGOUT) { + /* + * Target left MESSAGE OUT, possibly to reject + * our message. + * + * If this is the last message being sent, then + * we deassert ATN, since either the target is + * going to ignore this message, or it's going + * to ask for a retransmission via MESSAGE + * PARITY ERROR (in which case we reassert ATN + * anyway). + */ + if (sc->sc_msgpriq == 0) + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, + scsisig & ~AIC_SS_ATN_OUT); + return; + } + if ((scsisig & AIC_SS_REQ_IN) != 0) + break; + } + + /* Clear ATN before last byte if this is the last message. */ + if (n == 1 && sc->sc_msgpriq == 0) + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, + scsisig & ~AIC_SS_ATN_OUT); + /* Send message byte. */ + (*sc->sc_write)(sc, AIC_SCSI_ID_DATA, *--sc->sc_omp); + --n; + /* Keep track of the last message we've sent any bytes of. */ + sc->sc_lastmsg = sc->sc_currmsg; + + aic6250_ack(sc); + + if (n == 0) + break; + } + + /* We get here only if the entire message has been transmitted. */ + if (sc->sc_msgpriq != 0) { + /* There are more outgoing messages. */ + goto nextmsg; + } + + /* + * The last message has been transmitted. We need to remember the last + * message transmitted (in case the target switches to MESSAGE IN phase + * and sends a MESSAGE REJECT), and the list of messages transmitted + * this time around (in case the target stays in MESSAGE OUT phase to + * request a retransmit). + */ +} + +/* aic6250_dataout_pio: perform a data transfer in CPU-controlled PIO mode. + * Precondition: The SCSI bus should be in the DOUT or CMDOUT phase, with REQ + * asserted and ACK deasserted (i.e. waiting for a data byte). + */ +int +aic6250_dataout_pio(struct aic6250_softc *sc, uint8_t *p, int n, int phase) +{ + uint8_t scsisig; + int out = 0; + + sc->sc_imr1 &= ~AIC_IMR1_EN_SCSI_REQ_ON_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + + /* I have tried to make the main loop as tight as possible. This + * means that some of the code following the loop is a bit more + * complex than otherwise. + */ + while (out != n) { + for (;;) { + scsisig = (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG); + if ((scsisig & AIC_SS_REQ_IN) != 0) + break; + } + if ((scsisig & PH_MASK) != phase) + break; + + (*sc->sc_write)(sc, AIC_SCSI_ID_DATA, *p++); + out++; + + aic6250_ack(sc); + } + + sc->sc_imr1 |= AIC_IMR1_EN_SCSI_REQ_ON_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + + return out; +} + +/* aic6250_datain_pio: perform data transfers using the FIFO datapath in the + * aic6250. + * Precondition: The SCSI bus should be in the DIN or STAT phase, with REQ + * asserted and ACK deasserted (i.e. at least one byte is ready). + * For now, uses a pretty dumb algorithm, hangs around until all data has been + * transferred. This, is OK for fast targets, but not so smart for slow + * targets which don't disconnect or for huge transfers. + */ +int +aic6250_datain_pio(struct aic6250_softc *sc, uint8_t *p, int n, int phase) +{ + uint8_t scsisig; + int in = 0; + + sc->sc_imr1 &= ~AIC_IMR1_EN_SCSI_REQ_ON_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + + /* We leave this loop if one or more of the following is true: + * a) phase != PH_DATAIN && FIFOs are empty + * b) SCSIRSTI is set (a reset has occurred) or busfree is detected. + */ + while (in != n) { + for (;;) { + scsisig = (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG); + if ((scsisig & AIC_SS_REQ_IN) != 0) + break; + } + if ((scsisig & PH_MASK) != phase) + break; + + *p++ = (*sc->sc_read)(sc, AIC_SCSI_ID_DATA); + in++; + + aic6250_ack(sc); + } + + sc->sc_imr1 |= AIC_IMR1_EN_SCSI_REQ_ON_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + + return in; +} + +/* + * This is the workhorse routine of the driver. + * Deficiencies (for now): + * 1) always uses programmed I/O + */ +int +aic6250_intr(void *arg) +{ + struct aic6250_softc *sc = arg; + uint8_t sr1, sr0; + struct aic6250_acb *acb; + struct scsi_link *sc_link; + struct aic6250_tinfo *ti; + int n, first = 1; + + /* Read SR1 before writing to IMR0 (which will reset some SR1 bits). */ + sr1 = (*sc->sc_read)(sc, AIC_STATUS_REG1); + (*sc->sc_write)(sc, AIC_INT_MSK_REG0, 0); + + AIC_TRACE(("aic6250_intr ")); + +loop: + sr0 = (*sc->sc_read)(sc, AIC_STATUS_REG0); + /* + * First check for abnormal conditions, such as reset. + */ + AIC_MISC(("sr0:0x%02x ", sr0)); + + if ((sr0 & AIC_SR0_SCSI_RST_OCCURED) != 0) { + printf("%s: SCSI bus reset\n", sc->sc_dev.dv_xname); + while (((*sc->sc_read)(sc, AIC_STATUS_REG1) & + AIC_SR1_SCSI_RST_IN) != 0) + delay(5); + goto reset; + } + + /* + * Check for less serious errors. + */ + if ((sr0 & AIC_SR0_SCSI_PARITY_ERR) != 0) { + printf("%s: SCSI bus parity error\n", sc->sc_dev.dv_xname); + if (sc->sc_prevphase == PH_MSGIN) { + sc->sc_flags |= AIC_DROP_MSGIN; + aic6250_sched_msgout(sc, SEND_PARITY_ERROR); + } else + aic6250_sched_msgout(sc, SEND_INIT_DET_ERR); + } + + + /* + * If we're not already busy doing something test for the following + * conditions: + * 1) We have been reselected by something + * 2) We have selected something successfully + * 3) Our selection process has timed out + * 4) This is really a bus free interrupt just to get a new command + * going? + * 5) Spurious interrupt? + */ + switch (sc->sc_state) { + case AIC_IDLE: + case AIC_SELECTING: + if (first) + first = 0; + else + sr1 = (*sc->sc_read)(sc, AIC_STATUS_REG1); + AIC_MISC(("sr1:0x%02x ", sr1)); + + if (sc->sc_state == AIC_SELECTING && + (sr1 & AIC_SR1_SEL_OUT) != 0) { + /* start selection timeout */ + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + if ((acb->xs->flags & SCSI_POLL) != 0) + sc->sc_selto = 250; /* msec */ + else + timeout_add_msec(&sc->sc_seltimeout, 250); + sc->sc_imr0 &= ~AIC_IMR_EN_SEL_OUT_INT; + goto out; + } + + if ((sr1 & AIC_SR1_RESELECTED) != 0) { + AIC_MISC(("reselected ")); + + /* kill selection timeout timer */ + sc->sc_imr0 &= + ~(AIC_IMR_EN_SEL_OUT_INT | AIC_IMR_EN_CMD_DONE_INT); + timeout_del(&sc->sc_seltimeout); + sc->sc_selto = 0; + + /* + * If we're trying to select a target ourselves, + * push our command back into the ready list. + */ + if (sc->sc_state == AIC_SELECTING) { + AIC_MISC(("backoff selector ")); + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + sc->sc_nexus = NULL; + TAILQ_INSERT_HEAD(&sc->ready_list, acb, chain); + } + + /* Save reselection ID. */ + sc->sc_selid = (*sc->sc_read)(sc, AIC_SOURCE_DEST_ID); + + sc->sc_state = AIC_RESELECTED; + } else if ((sr1 & (AIC_SR1_SELECTED | AIC_SR1_CMD_DONE)) != 0) { + AIC_MISC(("selected ")); + + /* kill selection timeout timer */ + sc->sc_imr0 &= + ~(AIC_IMR_EN_SEL_OUT_INT | AIC_IMR_EN_CMD_DONE_INT); + timeout_del(&sc->sc_seltimeout); + sc->sc_selto = 0; + + /* We have selected a target. Things to do: + * a) Determine what message(s) to send. + * b) Verify that we're still selecting the target. + * c) Mark device as busy. + */ + if (sc->sc_state != AIC_SELECTING) { + printf("%s: selection out while idle; ", + sc->sc_dev.dv_xname); + printf("resetting\n"); + AIC_BREAK(); + goto reset; + } + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + sc_link = acb->xs->sc_link; + ti = &sc->sc_tinfo[sc_link->target]; + + sc->sc_msgpriq = SEND_IDENTIFY; + if (acb->flags & ACB_RESET) + sc->sc_msgpriq |= SEND_DEV_RESET; + else if (acb->flags & ACB_ABORT) + sc->sc_msgpriq |= SEND_ABORT; + else { +#if AIC_USE_SYNCHRONOUS + if ((ti->flags & DO_SYNC) != 0) + sc->sc_msgpriq |= SEND_SDTR; +#endif +#if AIC_USE_WIDE + if ((ti->flags & DO_WIDE) != 0) + sc->sc_msgpriq |= SEND_WDTR; +#endif + } + + acb->flags |= ACB_NEXUS; + ti->lubusy |= (1 << sc_link->lun); + + /* Do an implicit RESTORE POINTERS. */ + sc->sc_dp = acb->data_addr; + sc->sc_dleft = acb->data_length; + sc->sc_cp = (uint8_t *)&acb->scsi_cmd; + sc->sc_cleft = acb->scsi_cmd_length; + + /* On our first connection, schedule a timeout. */ + if ((acb->xs->flags & SCSI_POLL) == 0) + timeout_add_msec(&acb->xs->stimeout, + acb->timeout); + + sc->sc_state = AIC_CONNECTED; + } else { + if (sc->sc_state != AIC_IDLE) { + printf("%s: BUS FREE while not idle; ", + sc->sc_dev.dv_xname); + printf("state=%d\n", sc->sc_state); + AIC_BREAK(); + goto out; + } + + goto sched; + } + + /* + * Turn off selection stuff, and prepare to catch bus free + * interrupts, parity errors, and phase changes. + */ + sc->sc_imr1 |= + AIC_IMR1_EN_SCSI_REQ_ON_INT | AIC_IMR1_EN_SCSI_RST_INT | + AIC_IMR1_EN_BUS_FREE_DETECT_INT | + AIC_IMR1_EN_SCSI_PARITY_ERR_INT | + AIC_IMR1_EN_PHASE_CHANGE_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG1, sc->sc_imr1); + + sc->sc_flags = 0; + sc->sc_prevphase = PH_INVALID; + goto dophase; + } + + if ((sr0 & AIC_SR0_BUS_FREE_DETECT) != 0) { + /* We've gone to BUS FREE phase. */ + switch (sc->sc_state) { + case AIC_RESELECTED: + goto sched; + + case AIC_CONNECTED: + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + +#if AIC_USE_SYNCHRONOUS + AIC_USE_WIDE + if (sc->sc_prevphase == PH_MSGOUT) { + /* + * If the target went to BUS FREE phase during + * or immediately after sending a SDTR or WDTR + * message, disable negotiation. + */ + sc_link = acb->xs->sc_link; + ti = &sc->sc_tinfo[sc_link->target]; + switch (sc->sc_lastmsg) { +#if AIC_USE_SYNCHRONOUS + case SEND_SDTR: + ti->flags &= ~DO_SYNC; + ti->period = ti->offset = 0; + break; +#endif +#if AIC_USE_WIDE + case SEND_WDTR: + ti->flags &= ~DO_WIDE; + ti->width = 0; + break; +#endif + } + } +#endif + + if ((sc->sc_flags & AIC_ABORTING) == 0) { + /* + * Section 5.1.1 of the SCSI 2 spec suggests + * issuing a REQUEST SENSE following an + * unexpected disconnect. Some devices go into + * a contingent allegiance condition when + * disconnecting, and this is necessary to + * clean up their state. + */ + printf("%s: unexpected disconnect; ", + sc->sc_dev.dv_xname); + printf("sending REQUEST SENSE\n"); + AIC_BREAK(); + aic6250_sense(sc, acb); + goto out; + } + + acb->xs->error = XS_DRIVER_STUFFUP; + goto finish; + + case AIC_DISCONNECT: + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; +#if 1 /* XXX */ + acb->data_addr = sc->sc_dp; + acb->data_length = sc->sc_dleft; +#endif + TAILQ_INSERT_HEAD(&sc->nexus_list, acb, chain); + sc->sc_nexus = NULL; + goto sched; + + case AIC_CMDCOMPLETE: + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + goto finish; + } + } + +dophase: + if ((sr0 & AIC_SR0_SCSI_REQ_ON) == 0) { + /* Wait for AIC_SR0_SCSI_REQ_ON. */ + goto out; + } + + sc->sc_phase = (*sc->sc_read)(sc, AIC_SCSI_SIGNAL_REG) & PH_MASK; + (*sc->sc_write)(sc, AIC_SCSI_SIGNAL_REG, sc->sc_phase); + + switch (sc->sc_phase) { + case PH_MSGOUT: + if (sc->sc_state != AIC_CONNECTED && + sc->sc_state != AIC_RESELECTED) + break; + aic6250_msgout(sc); + sc->sc_prevphase = PH_MSGOUT; + goto loop; + + case PH_MSGIN: + if (sc->sc_state != AIC_CONNECTED && + sc->sc_state != AIC_RESELECTED) + break; + aic6250_msgin(sc); + sc->sc_prevphase = PH_MSGIN; + goto loop; + + case PH_CMD: + if (sc->sc_state != AIC_CONNECTED) + break; +#ifdef AIC_DEBUG + if ((aic6250_debug & AIC_SHOWMISC) != 0) { + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + printf("cmd=0x%02x+%d ", + acb->scsi_cmd.opcode, acb->scsi_cmd_length-1); + } +#endif + n = aic6250_dataout_pio(sc, sc->sc_cp, sc->sc_cleft, PH_CMD); + sc->sc_cp += n; + sc->sc_cleft -= n; + sc->sc_prevphase = PH_CMD; + goto loop; + + case PH_DATAOUT: + if (sc->sc_state != AIC_CONNECTED) + break; + AIC_MISC(("dataout dleft=%lu ", (u_long)sc->sc_dleft)); + n = aic6250_dataout_pio(sc, sc->sc_dp, sc->sc_dleft, PH_DATAOUT); + sc->sc_dp += n; + sc->sc_dleft -= n; + sc->sc_prevphase = PH_DATAOUT; + goto loop; + + case PH_DATAIN: + if (sc->sc_state != AIC_CONNECTED) + break; + AIC_MISC(("datain %lu ", (u_long)sc->sc_dleft)); + n = aic6250_datain_pio(sc, sc->sc_dp, sc->sc_dleft, PH_DATAIN); + sc->sc_dp += n; + sc->sc_dleft -= n; + sc->sc_prevphase = PH_DATAIN; + goto loop; + + case PH_STAT: + if (sc->sc_state != AIC_CONNECTED) + break; + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + aic6250_datain_pio(sc, &acb->target_stat, 1, PH_STAT); + AIC_MISC(("target_stat=0x%02x ", acb->target_stat)); + sc->sc_prevphase = PH_STAT; + goto loop; + } + + printf("%s: unexpected bus phase; resetting\n", sc->sc_dev.dv_xname); + AIC_BREAK(); +reset: + aic6250_init(sc); + return 1; + +finish: + timeout_del(&acb->xs->stimeout); + aic6250_done(sc, acb); + goto out; + +sched: + sc->sc_state = AIC_IDLE; + aic6250_sched(sc); + goto out; + +out: + sc->sc_imr0 |= AIC_IMR_EN_ERROR_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG0, sc->sc_imr0); + return 1; +} + +void +aic6250_seltimeout(void *arg) +{ + struct aic6250_softc *sc = arg; + struct aic6250_acb *acb; + + AIC_MISC(("selection timeout ")); + + if (sc->sc_state != AIC_SELECTING) { + printf("%s: selection timeout while idle; ", + sc->sc_dev.dv_xname); + printf("resetting\n"); + AIC_BREAK(); + aic6250_init(sc); + return; + } + + AIC_ASSERT(sc->sc_nexus != NULL); + acb = sc->sc_nexus; + + (*sc->sc_write)(sc, AIC_SCSI_ID_DATA, 0); + delay(200); + + acb->xs->error = XS_SELTIMEOUT; + timeout_del(&acb->xs->stimeout); + aic6250_done(sc, acb); + + sc->sc_imr0 |= AIC_IMR_EN_ERROR_INT; + (*sc->sc_write)(sc, AIC_INT_MSK_REG0, sc->sc_imr0); +} + +void +aic6250_abort(struct aic6250_softc *sc, struct aic6250_acb *acb) +{ + + /* 2 secs for the abort */ + acb->timeout = AIC_ABORT_TIMEOUT; + acb->flags |= ACB_ABORT; + + if (acb == sc->sc_nexus) { + /* + * If we're still selecting, the message will be scheduled + * after selection is complete. + */ + if (sc->sc_state == AIC_CONNECTED) + aic6250_sched_msgout(sc, SEND_ABORT); + } else { + aic6250_dequeue(sc, acb); + TAILQ_INSERT_HEAD(&sc->ready_list, acb, chain); + if (sc->sc_state == AIC_IDLE) + aic6250_sched(sc); + } +} + +void +aic6250_timeout(void *arg) +{ + struct aic6250_acb *acb = arg; + struct scsi_xfer *xs = acb->xs; + struct scsi_link *sc_link = xs->sc_link; + struct aic6250_softc *sc = sc_link->adapter_softc; + int s; + + sc_print_addr(sc_link); + printf("timed out"); + + s = splbio(); + + if (acb->flags & ACB_ABORT) { + /* abort timed out */ + printf(" AGAIN\n"); + /* XXX Must reset! */ + } else { + /* abort the operation that has timed out */ + printf("\n"); + acb->xs->error = XS_TIMEOUT; + aic6250_abort(sc, acb); + } + + splx(s); +} + +#ifdef AIC_DEBUG +/* + * The following functions are mostly used for debugging purposes, either + * directly called from the driver or from the kernel debugger. + */ + +void +aic6250_show_scsi_cmd(struct aic6250_acb *acb) +{ + uint8_t *b = (uint8_t *)&acb->scsi_cmd; + struct scsi_link *sc_link = acb->xs->sc_link; + int i; + + sc_print_addr(sc_link); + if ((acb->xs->flags & SCSI_RESET) == 0) { + for (i = 0; i < acb->scsi_cmd_length; i++) { + if (i) + printf(","); + printf("%x", b[i]); + } + printf("\n"); + } else + printf("RESET\n"); +} + +void +aic6250_print_acb(struct aic6250_acb *acb) +{ + + printf("acb@%p xs=%p flags=%x", acb, acb->xs, acb->flags); + printf(" dp=%p dleft=%d target_stat=%x\n", + acb->data_addr, acb->data_length, acb->target_stat); + aic6250_show_scsi_cmd(acb); +} + +void +aic6250_print_active_acb(void) +{ + struct aic6250_acb *acb; + struct aic6250_softc *sc = oaic_cd.cd_devs[0]; + + printf("ready list:\n"); + TAILQ_FOREACH(acb, &sc->ready_list, chain) + aic6250_print_acb(acb); + printf("nexus:\n"); + if (sc->sc_nexus != NULL) + aic6250_print_acb(sc->sc_nexus); + printf("nexus list:\n"); + TAILQ_FOREACH(acb, &sc->nexus_list, chain) + aic6250_print_acb(acb); +} + +void +aic6250_dump_driver(struct aic6250_softc *sc) +{ + struct aic6250_tinfo *ti; + int i; + + printf("nexus=%p prevphase=%x\n", sc->sc_nexus, sc->sc_prevphase); + printf("state=%x msgin=%x ", sc->sc_state, sc->sc_imess[0]); + printf("msgpriq=%x msgoutq=%x lastmsg=%x currmsg=%x\n", sc->sc_msgpriq, + sc->sc_msgoutq, sc->sc_lastmsg, sc->sc_currmsg); + for (i = 0; i < 7; i++) { + ti = &sc->sc_tinfo[i]; + printf("tinfo%d: %d cmds %d disconnects %d timeouts", + i, ti->cmds, ti->dconns, ti->touts); + printf(" %d senses flags=%x\n", ti->senses, ti->flags); + } +} +#endif diff --git a/sys/dev/ic/aic6250reg.h b/sys/dev/ic/aic6250reg.h new file mode 100644 index 00000000000..f87c7a7056a --- /dev/null +++ b/sys/dev/ic/aic6250reg.h @@ -0,0 +1,150 @@ +/* $OpenBSD: aic6250reg.h,v 1.1 2013/10/15 01:41:46 miod Exp $ */ + +/* + * Copyright (c) 2010, 2013 Miodrag Vallat. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Register definitions for the AIC 6250 SCSI controller + */ + +#define AIC_DMA_BYTE_COUNT_L 0x00 +#define AIC_DMA_BYTE_COUNT_M 0x01 +#define AIC_DMA_BYTE_COUNT_H 0x02 +#define AIC_INT_MSK_REG0 0x03 /* w-only */ +#define AIC_OFFSET_CNTRL 0x04 /* w-only */ +#define AIC_FIFO_STATUS 0x05 /* r-only */ +#define AIC_DMA_CNTRL 0x05 /* w-only */ +#define AIC_REV_CNTRL 0x06 /* r-only */ +#define AIC_INT_MSK_REG1 0x06 /* w-only */ +#define AIC_STATUS_REG0 0x07 /* r-only */ +#define AIC_CONTROL_REG0 0x07 /* w-only */ +#define AIC_STATUS_REG1 0x08 /* r-only */ +#define AIC_CONTROL_REG1 0x08 /* w-only */ +#define AIC_SCSI_SIGNAL_REG 0x09 +#define AIC_SCSI_ID_DATA 0x0a +#define AIC_SOURCE_DEST_ID 0x0b /* r-only */ +#define AIC_MEMORY_DATA 0x0c +#define AIC_PORT_A 0x0d +#define AIC_PORT_B 0x0e +#define AIC_SCSI_LATCH_DATA 0x0f /* r-only */ +#define AIC_SCSI_BSY_RST 0x0f /* w-only */ + +#define AIC_NREG 0x10 + +/* INT MSK REG0 */ +#define AIC_IMR_ARB_SEL_START 0x40 +#define AIC_IMR_EN_AUTO_ATN 0x20 +#define AIC_IMR_EN_ERROR_INT 0x10 +#define AIC_IMR_EN_CMD_DONE_INT 0x08 +#define AIC_IMR_EN_SEL_OUT_INT 0x04 +#define AIC_IMR_EN_RESEL_INT 0x02 +#define AIC_IMR_EN_SELECT_INT 0x01 + +/* OFFSET CNTRL */ +#define AIC_OC_SYNC_XFER_MASK 0x70 +#define AIC_OC_SYNC_XFER_SHIFT 4 +#define AIC_OC_OFFSET_MASK 0x0f +#define AIC_OC_OFFSET_SHIFT 0 + +/* FIFO STATUS */ +#define AIC_FS_OFFSET_COUNT_ZERO 0x20 +#define AIC_FS_FIFO_EMPTY 0x10 +#define AIC_FS_FIFO_FULL 0x08 +#define AIC_FS_FIFO_COUNTER_MASK 0x07 +#define AIC_FS_FIFO_COUNTER_SHIFT 0 + +/* DMA CNTRL */ +#define AIC_DC_ODD_XFER_START 0x04 +#define AIC_DC_TRANSFER_DIR 0x02 +#define AIC_DC_DMA_XFER_EN 0x01 + +/* REV CNTRL */ +#define AIC_RC_MASK 0x03 + +/* INT MSK REG1 */ +#define AIC_IMR1_EN_SCSI_REQ_ON_INT 0x40 +#define AIC_IMR1_EN_SCSI_RST_INT 0x20 +#define AIC_IMR1_EN_MEM_PARITY_ERR_INT 0x10 +#define AIC_IMR1_EN_PHASE_MISMATCH_INT 0x08 +#define AIC_IMR1_EN_BUS_FREE_DETECT_INT 0x04 +#define AIC_IMR1_EN_SCSI_PARITY_ERR_INT 0x02 +#define AIC_IMR1_EN_PHASE_CHANGE_INT 0x01 /* initiator */ +#define AIC_IMR1_EN_ATTN_ON_INT 0x01 /* target */ + +/* STATUS REG0 */ +#define AIC_SR0_SCSI_RST_OCCURED 0x80 +#define AIC_SR0_MEMORY_PARITY_ERR 0x40 +#define AIC_SR0_PHASE_MISMATCH_ERR 0x20 +#define AIC_SR0_BUS_FREE_DETECT 0x10 +#define AIC_SR0_SCSI_PARITY_ERR 0x08 +#define AIC_SR0_SCSI_REQ_ON 0x04 +#define AIC_SR0_SCSI_PHASE_CHG_ATTN 0x02 +#define AIC_SR0_DMA_BYTE_CNT_ZERO 0x01 + +/* CONTROL REG0 */ +#define AIC_CR0_P_MEM_CYCLE_REQ 0x80 +#define AIC_CR0_P_MEM_RW 0x40 +#define AIC_CR0_TARGET_MODE 0x20 +#define AIC_CR0_EN_PORT_A 0x10 +#define AIC_CR0_SCSI_INTERFACE_MODE 0x08 +#define AIC_CR0_SCSI_ID_MASK 0x07 + +/* STATUS REG1 */ +#define AIC_SR1_MEM_CYCLE_COMPLETE 0x80 +#define AIC_SR1_SCSI_RST_IN 0x20 +#define AIC_SR1_ERROR 0x10 +#define AIC_SR1_CMD_DONE 0x08 +#define AIC_SR1_SEL_OUT 0x04 +#define AIC_SR1_RESELECTED 0x02 +#define AIC_SR1_SELECTED 0x01 + +/* CONTROL REG1 */ +#define AIC_CR1_AUTO_SCSI_PIO_REQ 0x80 +#define AIC_CR1_ENABLE_16BIT_MEM_BUS 0x40 +#define AIC_CR1_EN_PORT_B 0x10 +#define AIC_CR1_PHASE_CHANGE_MODE 0x08 +#define AIC_CR1_CLK_FREQ_MODE 0x04 +#define AIC_CR1_SCSI_RST_OUT 0x02 +#define AIC_CR1_CHIP_SW_RESET 0x01 + +/* SCSI SIGNAL REG (read path) */ +#define AIC_SS_CD_IN 0x80 +#define AIC_SS_IO_IN 0x40 +#define AIC_SS_MSG_IN 0x20 +#define AIC_SS_ATN_IN 0x10 +#define AIC_SS_SEL_IN 0x08 +#define AIC_SS_BSY_IN 0x04 +#define AIC_SS_REQ_IN 0x02 +#define AIC_SS_ACK_IN 0x01 + +/* SCSI SIGNAL REG (write path) */ +#define AIC_SS_CD_OUT 0x80 +#define AIC_SS_IO_OUT 0x40 +#define AIC_SS_MSG_OUT 0x20 +#define AIC_SS_ATN_OUT 0x10 +#define AIC_SS_SEL_OUT 0x08 +#define AIC_SS_BSY_OUT 0x04 +#define AIC_SS_ACK_OUT 0x02 /* initiator */ +#define AIC_SS_REQ_OUT 0x02 /* target */ + +#define PH_DATAOUT 0x00 +#define PH_DATAIN AIC_SS_IO_IN +#define PH_CMD AIC_SS_CD_IN +#define PH_STAT (AIC_SS_CD_IN | AIC_SS_IO_IN) +#define PH_MSGOUT (AIC_SS_CD_OUT | AIC_SS_MSG_OUT) +#define PH_MSGIN (AIC_SS_CD_IN | AIC_SS_IO_IN | AIC_SS_MSG_IN) +#define PH_MASK (AIC_SS_CD_IN | AIC_SS_IO_IN | AIC_SS_MSG_IN) +#define PH_INVALID 0xff diff --git a/sys/dev/ic/aic6250var.h b/sys/dev/ic/aic6250var.h new file mode 100644 index 00000000000..713e165ce0f --- /dev/null +++ b/sys/dev/ic/aic6250var.h @@ -0,0 +1,228 @@ +/* $OpenBSD: aic6250var.h,v 1.1 2013/10/15 01:41:46 miod Exp $ */ + +/* + * Copyright (c) 2010, 2013 Miodrag Vallat. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Derived from sys/dev/ic/aic6360var.h under the following licence terms: + */ +/* OpenBSD: aic6360var.h,v 1.8 2011/04/06 18:14:35 miod Exp */ +/* $NetBSD: aic6360.c,v 1.52 1996/12/10 21:27:51 thorpej Exp $ */ + +/* + * Copyright (c) 1994, 1995, 1996 Charles Hannum. 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 Charles M. Hannum. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Copyright (c) 1994 Jarle Greipsland + * 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. 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. + */ + +/* + * Acknowledgements: Many of the algorithms used in this driver are + * inspired by the work of Julian Elischer (julian@tfs.com) and + * Charles Hannum (mycroft@duality.gnu.ai.mit.edu). Thanks a million! + */ + +/* + * ACB. Holds additional information for each SCSI command Comments: We + * need a separate scsi command block because we may need to overwrite it + * with a request sense command. Basicly, we refrain from fiddling with + * the scsi_xfer struct (except do the expected updating of return values). + * We'll generally update: xs->{flags,resid,error,sense,status} and + * occasionally xs->retries. + */ +struct aic6250_acb { + struct scsi_generic scsi_cmd; + int scsi_cmd_length; + void *data_addr; /* Saved data pointer */ + int data_length; /* Residue */ + + uint8_t target_stat; /* SCSI status byte */ + + TAILQ_ENTRY(aic6250_acb) chain; + struct scsi_xfer *xs; /* SCSI xfer ctrl block from above */ + int flags; +#define ACB_ALLOC 0x01 +#define ACB_NEXUS 0x02 +#define ACB_SENSE 0x04 +#define ACB_ABORT 0x40 +#define ACB_RESET 0x80 + int timeout; +}; + +/* + * Some info about each (possible) target on the SCSI bus. This should + * probably have been a "per target+lunit" structure, but we'll leave it at + * this for now. + */ +struct aic6250_tinfo { + int cmds; /* #commands processed */ + int dconns; /* #disconnects */ + int touts; /* #timeouts */ + int perrs; /* #parity errors */ + int senses; /* #request sense commands sent */ + ushort lubusy; /* What local units/subr. are busy? */ + uint8_t flags; +#define DO_SYNC 0x01 /* (Re)Negotiate synchronous options */ +#define DO_WIDE 0x02 /* (Re)Negotiate wide options */ + uint8_t period; /* Period suggestion */ + uint8_t offset; /* Offset suggestion */ + uint8_t width; /* Width suggestion */ +}; + +struct aic6250_softc { + struct device sc_dev; + + uint8_t (*sc_read)(struct aic6250_softc *, uint); + void (*sc_write)(struct aic6250_softc *, uint, uint8_t); + + struct scsi_link sc_link; /* prototype for subdevs */ + + TAILQ_HEAD(, aic6250_acb) free_list, ready_list, nexus_list; + struct aic6250_acb *sc_nexus; /* current command */ + struct aic6250_acb sc_acb[8]; + struct aic6250_tinfo sc_tinfo[8]; + + struct mutex sc_acb_mtx; + struct scsi_iopool sc_iopool; + + /* Data about the current nexus (updated for every cmd switch) */ + u_char *sc_dp; /* Current data pointer */ + size_t sc_dleft; /* Data bytes left to transfer */ + u_char *sc_cp; /* Current command pointer */ + size_t sc_cleft; /* Command bytes left to transfer */ + + /* Adapter state */ + uint8_t sc_phase; /* Current bus phase */ + uint8_t sc_prevphase; /* Previous bus phase */ + uint8_t sc_state; /* State applicable to the adapter */ +#define AIC_INIT 0 +#define AIC_IDLE 1 +#define AIC_SELECTING 2 /* SCSI command is arbiting */ +#define AIC_RESELECTED 3 /* Has been reselected */ +#define AIC_CONNECTED 4 /* Actively using the SCSI bus */ +#define AIC_DISCONNECT 5 /* MSG_DISCONNECT received */ +#define AIC_CMDCOMPLETE 6 /* MSG_CMDCOMPLETE received */ +#define AIC_CLEANING 7 + uint8_t sc_flags; +#define AIC_DROP_MSGIN 0x01 /* Discard all msgs (parity err detected) */ +#define AIC_ABORTING 0x02 /* Bailing out */ +#define AIC_DOINGDMA 0x04 /* The FIFO data path is active! */ + uint8_t sc_selid; /* Reselection ID */ + + uint8_t sc_imr0; + uint8_t sc_imr1; + uint8_t sc_cr0; + uint8_t sc_cr1; + struct timeout sc_seltimeout; + uint sc_selto; /* Selection timeout (when polling) */ + + /* Message stuff */ + uint8_t sc_msgpriq; /* Messages we want to send */ + uint8_t sc_msgoutq; /* Messages sent during last MESSAGE OUT */ + uint8_t sc_lastmsg; /* Message last transmitted */ + uint8_t sc_currmsg; /* Message currently ready to transmit */ +#define SEND_DEV_RESET 0x01 +#define SEND_PARITY_ERROR 0x02 +#define SEND_INIT_DET_ERR 0x04 +#define SEND_REJECT 0x08 +#define SEND_IDENTIFY 0x10 +#define SEND_ABORT 0x20 +#define SEND_SDTR 0x40 +#define SEND_WDTR 0x80 +#define AIC_MAX_MSG_LEN 8 + uint8_t sc_omess[AIC_MAX_MSG_LEN]; + uint8_t *sc_omp; /* Outgoing message pointer */ + uint8_t sc_imess[AIC_MAX_MSG_LEN]; + uint8_t *sc_imp; /* Incoming message pointer */ + + /* Hardware stuff */ + int sc_initiator; /* Our scsi id */ + int sc_freq; /* Clock frequency in MHz */ + int sc_minsync; /* Minimum sync period / 4 */ + int sc_maxsync; /* Maximum sync period / 4 */ +}; + +#if AIC_DEBUG +#define AIC_SHOWACBS 0x01 +#define AIC_SHOWINTS 0x02 +#define AIC_SHOWCMDS 0x04 +#define AIC_SHOWMISC 0x08 +#define AIC_SHOWTRACE 0x10 +#define AIC_SHOWSTART 0x20 +#define AIC_DOBREAK 0x40 +#define AIC_PRINT(b, s) do {if ((aic6250_debug & (b)) != 0) printf s;} while (0) +#define AIC_BREAK() \ + do { if ((aic6250_debug & AIC_DOBREAK) != 0) Debugger(); } while (0) +#define AIC_ASSERT(x) \ + do { \ + if (!(x)) { \ + printf("%s at line %d: assertion failed\n", \ + sc->sc_dev.dv_xname, __LINE__); \ + Debugger(); \ + } \ + } while (0) +#else +#define AIC_PRINT(b, s) +#define AIC_BREAK() +#define AIC_ASSERT(x) +#endif + +#define AIC_ACBS(s) AIC_PRINT(AIC_SHOWACBS, s) +#define AIC_INTS(s) AIC_PRINT(AIC_SHOWINTS, s) +#define AIC_CMDS(s) AIC_PRINT(AIC_SHOWCMDS, s) +#define AIC_MISC(s) AIC_PRINT(AIC_SHOWMISC, s) +#define AIC_TRACE(s) AIC_PRINT(AIC_SHOWTRACE, s) +#define AIC_START(s) AIC_PRINT(AIC_SHOWSTART, s) + +void aic6250_attach(struct aic6250_softc *); +int aic6250_intr(void *); |