diff options
Diffstat (limited to 'sys/dev/i2o/iop.c')
-rw-r--r-- | sys/dev/i2o/iop.c | 2471 |
1 files changed, 2471 insertions, 0 deletions
diff --git a/sys/dev/i2o/iop.c b/sys/dev/i2o/iop.c new file mode 100644 index 00000000000..462d58870c7 --- /dev/null +++ b/sys/dev/i2o/iop.c @@ -0,0 +1,2471 @@ +/* $OpenBSD: iop.c,v 1.1 2001/06/25 23:04:29 niklas Exp $ */ +/* $NetBSD: iop.c,v 1.12 2001/03/21 14:27:05 ad Exp $ */ + +/*- + * Copyright (c) 2000, 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Andrew Doran. + * + * 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 the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +/* + * Support for I2O IOPs (intelligent I/O processors). + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/queue.h> +#include <sys/proc.h> +#include <sys/malloc.h> +#include <sys/ioctl.h> +#include <sys/endian.h> +#include <sys/conf.h> +#include <sys/kthread.h> + +#include <vm/vm.h> +#include <uvm/uvm_extern.h> + +#include <machine/bus.h> + +#include <dev/i2o/i2o.h> +#include <dev/i2o/iopio.h> +#include <dev/i2o/iopreg.h> +#include <dev/i2o/iopvar.h> + +#define POLL(ms, cond) \ +do { \ + int i; \ + for (i = (ms) * 10; i; i--) { \ + if (cond) \ + break; \ + DELAY(100); \ + } \ +} while (/* CONSTCOND */0); + +#ifdef I2ODEBUG +#define DPRINTF(x) printf x +#else +#define DPRINTF(x) +#endif + +#ifdef I2OVERBOSE +#define IFVERBOSE(x) x +#define COMMENT(x) NULL +#else +#define IFVERBOSE(x) +#define COMMENT(x) +#endif + +#define IOP_ICTXHASH_NBUCKETS 16 +#define IOP_ICTXHASH(ictx) (&iop_ictxhashtbl[(ictx) & iop_ictxhash]) + +#define IOP_MAX_SEGS (((IOP_MAX_XFER + PAGE_SIZE - 1) / PAGE_SIZE) + 1) + +#define IOP_TCTX_SHIFT 12 +#define IOP_TCTX_MASK ((1 << IOP_TCTX_SHIFT) - 1) + +LIST_HEAD(, iop_initiator) *iop_ictxhashtbl; +u_long iop_ictxhash; +void *iop_sdh; +struct i2o_systab *iop_systab; +int iop_systab_size; + +struct cfdriver iop_cd = { + NULL, "iop", DV_DULL +}; + +#define IC_CONFIGURE 0x01 +#define IC_PRIORITY 0x02 + +struct iop_class { + u_short ic_class; + u_short ic_flags; +#ifdef I2OVERBOSE + const char *ic_caption; +#endif +} static const iop_class[] = { + { + I2O_CLASS_EXECUTIVE, + 0, + COMMENT("executive") + }, + { + I2O_CLASS_DDM, + 0, + COMMENT("device driver module") + }, + { + I2O_CLASS_RANDOM_BLOCK_STORAGE, + IC_CONFIGURE | IC_PRIORITY, + IFVERBOSE("random block storage") + }, + { + I2O_CLASS_SEQUENTIAL_STORAGE, + IC_CONFIGURE | IC_PRIORITY, + IFVERBOSE("sequential storage") + }, + { + I2O_CLASS_LAN, + IC_CONFIGURE | IC_PRIORITY, + IFVERBOSE("LAN port") + }, + { + I2O_CLASS_WAN, + IC_CONFIGURE | IC_PRIORITY, + IFVERBOSE("WAN port") + }, + { + I2O_CLASS_FIBRE_CHANNEL_PORT, + IC_CONFIGURE, + IFVERBOSE("fibrechannel port") + }, + { + I2O_CLASS_FIBRE_CHANNEL_PERIPHERAL, + 0, + COMMENT("fibrechannel peripheral") + }, + { + I2O_CLASS_SCSI_PERIPHERAL, + 0, + COMMENT("SCSI peripheral") + }, + { + I2O_CLASS_ATE_PORT, + IC_CONFIGURE, + IFVERBOSE("ATE port") + }, + { + I2O_CLASS_ATE_PERIPHERAL, + 0, + COMMENT("ATE peripheral") + }, + { + I2O_CLASS_FLOPPY_CONTROLLER, + IC_CONFIGURE, + IFVERBOSE("floppy controller") + }, + { + I2O_CLASS_FLOPPY_DEVICE, + 0, + COMMENT("floppy device") + }, + { + I2O_CLASS_BUS_ADAPTER_PORT, + IC_CONFIGURE, + IFVERBOSE("bus adapter port" ) + }, +}; + +#if defined(I2ODEBUG) && defined(I2OVERBOSE) +static const char * const iop_status[] = { + "success", + "abort (dirty)", + "abort (no data transfer)", + "abort (partial transfer)", + "error (dirty)", + "error (no data transfer)", + "error (partial transfer)", + "undefined error code", + "process abort (dirty)", + "process abort (no data transfer)", + "process abort (partial transfer)", + "transaction error", +}; +#endif + +static inline u_int32_t iop_inl(struct iop_softc *, int); +static inline void iop_outl(struct iop_softc *, int, u_int32_t); + +void iop_config_interrupts(struct device *); +void iop_configure_devices(struct iop_softc *, int, int); +void iop_devinfo(int, char *); +int iop_print(void *, const char *); +int iop_reconfigure(struct iop_softc *, u_int); +void iop_shutdown(void *); +int iop_submatch(struct device *, void *, void *); +#ifdef notyet +int iop_vendor_print(void *, const char *); +#endif + +void iop_adjqparam(struct iop_softc *, int); +void iop_create_reconf_thread(void *); +int iop_handle_reply(struct iop_softc *, u_int32_t); +int iop_hrt_get(struct iop_softc *); +int iop_hrt_get0(struct iop_softc *, struct i2o_hrt *, int); +void iop_intr_event(struct device *, struct iop_msg *, void *); +int iop_lct_get0(struct iop_softc *, struct i2o_lct *, int, + u_int32_t); +void iop_msg_poll(struct iop_softc *, struct iop_msg *, int); +void iop_msg_wait(struct iop_softc *, struct iop_msg *, int); +int iop_ofifo_init(struct iop_softc *); +int iop_passthrough(struct iop_softc *, struct ioppt *); +int iop_post(struct iop_softc *, u_int32_t *); +void iop_reconf_thread(void *); +void iop_release_mfa(struct iop_softc *, u_int32_t); +int iop_reset(struct iop_softc *); +int iop_status_get(struct iop_softc *, int); +int iop_systab_set(struct iop_softc *); +void iop_tfn_print(struct iop_softc *, struct i2o_fault_notify *); + +#ifdef I2ODEBUG +void iop_reply_print(struct iop_softc *, struct i2o_reply *); +#endif + +cdev_decl(iop); + +static inline u_int32_t +iop_inl(struct iop_softc *sc, int off) +{ + + bus_space_barrier(sc->sc_iot, sc->sc_ioh, off, 4, + BUS_SPACE_BARRIER_WRITE | BUS_SPACE_BARRIER_READ); + return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, off)); +} + +static inline void +iop_outl(struct iop_softc *sc, int off, u_int32_t val) +{ + + bus_space_write_4(sc->sc_iot, sc->sc_ioh, off, val); + bus_space_barrier(sc->sc_iot, sc->sc_ioh, off, 4, + BUS_SPACE_BARRIER_WRITE); +} + +/* + * Initialise the IOP and our interface. + */ +void +iop_init(struct iop_softc *sc, const char *intrstr) +{ + struct iop_msg *im; + int rv, i; + u_int32_t mask; + char ident[64]; + + if (iop_ictxhashtbl == NULL) + iop_ictxhashtbl = hashinit(IOP_ICTXHASH_NBUCKETS, + M_DEVBUF, M_NOWAIT, &iop_ictxhash); + + /* Reset the IOP and request status. */ + printf("I2O adapter"); + + if ((rv = iop_reset(sc)) != 0) { + printf("%s: not responding (reset)\n", sc->sc_dv.dv_xname); + return; + } + if ((rv = iop_status_get(sc, 1)) != 0) { + printf("%s: not responding (get status)\n", sc->sc_dv.dv_xname); + return; + } + sc->sc_flags |= IOP_HAVESTATUS; + iop_strvis(sc, sc->sc_status.productid, sizeof(sc->sc_status.productid), + ident, sizeof(ident)); + printf(" <%s>\n", ident); + +#ifdef I2ODEBUG + printf("%s: orgid=0x%04x version=%d\n", sc->sc_dv.dv_xname, + letoh16(sc->sc_status.orgid), + (letoh32(sc->sc_status.segnumber) >> 12) & 15); + printf("%s: type want have cbase\n", sc->sc_dv.dv_xname); + printf("%s: mem %04x %04x %08x\n", sc->sc_dv.dv_xname, + letoh32(sc->sc_status.desiredprivmemsize), + letoh32(sc->sc_status.currentprivmemsize), + letoh32(sc->sc_status.currentprivmembase)); + printf("%s: i/o %04x %04x %08x\n", sc->sc_dv.dv_xname, + letoh32(sc->sc_status.desiredpriviosize), + letoh32(sc->sc_status.currentpriviosize), + letoh32(sc->sc_status.currentpriviobase)); +#endif + + sc->sc_maxob = letoh32(sc->sc_status.maxoutboundmframes); + if (sc->sc_maxob > IOP_MAX_OUTBOUND) + sc->sc_maxob = IOP_MAX_OUTBOUND; + sc->sc_maxib = letoh32(sc->sc_status.maxinboundmframes); + if (sc->sc_maxib > IOP_MAX_INBOUND) + sc->sc_maxib = IOP_MAX_INBOUND; + + /* Allocate message wrappers. */ + im = malloc(sizeof(*im) * sc->sc_maxib, M_DEVBUF, M_NOWAIT); + if (!im) + return; + bzero(im, sizeof(*im) * sc->sc_maxib); + sc->sc_ims = im; + SLIST_INIT(&sc->sc_im_freelist); + + for (i = 0; i < sc->sc_maxib; i++, im++) { + rv = bus_dmamap_create(sc->sc_dmat, IOP_MAX_XFER, + IOP_MAX_SEGS, IOP_MAX_XFER, 0, + BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, + &im->im_xfer[0].ix_map); + if (rv != 0) { + printf("%s: couldn't create dmamap (%d)", + sc->sc_dv.dv_xname, rv); + return; + } + + im->im_tctx = i; + SLIST_INSERT_HEAD(&sc->sc_im_freelist, im, im_chain); + } + + /* Initalise the IOP's outbound FIFO. */ + if (iop_ofifo_init(sc) != 0) { + printf("%s: unable to init oubound FIFO\n", sc->sc_dv.dv_xname); + return; + } + + /* Configure shutdown hook before we start any device activity. */ + if (iop_sdh == NULL) + iop_sdh = shutdownhook_establish(iop_shutdown, NULL); + + /* Ensure interrupts are enabled at the IOP. */ + mask = iop_inl(sc, IOP_REG_INTR_MASK); + iop_outl(sc, IOP_REG_INTR_MASK, mask & ~IOP_INTR_OFIFO); + + if (intrstr != NULL) + printf("%s: interrupting at %s\n", sc->sc_dv.dv_xname, + intrstr); + +#ifdef I2ODEBUG + printf("%s: queue depths: inbound %d/%d, outbound %d/%d\n", + sc->sc_dv.dv_xname, sc->sc_maxib, + letoh32(sc->sc_status.maxinboundmframes), + sc->sc_maxob, letoh32(sc->sc_status.maxoutboundmframes)); +#endif + + lockinit(&sc->sc_conflock, PRIBIO, "iopconf", hz * 30, 0); + + kthread_create_deferred(iop_create_reconf_thread, sc); +} + +/* + * Perform autoconfiguration tasks. + */ +void +iop_config_interrupts(struct device *self) +{ + struct iop_softc *sc, *iop; + struct i2o_systab_entry *ste; + int rv, i, niop; + + sc = (struct iop_softc *)self; + LIST_INIT(&sc->sc_iilist); + + printf("%s: configuring...\n", sc->sc_dv.dv_xname); + + if (iop_hrt_get(sc) != 0) { + printf("%s: unable to retrieve HRT\n", sc->sc_dv.dv_xname); + return; + } + + /* + * Build the system table. + */ + if (iop_systab == NULL) { + for (i = 0, niop = 0; i < iop_cd.cd_ndevs; i++) { + if (!(iop = (struct iop_softc *)device_lookup(&iop_cd, i))) + continue; + if ((iop->sc_flags & IOP_HAVESTATUS) == 0) + continue; + if (iop_status_get(iop, 1) != 0) { + printf("%s: unable to retrieve status\n", + sc->sc_dv.dv_xname); + iop->sc_flags &= ~IOP_HAVESTATUS; + continue; + } + niop++; + } + if (niop == 0) + return; + + i = sizeof(struct i2o_systab_entry) * (niop - 1) + + sizeof(struct i2o_systab); + iop_systab_size = i; + iop_systab = malloc(i, M_DEVBUF, M_NOWAIT); + if (!iop_systab) + return; + + bzero(iop_systab, i); + iop_systab->numentries = niop; + iop_systab->version = I2O_VERSION_11; + + for (i = 0, ste = iop_systab->entry; i < iop_cd.cd_ndevs; i++) { + iop = (struct iop_softc *)device_lookup(&iop_cd, i); + if (iop == NULL) + continue; + if ((iop->sc_flags & IOP_HAVESTATUS) == 0) + continue; + + ste->orgid = iop->sc_status.orgid; + ste->iopid = iop->sc_dv.dv_unit + 2; + ste->segnumber = + htole32(letoh32(iop->sc_status.segnumber) & ~4095); + ste->iopcaps = iop->sc_status.iopcaps; + ste->inboundmsgframesize = + iop->sc_status.inboundmframesize; + ste->inboundmsgportaddresslow = + htole32(iop->sc_memaddr + IOP_REG_IFIFO); + ste++; + } + } + + /* + * Post the system table to the IOP and bring it to the OPERATIONAL + * state. + */ + if (iop_systab_set(sc) != 0) { + printf("%s: unable to set system table\n", sc->sc_dv.dv_xname); + return; + } + if (iop_simple_cmd(sc, I2O_TID_IOP, I2O_EXEC_SYS_ENABLE, IOP_ICTX, 1, + 30000) != 0) { + printf("%s: unable to enable system\n", sc->sc_dv.dv_xname); + return; + } + + /* + * Set up an event handler for this IOP. + */ + sc->sc_eventii.ii_dv = self; + sc->sc_eventii.ii_intr = iop_intr_event; + sc->sc_eventii.ii_flags = II_DISCARD | II_UTILITY; + sc->sc_eventii.ii_tid = I2O_TID_IOP; + iop_initiator_register(sc, &sc->sc_eventii); + + rv = iop_util_eventreg(sc, &sc->sc_eventii, + I2O_EVENT_EXEC_RESOURCE_LIMITS | + I2O_EVENT_EXEC_CONNECTION_FAIL | + I2O_EVENT_EXEC_ADAPTER_FAULT | + I2O_EVENT_EXEC_POWER_FAIL | + I2O_EVENT_EXEC_RESET_PENDING | + I2O_EVENT_EXEC_RESET_IMMINENT | + I2O_EVENT_EXEC_HARDWARE_FAIL | + I2O_EVENT_EXEC_XCT_CHANGE | + I2O_EVENT_EXEC_DDM_AVAILIBILITY | + I2O_EVENT_GEN_DEVICE_RESET | + I2O_EVENT_GEN_STATE_CHANGE | + I2O_EVENT_GEN_GENERAL_WARNING); + if (rv != 0) { + printf("%s: unable to register for events", sc->sc_dv.dv_xname); + return; + } + + /* Attempt to match and attach a product-specific extension. */ + ia.ia_class = I2O_CLASS_ANY; + ia.ia_tid = I2O_TID_IOP; + config_found_sm(self, &ia, iop_vendor_print, iop_submatch); + + lockmgr(&sc->sc_conflock, LK_EXCLUSIVE, NULL, curproc); + if ((rv = iop_reconfigure(sc, 0)) == -1) { + printf("%s: configure failed (%d)\n", sc->sc_dv.dv_xname, rv); + return; + } + lockmgr(&sc->sc_conflock, LK_RELEASE, NULL, curproc); +} + +/* + * Create the reconfiguration thread. Called after the standard kernel + * threads have been created. + */ +void +iop_create_reconf_thread(void *cookie) +{ + struct iop_softc *sc; + int rv; + + sc = cookie; + sc->sc_flags |= IOP_ONLINE; + + iop_config_interrupts(cookie); + + rv = kthread_create(iop_reconf_thread, sc, &sc->sc_reconf_proc, + "%s", sc->sc_dv.dv_xname); + if (rv != 0) { + printf("%s: unable to create reconfiguration thread (%d)", + sc->sc_dv.dv_xname, rv); + return; + } +} + +/* + * Reconfiguration thread; listens for LCT change notification, and + * initiates re-configuration if recieved. + */ +void +iop_reconf_thread(void *cookie) +{ + struct iop_softc *sc = cookie; + struct proc *p = sc->sc_reconf_proc; + struct i2o_lct lct; + u_int32_t chgind; + int rv; + + chgind = sc->sc_chgind + 1; + + for (;;) { + DPRINTF(("%s: async reconfig: requested 0x%08x\n", + sc->sc_dv.dv_xname, chgind)); + + PHOLD(sc->sc_reconf_proc); + rv = iop_lct_get0(sc, &lct, sizeof(lct), chgind); + PRELE(sc->sc_reconf_proc); + + DPRINTF(("%s: async reconfig: notified (0x%08x, %d)\n", + sc->sc_dv.dv_xname, letoh32(lct.changeindicator), rv)); + + if (rv == 0 && + lockmgr(&sc->sc_conflock, LK_EXCLUSIVE, NULL, p) == 0) { + iop_reconfigure(sc, letoh32(lct.changeindicator)); + chgind = sc->sc_chgind + 1; + lockmgr(&sc->sc_conflock, LK_RELEASE, NULL, p); + } + + tsleep(iop_reconf_thread, PWAIT, "iopzzz", hz * 5); + } +} + +/* + * Reconfigure: find new and removed devices. + */ +int +iop_reconfigure(struct iop_softc *sc, u_int chgind) +{ + struct iop_msg *im; + struct i2o_hba_bus_scan mf; + struct i2o_lct_entry *le; + struct iop_initiator *ii, *nextii; + int rv, tid, i; + + /* + * If the reconfiguration request isn't the result of LCT change + * notification, then be more thorough: ask all bus ports to scan + * their busses. Wait up to 5 minutes for each bus port to complete + * the request. + */ + if (chgind == 0) { + if ((rv = iop_lct_get(sc)) != 0) { + DPRINTF(("iop_reconfigure: unable to read LCT\n")); + return (rv); + } + + le = sc->sc_lct->entry; + for (i = 0; i < sc->sc_nlctent; i++, le++) { + if ((letoh16(le->classid) & 4095) != + I2O_CLASS_BUS_ADAPTER_PORT) + continue; + tid = letoh32(le->localtid) & 4095; + + im = iop_msg_alloc(sc, NULL, IM_WAIT); + + mf.msgflags = I2O_MSGFLAGS(i2o_hba_bus_scan); + mf.msgfunc = I2O_MSGFUNC(tid, I2O_HBA_BUS_SCAN); + mf.msgictx = IOP_ICTX; + mf.msgtctx = im->im_tctx; + + DPRINTF(("%s: scanning bus %d\n", sc->sc_dv.dv_xname, + tid)); + + rv = iop_msg_post(sc, im, &mf, 5*60*1000); + iop_msg_free(sc, im); +#ifdef I2ODEBUG + if (rv != 0) + printf("%s: bus scan failed\n", + sc->sc_dv.dv_xname); +#endif + } + } else if (chgind <= sc->sc_chgind) { + DPRINTF(("%s: LCT unchanged (async)\n", sc->sc_dv.dv_xname)); + return (0); + } + + /* Re-read the LCT and determine if it has changed. */ + if ((rv = iop_lct_get(sc)) != 0) { + DPRINTF(("iop_reconfigure: unable to re-read LCT\n")); + return (rv); + } + DPRINTF(("%s: %d LCT entries\n", sc->sc_dv.dv_xname, sc->sc_nlctent)); + + chgind = letoh32(sc->sc_lct->changeindicator); + if (chgind == sc->sc_chgind) { + DPRINTF(("%s: LCT unchanged\n", sc->sc_dv.dv_xname)); + return (0); + } + DPRINTF(("%s: LCT changed\n", sc->sc_dv.dv_xname)); + sc->sc_chgind = chgind; + + if (sc->sc_tidmap != NULL) + free(sc->sc_tidmap, M_DEVBUF); + sc->sc_tidmap = malloc(sc->sc_nlctent * sizeof(struct iop_tidmap), + M_DEVBUF, M_NOWAIT); + if (!sc->sc_tidmap) { + DPRINTF(("iop_reconfigure: out of memory\n")); + return (ENOMEM); + } + bzero(sc->sc_tidmap, sizeof(sc->sc_tidmap)); + + /* Allow 1 queued command per device while we're configuring. */ + iop_adjqparam(sc, 1); + + /* + * Match and attach child devices. We configure high-level devices + * first so that any claims will propagate throughout the LCT, + * hopefully masking off aliased devices as a result. + * + * Re-reading the LCT at this point is a little dangerous, but we'll + * trust the IOP (and the operator) to behave itself... + */ + iop_configure_devices(sc, IC_CONFIGURE | IC_PRIORITY, + IC_CONFIGURE | IC_PRIORITY); + if ((rv = iop_lct_get(sc)) != 0) + DPRINTF(("iop_reconfigure: unable to re-read LCT\n")); + iop_configure_devices(sc, IC_CONFIGURE | IC_PRIORITY, + IC_CONFIGURE); + + for (ii = LIST_FIRST(&sc->sc_iilist); ii != NULL; ii = nextii) { + nextii = LIST_NEXT(ii, ii_list); + + /* Detach devices that were configured, but are now gone. */ + for (i = 0; i < sc->sc_nlctent; i++) + if (ii->ii_tid == sc->sc_tidmap[i].it_tid) + break; + if (i == sc->sc_nlctent || + (sc->sc_tidmap[i].it_flags & IT_CONFIGURED) == 0) + config_detach(ii->ii_dv, DETACH_FORCE); + + /* + * Tell initiators that existed before the re-configuration + * to re-configure. + */ + if (ii->ii_reconfig == NULL) + continue; + if ((rv = (*ii->ii_reconfig)(ii->ii_dv)) != 0) + printf("%s: %s failed reconfigure (%d)\n", + sc->sc_dv.dv_xname, ii->ii_dv->dv_xname, rv); + } + + /* Re-adjust queue parameters and return. */ + if (sc->sc_nii != 0) + iop_adjqparam(sc, (sc->sc_maxib - sc->sc_nuii - IOP_MF_RESERVE) + / sc->sc_nii); + + return (0); +} + +/* + * Configure I2O devices into the system. + */ +void +iop_configure_devices(struct iop_softc *sc, int mask, int maskval) +{ + struct iop_attach_args ia; + struct iop_initiator *ii; + const struct i2o_lct_entry *le; + struct device *dv; + int i, j, nent; + u_int usertid; + + nent = sc->sc_nlctent; + for (i = 0, le = sc->sc_lct->entry; i < nent; i++, le++) { + sc->sc_tidmap[i].it_tid = letoh32(le->localtid) & 4095; + + /* Ignore the device if it's in use. */ + usertid = letoh32(le->usertid) & 4095; + if (usertid != I2O_TID_NONE && usertid != I2O_TID_HOST) + continue; + + ia.ia_class = letoh16(le->classid) & 4095; + ia.ia_tid = sc->sc_tidmap[i].it_tid; + + /* Ignore uninteresting devices. */ + for (j = 0; j < sizeof(iop_class) / sizeof(iop_class[0]); j++) + if (iop_class[j].ic_class == ia.ia_class) + break; + if (j < sizeof(iop_class) / sizeof(iop_class[0]) && + (iop_class[j].ic_flags & mask) != maskval) + continue; + + /* + * Try to configure the device only if it's not already + * configured. + */ + LIST_FOREACH(ii, &sc->sc_iilist, ii_list) { + if (ia.ia_tid == ii->ii_tid) { + sc->sc_tidmap[i].it_flags |= IT_CONFIGURED; + strcpy(sc->sc_tidmap[i].it_dvname, + ii->ii_dv->dv_xname); + break; + } + } + if (ii != NULL) + continue; + dv = config_found_sm(&sc->sc_dv, &ia, iop_print, iop_submatch); + if (dv != NULL) { + sc->sc_tidmap[i].it_flags |= IT_CONFIGURED; + strcpy(sc->sc_tidmap[i].it_dvname, dv->dv_xname); + } + } +} + +/* + * Adjust queue parameters for all child devices. + */ +void +iop_adjqparam(struct iop_softc *sc, int mpi) +{ + struct iop_initiator *ii; + + LIST_FOREACH(ii, &sc->sc_iilist, ii_list) + if (ii->ii_adjqparam != NULL) + (*ii->ii_adjqparam)(ii->ii_dv, mpi); +} + +void +iop_devinfo(int class, char *devinfo) +{ +#ifdef I2OVERBOSE + int i; + + for (i = 0; i < sizeof(iop_class) / sizeof(iop_class[0]); i++) + if (class == iop_class[i].ic_class) + break; + + if (i == sizeof(iop_class) / sizeof(iop_class[0])) + sprintf(devinfo, "device (class 0x%x)", class); + else + strcpy(devinfo, iop_class[i].ic_caption); +#else + + sprintf(devinfo, "device (class 0x%x)", class); +#endif +} + +int +iop_print(void *aux, const char *pnp) +{ + struct iop_attach_args *ia; + char devinfo[256]; + + ia = aux; + + if (pnp != NULL) { + iop_devinfo(ia->ia_class, devinfo); + printf("%s at %s", devinfo, pnp); + } + printf(" tid %d", ia->ia_tid); + return (UNCONF); +} + +#ifdef notyet +int +iop_vendor_print(void *aux, const char *pnp) +{ + + if (pnp != NULL) + printf("vendor specific extension at %s", pnp); + return (UNCONF); +} +#endif + +int +iop_submatch(struct device *parent, void *vcf, void *aux) +{ + struct cfdata *cf = vcf; + struct iop_attach_args *ia; + + ia = aux; + + if (cf->iopcf_tid != IOPCF_TID_DEFAULT && cf->iopcf_tid != ia->ia_tid) + return (0); + + return ((*cf->cf_attach->ca_match)(parent, cf, aux)); +} + +/* + * Shut down all configured IOPs. + */ +void +iop_shutdown(void *junk) +{ + struct iop_softc *sc; + int i; + + printf("shutting down iop devices..."); + + for (i = 0; i < iop_cd.cd_ndevs; i++) { + if (!(sc = (struct iop_softc *)device_lookup(&iop_cd, i))) + continue; + if ((sc->sc_flags & IOP_ONLINE) == 0) + continue; + iop_simple_cmd(sc, I2O_TID_IOP, I2O_EXEC_SYS_QUIESCE, IOP_ICTX, + 0, 5000); + iop_simple_cmd(sc, I2O_TID_IOP, I2O_EXEC_IOP_CLEAR, IOP_ICTX, + 0, 1000); + } + + /* Wait. Some boards could still be flushing, stupidly enough. */ + delay(5000*1000); + printf(" done.\n"); +} + +/* + * Retrieve IOP status. + */ +int +iop_status_get(struct iop_softc *sc, int nosleep) +{ + struct i2o_exec_status_get mf; + int rv, i; + paddr_t pa; + + mf.msgflags = I2O_MSGFLAGS(i2o_exec_status_get); + mf.msgfunc = I2O_MSGFUNC(I2O_TID_IOP, I2O_EXEC_STATUS_GET); + mf.reserved[0] = 0; + mf.reserved[1] = 0; + mf.reserved[2] = 0; + mf.reserved[3] = 0; + pa = vtophys((vaddr_t)&sc->sc_status); + mf.addrlow = pa & 0xffffffff; + mf.addrhigh = sizeof pa > sizeof mf.addrlow ? pa >> 32 : 0; + mf.length = sizeof(sc->sc_status); + + memset(&sc->sc_status, 0, sizeof(sc->sc_status)); + + if ((rv = iop_post(sc, (u_int32_t *)&mf)) != 0) + return (rv); + + /* XXX */ + for (i = 25; i != 0; i--) { + if (*((volatile u_char *)&sc->sc_status.syncbyte) == 0xff) + break; + if (nosleep) + DELAY(100*1000); + else + tsleep(iop_status_get, PWAIT, "iopstat", hz / 10); + } + + if (*((volatile u_char *)&sc->sc_status.syncbyte) != 0xff) + rv = EIO; + else + rv = 0; + return (rv); +} + +/* + * Initalize and populate the IOP's outbound FIFO. + */ +int +iop_ofifo_init(struct iop_softc *sc) +{ + struct iop_msg *im; + volatile u_int32_t status; + bus_addr_t addr; + bus_dma_segment_t seg; + struct i2o_exec_outbound_init *mf; + int i, rseg, rv; + u_int32_t mb[IOP_MAX_MSG_SIZE / sizeof(u_int32_t)]; + + im = iop_msg_alloc(sc, NULL, IM_POLL); + + mf = (struct i2o_exec_outbound_init *)mb; + mf->msgflags = I2O_MSGFLAGS(i2o_exec_outbound_init); + mf->msgfunc = I2O_MSGFUNC(I2O_TID_IOP, I2O_EXEC_OUTBOUND_INIT); + mf->msgictx = IOP_ICTX; + mf->msgtctx = im->im_tctx; + mf->pagesize = PAGE_SIZE; + mf->flags = IOP_INIT_CODE | ((IOP_MAX_MSG_SIZE >> 2) << 16); + + status = 0; + + /* + * The I2O spec says that there are two SGLs: one for the status + * word, and one for a list of discarded MFAs. It continues to say + * that if you don't want to get the list of MFAs, an IGNORE SGL is + * necessary; this isn't the case (and is in fact a bad thing). + */ + iop_msg_map(sc, im, mb, (void *)&status, sizeof(status), 0); + if ((rv = iop_msg_post(sc, im, mb, 0)) != 0) { + iop_msg_free(sc, im); + return (rv); + } + iop_msg_unmap(sc, im); + iop_msg_free(sc, im); + + /* XXX */ + POLL(5000, status == I2O_EXEC_OUTBOUND_INIT_COMPLETE); + if (status != I2O_EXEC_OUTBOUND_INIT_COMPLETE) { + printf("%s: outbound FIFO init failed\n", sc->sc_dv.dv_xname); + return (EIO); + } + + /* Allocate DMA safe memory for the reply frames. */ + if (sc->sc_rep_phys == 0) { + sc->sc_rep_size = sc->sc_maxob * IOP_MAX_MSG_SIZE; + + rv = bus_dmamem_alloc(sc->sc_dmat, sc->sc_rep_size, PAGE_SIZE, + 0, &seg, 1, &rseg, BUS_DMA_NOWAIT); + if (rv != 0) { + printf("%s: dma alloc = %d\n", sc->sc_dv.dv_xname, + rv); + return (rv); + } + + rv = bus_dmamem_map(sc->sc_dmat, &seg, rseg, sc->sc_rep_size, + &sc->sc_rep, BUS_DMA_NOWAIT | BUS_DMA_COHERENT); + if (rv != 0) { + printf("%s: dma map = %d\n", sc->sc_dv.dv_xname, rv); + return (rv); + } + + rv = bus_dmamap_create(sc->sc_dmat, sc->sc_rep_size, 1, + sc->sc_rep_size, 0, BUS_DMA_NOWAIT, &sc->sc_rep_dmamap); + if (rv != 0) { + printf("%s: dma create = %d\n", sc->sc_dv.dv_xname, rv); + return (rv); + } + + rv = bus_dmamap_load(sc->sc_dmat, sc->sc_rep_dmamap, sc->sc_rep, + sc->sc_rep_size, NULL, BUS_DMA_NOWAIT); + if (rv != 0) { + printf("%s: dma load = %d\n", sc->sc_dv.dv_xname, rv); + return (rv); + } + + sc->sc_rep_phys = sc->sc_rep_dmamap->dm_segs[0].ds_addr; + } + + /* Populate the outbound FIFO. */ + for (i = sc->sc_maxob, addr = sc->sc_rep_phys; i != 0; i--) { + iop_outl(sc, IOP_REG_OFIFO, (u_int32_t)addr); + addr += IOP_MAX_MSG_SIZE; + } + + return (0); +} + +/* + * Read the specified number of bytes from the IOP's hardware resource table. + */ +int +iop_hrt_get0(struct iop_softc *sc, struct i2o_hrt *hrt, int size) +{ + struct iop_msg *im; + int rv; + struct i2o_exec_hrt_get *mf; + u_int32_t mb[IOP_MAX_MSG_SIZE / sizeof(u_int32_t)]; + + im = iop_msg_alloc(sc, NULL, IM_WAIT); + mf = (struct i2o_exec_hrt_get *)mb; + mf->msgflags = I2O_MSGFLAGS(i2o_exec_hrt_get); + mf->msgfunc = I2O_MSGFUNC(I2O_TID_IOP, I2O_EXEC_HRT_GET); + mf->msgictx = IOP_ICTX; + mf->msgtctx = im->im_tctx; + + iop_msg_map(sc, im, mb, hrt, size, 0); + rv = iop_msg_post(sc, im, mb, 30000); + iop_msg_unmap(sc, im); + iop_msg_free(sc, im); + return (rv); +} + +/* + * Read the IOP's hardware resource table. + */ +int +iop_hrt_get(struct iop_softc *sc) +{ + struct i2o_hrt hrthdr, *hrt; + int size, rv; + + PHOLD(curproc); + rv = iop_hrt_get0(sc, &hrthdr, sizeof(hrthdr)); + PRELE(curproc); + if (rv != 0) + return (rv); + + DPRINTF(("%s: %d hrt entries\n", sc->sc_dv.dv_xname, + letoh16(hrthdr.numentries))); + + size = sizeof(struct i2o_hrt) + + (htole32(hrthdr.numentries) - 1) * sizeof(struct i2o_hrt_entry); + hrt = (struct i2o_hrt *)malloc(size, M_DEVBUF, M_NOWAIT); + if (!hrt) + return (ENOMEM); + + if ((rv = iop_hrt_get0(sc, hrt, size)) != 0) { + free(hrt, M_DEVBUF); + return (rv); + } + + if (sc->sc_hrt != NULL) + free(sc->sc_hrt, M_DEVBUF); + sc->sc_hrt = hrt; + return (0); +} + +/* + * Request the specified number of bytes from the IOP's logical + * configuration table. If a change indicator is specified, this + * is a verbatim notification request, so the caller is prepared + * to wait indefinitely. + */ +int +iop_lct_get0(struct iop_softc *sc, struct i2o_lct *lct, int size, + u_int32_t chgind) +{ + struct iop_msg *im; + struct i2o_exec_lct_notify *mf; + int rv; + u_int32_t mb[IOP_MAX_MSG_SIZE / sizeof(u_int32_t)]; + + im = iop_msg_alloc(sc, NULL, IM_WAIT); + memset(lct, 0, size); + + mf = (struct i2o_exec_lct_notify *)mb; + mf->msgflags = I2O_MSGFLAGS(i2o_exec_lct_notify); + mf->msgfunc = I2O_MSGFUNC(I2O_TID_IOP, I2O_EXEC_LCT_NOTIFY); + mf->msgictx = IOP_ICTX; + mf->msgtctx = im->im_tctx; + mf->classid = I2O_CLASS_ANY; + mf->changeindicator = chgind; + +#ifdef I2ODEBUG + printf("iop_lct_get0: reading LCT"); + if (chgind != 0) + printf(" (async)"); + printf("\n"); +#endif + + iop_msg_map(sc, im, mb, lct, size, 0); + rv = iop_msg_post(sc, im, mb, (chgind == 0 ? 120*1000 : 0)); + iop_msg_unmap(sc, im); + iop_msg_free(sc, im); + return (rv); +} + +/* + * Read the IOP's logical configuration table. + */ +int +iop_lct_get(struct iop_softc *sc) +{ + int esize, size, rv; + struct i2o_lct *lct; + + esize = letoh32(sc->sc_status.expectedlctsize); + lct = (struct i2o_lct *)malloc(esize, M_DEVBUF, M_WAITOK); + if (lct == NULL) + return (ENOMEM); + + if ((rv = iop_lct_get0(sc, lct, esize, 0)) != 0) { + free(lct, M_DEVBUF); + return (rv); + } + + size = letoh16(lct->tablesize) << 2; + if (esize != size) { + free(lct, M_DEVBUF); + lct = (struct i2o_lct *)malloc(size, M_DEVBUF, M_WAITOK); + if (lct == NULL) + return (ENOMEM); + + if ((rv = iop_lct_get0(sc, lct, size, 0)) != 0) { + free(lct, M_DEVBUF); + return (rv); + } + } + + /* Swap in the new LCT. */ + if (sc->sc_lct != NULL) + free(sc->sc_lct, M_DEVBUF); + sc->sc_lct = lct; + sc->sc_nlctent = ((letoh16(sc->sc_lct->tablesize) << 2) - + sizeof(struct i2o_lct) + sizeof(struct i2o_lct_entry)) / + sizeof(struct i2o_lct_entry); + return (0); +} + +/* + * Request the specified parameter group from the target. If an initiator + * is specified (a) don't wait for the operation to complete, but instead + * let the initiator's interrupt handler deal with the reply and (b) place a + * pointer to the parameter group op in the wrapper's `im_dvcontext' field. + */ +int +iop_param_op(struct iop_softc *sc, int tid, struct iop_initiator *ii, + int write, int group, void *buf, int size) +{ + struct iop_msg *im; + struct i2o_util_params_op *mf; + struct i2o_reply *rf; + int rv, func, op; + struct iop_pgop *pgop; + u_int32_t mb[IOP_MAX_MSG_SIZE / sizeof(u_int32_t)]; + + im = iop_msg_alloc(sc, ii, (ii == NULL ? IM_WAIT : 0) | IM_NOSTATUS); + if ((pgop = malloc(sizeof(*pgop), M_DEVBUF, M_WAITOK)) == NULL) { + iop_msg_free(sc, im); + return (ENOMEM); + } + if ((rf = malloc(sizeof(*rf), M_DEVBUF, M_WAITOK)) == NULL) { + iop_msg_free(sc, im); + free(pgop, M_DEVBUF); + return (ENOMEM); + } + im->im_dvcontext = pgop; + im->im_rb = rf; + + if (write) { + func = I2O_UTIL_PARAMS_SET; + op = I2O_PARAMS_OP_FIELD_SET; + } else { + func = I2O_UTIL_PARAMS_GET; + op = I2O_PARAMS_OP_FIELD_GET; + } + + mf = (struct i2o_util_params_op *)mb; + mf->msgflags = I2O_MSGFLAGS(i2o_util_params_op); + mf->msgfunc = I2O_MSGFUNC(tid, func); + mf->msgictx = IOP_ICTX; + mf->msgtctx = im->im_tctx; + mf->flags = 0; + + pgop->olh.count = htole16(1); + pgop->olh.reserved = htole16(0); + pgop->oat.operation = htole16(op); + pgop->oat.fieldcount = htole16(0xffff); + pgop->oat.group = htole16(group); + + if (ii == NULL) + PHOLD(curproc); + + memset(buf, 0, size); + iop_msg_map(sc, im, mb, pgop, sizeof(*pgop), 1); + iop_msg_map(sc, im, mb, buf, size, write); + rv = iop_msg_post(sc, im, mb, (ii == NULL ? 30000 : 0)); + + if (ii == NULL) + PRELE(curproc); + + /* Detect errors; let partial transfers to count as success. */ + if (ii == NULL && rv == 0) { + if (rf->reqstatus == I2O_STATUS_ERROR_PARTIAL_XFER && + letoh16(rf->detail) == I2O_DSC_UNKNOWN_ERROR) + rv = 0; + else + rv = (rf->reqstatus != 0 ? EIO : 0); + } + + if (ii == NULL || rv != 0) { + iop_msg_unmap(sc, im); + iop_msg_free(sc, im); + free(pgop, M_DEVBUF); + free(rf, M_DEVBUF); + } + + return (rv); +} + +/* + * Execute a simple command (no parameters). + */ +int +iop_simple_cmd(struct iop_softc *sc, int tid, int function, int ictx, + int async, int timo) +{ + struct iop_msg *im; + struct i2o_msg mf; + int rv, fl; + + fl = (async != 0 ? IM_WAIT : IM_POLL); + im = iop_msg_alloc(sc, NULL, fl); + + mf.msgflags = I2O_MSGFLAGS(i2o_msg); + mf.msgfunc = I2O_MSGFUNC(tid, function); + mf.msgictx = ictx; + mf.msgtctx = im->im_tctx; + + rv = iop_msg_post(sc, im, &mf, timo); + iop_msg_free(sc, im); + return (rv); +} + +/* + * Post the system table to the IOP. + */ +int +iop_systab_set(struct iop_softc *sc) +{ + struct i2o_exec_sys_tab_set *mf; + struct iop_msg *im; + bus_space_handle_t bsh; + bus_addr_t boo; + u_int32_t mema[2], ioa[2]; + int rv; + u_int32_t mb[IOP_MAX_MSG_SIZE / sizeof(u_int32_t)]; + + im = iop_msg_alloc(sc, NULL, IM_WAIT); + + mf = (struct i2o_exec_sys_tab_set *)mb; + mf->msgflags = I2O_MSGFLAGS(i2o_exec_sys_tab_set); + mf->msgfunc = I2O_MSGFUNC(I2O_TID_IOP, I2O_EXEC_SYS_TAB_SET); + mf->msgictx = IOP_ICTX; + mf->msgtctx = im->im_tctx; + mf->iopid = (sc->sc_dv.dv_unit + 2) << 12; + mf->segnumber = 0; + + mema[1] = sc->sc_status.desiredprivmemsize; + ioa[1] = sc->sc_status.desiredpriviosize; + + if (mema[1] != 0) { + rv = bus_space_alloc(sc->sc_bus_memt, 0, 0xffffffff, + letoh32(mema[1]), PAGE_SIZE, 0, 0, &boo, &bsh); + mema[0] = htole32(boo); + if (rv != 0) { + printf("%s: can't alloc priv mem space, err = %d\n", + sc->sc_dv.dv_xname, rv); + mema[0] = 0; + mema[1] = 0; + } + } + + if (ioa[1] != 0) { + rv = bus_space_alloc(sc->sc_bus_iot, 0, 0xffff, + letoh32(ioa[1]), 0, 0, 0, &boo, &bsh); + ioa[0] = htole32(boo); + if (rv != 0) { + printf("%s: can't alloc priv i/o space, err = %d\n", + sc->sc_dv.dv_xname, rv); + ioa[0] = 0; + ioa[1] = 0; + } + } + + PHOLD(curproc); + iop_msg_map(sc, im, mb, iop_systab, iop_systab_size, 1); + iop_msg_map(sc, im, mb, mema, sizeof(mema), 1); + iop_msg_map(sc, im, mb, ioa, sizeof(ioa), 1); + rv = iop_msg_post(sc, im, mb, 5000); + iop_msg_unmap(sc, im); + iop_msg_free(sc, im); + PRELE(curproc); + return (rv); +} + +/* + * Reset the IOP. Must be called with interrupts disabled. + */ +int +iop_reset(struct iop_softc *sc) +{ + u_int32_t mfa; + struct i2o_exec_iop_reset mf; + int rv = 0; + int state = 0; + bus_dmamap_t map; + bus_dma_segment_t seg; + int nsegs; + u_int32_t *sw; + paddr_t pa; + + if (bus_dmamap_create(sc->sc_dmat, sizeof *sw, 1, sizeof *sw, 0, + BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &map) != 0) + return (ENOMEM); + + if (bus_dmamem_alloc(sc->sc_dmat, sizeof *sw, sizeof *sw, 0, &seg, 1, + &nsegs, BUS_DMA_NOWAIT) != 0) { + rv = ENOMEM; + goto release; + } + state++; + + pa = seg.ds_addr; + if (bus_dmamem_map(sc->sc_dmat, &seg, nsegs, sizeof *sw, + (caddr_t *)&sw, 0)) { + rv = ENOMEM; + goto release; + } + state++; + + mf.msgflags = I2O_MSGFLAGS(i2o_exec_iop_reset); + mf.msgfunc = I2O_MSGFUNC(I2O_TID_IOP, I2O_EXEC_IOP_RESET); + mf.reserved[0] = 0; + mf.reserved[1] = 0; + mf.reserved[2] = 0; + mf.reserved[3] = 0; + mf.statuslow = pa & ~(u_int32_t)0; + mf.statushigh = sizeof pa > sizeof mf.statuslow ? pa >> 32 : 0; + + if (bus_dmamap_load(sc->sc_dmat, map, &sw, sizeof *sw, NULL, + BUS_DMA_NOWAIT)) { + rv = ENOMEM; + goto release; + } + state++; + + bus_dmamap_sync(sc->sc_dmat, map, BUS_DMASYNC_PREREAD); + + if ((rv = iop_post(sc, (u_int32_t *)&mf)) != 0) + goto release; + + /* XXX */ + POLL(2500, + (bus_dmamap_sync(sc->sc_dmat, map, BUS_DMASYNC_POSTREAD), + *sw != 0)); + if (*sw != I2O_RESET_IN_PROGRESS) { + printf("%s: reset rejected, status 0x%x\n", + sc->sc_dv.dv_xname, *sw); + rv = EIO; + goto release; + } + + /* + * IOP is now in the INIT state. Wait no more than 10 seconds for + * the inbound queue to become responsive. + */ + POLL(10000, (mfa = iop_inl(sc, IOP_REG_IFIFO)) != IOP_MFA_EMPTY); + if (mfa == IOP_MFA_EMPTY) { + printf("%s: reset failed\n", sc->sc_dv.dv_xname); + rv = EIO; + goto release; + } + + iop_release_mfa(sc, mfa); + + release: + if (state > 2) + bus_dmamap_unload(sc->sc_dmat, map); + if (state > 1) + bus_dmamem_unmap(sc->sc_dmat, (caddr_t)sw, sizeof *sw); + if (state > 0) + bus_dmamem_free(sc->sc_dmat, &seg, nsegs); + bus_dmamap_destroy(sc->sc_dmat, map); + return (rv); +} + +/* + * Register a new initiator. Must be called with the configuration lock + * held. + */ +void +iop_initiator_register(struct iop_softc *sc, struct iop_initiator *ii) +{ + static int ictxgen; + int s; + + /* 0 is reserved (by us) for system messages. */ + ii->ii_ictx = ++ictxgen; + + /* + * `Utility initiators' don't make it onto the per-IOP initiator list + * (which is used only for configuration), but do get one slot on + * the inbound queue. + */ + if ((ii->ii_flags & II_UTILITY) == 0) { + LIST_INSERT_HEAD(&sc->sc_iilist, ii, ii_list); + sc->sc_nii++; + } else + sc->sc_nuii++; + + s = splbio(); + LIST_INSERT_HEAD(IOP_ICTXHASH(ii->ii_ictx), ii, ii_hash); + splx(s); +} + +/* + * Unregister an initiator. Must be called with the configuration lock + * held. + */ +void +iop_initiator_unregister(struct iop_softc *sc, struct iop_initiator *ii) +{ + int s; + + if ((ii->ii_flags & II_UTILITY) == 0) { + LIST_REMOVE(ii, ii_list); + sc->sc_nii--; + } else + sc->sc_nuii--; + + s = splbio(); + LIST_REMOVE(ii, ii_hash); + splx(s); +} + +/* + * Handle a reply frame from the IOP. + */ +int +iop_handle_reply(struct iop_softc *sc, u_int32_t rmfa) +{ + struct iop_msg *im; + struct i2o_reply *rb; + struct i2o_fault_notify *fn; + struct iop_initiator *ii; + u_int off, ictx, tctx, status, size; + + off = (int)(rmfa - sc->sc_rep_phys); + rb = (struct i2o_reply *)(sc->sc_rep + off); + + /* Perform reply queue DMA synchronisation. XXX This is rubbish. */ + bus_dmamap_sync(sc->sc_dmat, sc->sc_rep_dmamap, BUS_DMASYNC_POSTREAD); + if (--sc->sc_curib != 0) + bus_dmamap_sync(sc->sc_dmat, sc->sc_rep_dmamap, + BUS_DMASYNC_PREREAD); + +#ifdef I2ODEBUG + if ((letoh32(rb->msgflags) & I2O_MSGFLAGS_64BIT) != 0) + panic("iop_handle_reply: 64-bit reply"); +#endif + /* + * Find the initiator. + */ + ictx = letoh32(rb->msgictx); + if (ictx == IOP_ICTX) + ii = NULL; + else { + ii = LIST_FIRST(IOP_ICTXHASH(ictx)); + for (; ii != NULL; ii = LIST_NEXT(ii, ii_hash)) + if (ii->ii_ictx == ictx) + break; + if (ii == NULL) { +#ifdef I2ODEBUG + iop_reply_print(sc, rb); +#endif + printf("%s: WARNING: bad ictx returned (%x)\n", + sc->sc_dv.dv_xname, ictx); + return (-1); + } + } + + /* + * If we recieved a transport failure notice, we've got to dig the + * transaction context (if any) out of the original message frame, + * and then release the original MFA back to the inbound FIFO. + */ + if ((rb->msgflags & I2O_MSGFLAGS_FAIL) != 0) { + status = I2O_STATUS_SUCCESS; + + fn = (struct i2o_fault_notify *)rb; + tctx = iop_inl(sc, fn->lowmfa + 12); /* XXX */ + iop_release_mfa(sc, fn->lowmfa); + iop_tfn_print(sc, fn); + } else { + status = rb->reqstatus; + tctx = letoh32(rb->msgtctx); + } + + if (ii == NULL || (ii->ii_flags & II_DISCARD) == 0) { + /* + * This initiator tracks state using message wrappers. + * + * Find the originating message wrapper, and if requested + * notify the initiator. + */ + im = sc->sc_ims + (tctx & IOP_TCTX_MASK); + if ((tctx & IOP_TCTX_MASK) > sc->sc_maxib || + (im->im_flags & IM_ALLOCED) == 0 || + tctx != im->im_tctx) { + printf("%s: WARNING: bad tctx returned (0x%08x, %p)\n", + sc->sc_dv.dv_xname, tctx, im); + if (im != NULL) + printf("%s: flags=0x%08x tctx=0x%08x\n", + sc->sc_dv.dv_xname, im->im_flags, + im->im_tctx); +#ifdef I2ODEBUG + if ((rb->msgflags & I2O_MSGFLAGS_FAIL) == 0) + iop_reply_print(sc, rb); +#endif + return (-1); + } + + if ((rb->msgflags & I2O_MSGFLAGS_FAIL) != 0) + im->im_flags |= IM_FAIL; + +#ifdef I2ODEBUG + if ((im->im_flags & IM_REPLIED) != 0) + panic("%s: dup reply", sc->sc_dv.dv_xname); +#endif + im->im_flags |= IM_REPLIED; + +#ifdef I2ODEBUG + if (status != I2O_STATUS_SUCCESS) + iop_reply_print(sc, rb); +#endif + im->im_reqstatus = status; + + /* Copy the reply frame, if requested. */ + if (im->im_rb != NULL) { + size = (letoh32(rb->msgflags) >> 14) & ~3; +#ifdef I2ODEBUG + if (size > IOP_MAX_MSG_SIZE) + panic("iop_handle_reply: reply too large"); +#endif + memcpy(im->im_rb, rb, size); + } + + /* Notify the initiator. */ + if ((im->im_flags & IM_WAIT) != 0) + wakeup(im); + else if ((im->im_flags & (IM_POLL | IM_POLL_INTR)) != IM_POLL) + (*ii->ii_intr)(ii->ii_dv, im, rb); + } else { + /* + * This initiator discards message wrappers. + * + * Simply pass the reply frame to the initiator. + */ + (*ii->ii_intr)(ii->ii_dv, NULL, rb); + } + + return (status); +} + +/* + * Handle an interrupt from the IOP. + */ +int +iop_intr(void *arg) +{ + struct iop_softc *sc; + u_int32_t rmfa; + + sc = arg; + + if ((iop_inl(sc, IOP_REG_INTR_STATUS) & IOP_INTR_OFIFO) == 0) + return (0); + + for (;;) { + /* Double read to account for IOP bug. */ + if ((rmfa = iop_inl(sc, IOP_REG_OFIFO)) == IOP_MFA_EMPTY) { + rmfa = iop_inl(sc, IOP_REG_OFIFO); + if (rmfa == IOP_MFA_EMPTY) + break; + } + iop_handle_reply(sc, rmfa); + iop_outl(sc, IOP_REG_OFIFO, rmfa); + } + + return (1); +} + +/* + * Handle an event signalled by the executive. + */ +void +iop_intr_event(struct device *dv, struct iop_msg *im, void *reply) +{ + struct i2o_util_event_register_reply *rb; + struct iop_softc *sc; + u_int event; + + sc = (struct iop_softc *)dv; + rb = reply; + + if ((rb->msgflags & I2O_MSGFLAGS_FAIL) != 0) + return; + + event = letoh32(rb->event); + printf("%s: event 0x%08x received\n", dv->dv_xname, event); +} + +/* + * Allocate a message wrapper. + */ +struct iop_msg * +iop_msg_alloc(struct iop_softc *sc, struct iop_initiator *ii, int flags) +{ + struct iop_msg *im; + static u_int tctxgen; + int s, i; + +#ifdef I2ODEBUG + if ((flags & IM_SYSMASK) != 0) + panic("iop_msg_alloc: system flags specified"); +#endif + + s = splbio(); /* XXX */ + im = SLIST_FIRST(&sc->sc_im_freelist); +#if defined(DIAGNOSTIC) || defined(I2ODEBUG) + if (im == NULL) + panic("iop_msg_alloc: no free wrappers"); +#endif + SLIST_REMOVE_HEAD(&sc->sc_im_freelist, im_chain); + splx(s); + + if (ii != NULL && (ii->ii_flags & II_DISCARD) != 0) + flags |= IM_DISCARD; + + im->im_tctx = (im->im_tctx & IOP_TCTX_MASK) | tctxgen; + tctxgen += (1 << IOP_TCTX_SHIFT); + im->im_flags = flags | IM_ALLOCED; + im->im_rb = NULL; + i = 0; + do { + im->im_xfer[i++].ix_size = 0; + } while (i < IOP_MAX_MSG_XFERS); + + return (im); +} + +/* + * Free a message wrapper. + */ +void +iop_msg_free(struct iop_softc *sc, struct iop_msg *im) +{ + int s; + +#ifdef I2ODEBUG + if ((im->im_flags & IM_ALLOCED) == 0) + panic("iop_msg_free: wrapper not allocated"); +#endif + + im->im_flags = 0; + s = splbio(); + SLIST_INSERT_HEAD(&sc->sc_im_freelist, im, im_chain); + splx(s); +} + +/* + * Map a data transfer. Write a scatter-gather list into the message frame. + */ +int +iop_msg_map(struct iop_softc *sc, struct iop_msg *im, u_int32_t *mb, + void *xferaddr, int xfersize, int out) +{ + bus_dmamap_t dm; + bus_dma_segment_t *ds; + struct iop_xfer *ix; + u_int rv, i, nsegs, flg, off, xn; + u_int32_t *p; + + for (xn = 0, ix = im->im_xfer; xn < IOP_MAX_MSG_XFERS; xn++, ix++) + if (ix->ix_size == 0) + break; + +#ifdef I2ODEBUG + if (xfersize == 0) + panic("iop_msg_map: null transfer"); + if (xfersize > IOP_MAX_XFER) + panic("iop_msg_map: transfer too large"); + if (xn == IOP_MAX_MSG_XFERS) + panic("iop_msg_map: too many xfers"); +#endif + + /* + * Only the first DMA map is static. + */ + if (xn != 0) { + rv = bus_dmamap_create(sc->sc_dmat, IOP_MAX_XFER, + IOP_MAX_SEGS, IOP_MAX_XFER, 0, + BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ix->ix_map); + if (rv != 0) + return (rv); + } + + dm = ix->ix_map; + rv = bus_dmamap_load(sc->sc_dmat, dm, xferaddr, xfersize, NULL, 0); + if (rv != 0) + goto bad; + + /* + * How many SIMPLE SG elements can we fit in this message? + */ + off = mb[0] >> 16; + p = mb + off; + nsegs = ((IOP_MAX_MSG_SIZE / 4) - off) >> 1; + + if (dm->dm_nsegs > nsegs) { + bus_dmamap_unload(sc->sc_dmat, ix->ix_map); + rv = EFBIG; + DPRINTF(("iop_msg_map: too many segs\n")); + goto bad; + } + + nsegs = dm->dm_nsegs; + xfersize = 0; + + /* + * Write out the SG list. + */ + if (out) + flg = I2O_SGL_SIMPLE | I2O_SGL_DATA_OUT; + else + flg = I2O_SGL_SIMPLE; + + for (i = nsegs, ds = dm->dm_segs; i > 1; i--, p += 2, ds++) { + p[0] = (u_int32_t)ds->ds_len | flg; + p[1] = (u_int32_t)ds->ds_addr; + xfersize += ds->ds_len; + } + + p[0] = (u_int32_t)ds->ds_len | flg | I2O_SGL_END_BUFFER; + p[1] = (u_int32_t)ds->ds_addr; + xfersize += ds->ds_len; + + /* Fix up the transfer record, and sync the map. */ + ix->ix_flags = (out ? IX_OUT : IX_IN); + ix->ix_size = xfersize; + bus_dmamap_sync(sc->sc_dmat, ix->ix_map, + out ? BUS_DMASYNC_POSTWRITE : BUS_DMASYNC_POSTREAD); + + /* + * If this is the first xfer we've mapped for this message, adjust + * the SGL offset field in the message header. + */ + if ((im->im_flags & IM_SGLOFFADJ) == 0) { + mb[0] += (mb[0] >> 12) & 0xf0; + im->im_flags |= IM_SGLOFFADJ; + } + mb[0] += (nsegs << 17); + return (0); + + bad: + if (xn != 0) + bus_dmamap_destroy(sc->sc_dmat, ix->ix_map); + return (rv); +} + +/* + * Map a block I/O data transfer (different in that there's only one per + * message maximum, and PAGE addressing may be used). Write a scatter + * gather list into the message frame. + */ +int +iop_msg_map_bio(struct iop_softc *sc, struct iop_msg *im, u_int32_t *mb, + void *xferaddr, int xfersize, int out) +{ + bus_dma_segment_t *ds; + bus_dmamap_t dm; + struct iop_xfer *ix; + u_int rv, i, nsegs, off, slen, tlen, flg; + paddr_t saddr, eaddr; + u_int32_t *p; + +#ifdef I2ODEBUG + if (xfersize == 0) + panic("iop_msg_map_bio: null transfer"); + if (xfersize > IOP_MAX_XFER) + panic("iop_msg_map_bio: transfer too large"); + if ((im->im_flags & IM_SGLOFFADJ) != 0) + panic("iop_msg_map_bio: SGLOFFADJ"); +#endif + + ix = im->im_xfer; + dm = ix->ix_map; + rv = bus_dmamap_load(sc->sc_dmat, dm, xferaddr, xfersize, NULL, 0); + if (rv != 0) + return (rv); + + off = mb[0] >> 16; + nsegs = ((IOP_MAX_MSG_SIZE / 4) - off) >> 1; + + /* + * If the transfer is highly fragmented and won't fit using SIMPLE + * elements, use PAGE_LIST elements instead. SIMPLE elements are + * potentially more efficient, both for us and the IOP. + */ + if (dm->dm_nsegs > nsegs) { + nsegs = 1; + p = mb + off + 1; + + /* XXX This should be done with a bus_space flag. */ + for (i = dm->dm_nsegs, ds = dm->dm_segs; i > 0; i--, ds++) { + slen = ds->ds_len; + saddr = ds->ds_addr; + + while (slen > 0) { + eaddr = (saddr + PAGE_SIZE) & ~(PAGE_SIZE - 1); + tlen = min(eaddr - saddr, slen); + slen -= tlen; + *p++ = letoh32(saddr); + saddr = eaddr; + nsegs++; + } + } + + mb[off] = xfersize | I2O_SGL_PAGE_LIST | I2O_SGL_END_BUFFER | + I2O_SGL_END; + if (out) + mb[off] |= I2O_SGL_DATA_OUT; + } else { + p = mb + off; + nsegs = dm->dm_nsegs; + + if (out) + flg = I2O_SGL_SIMPLE | I2O_SGL_DATA_OUT; + else + flg = I2O_SGL_SIMPLE; + + for (i = nsegs, ds = dm->dm_segs; i > 1; i--, p += 2, ds++) { + p[0] = (u_int32_t)ds->ds_len | flg; + p[1] = (u_int32_t)ds->ds_addr; + } + + p[0] = (u_int32_t)ds->ds_len | flg | I2O_SGL_END_BUFFER | + I2O_SGL_END; + p[1] = (u_int32_t)ds->ds_addr; + nsegs <<= 1; + } + + /* Fix up the transfer record, and sync the map. */ + ix->ix_flags = (out ? IX_OUT : IX_IN); + ix->ix_size = xfersize; + bus_dmamap_sync(sc->sc_dmat, ix->ix_map, + out ? BUS_DMASYNC_POSTWRITE : BUS_DMASYNC_POSTREAD); + + /* + * Adjust the SGL offset and total message size fields. We don't + * set IM_SGLOFFADJ, since it's used only for SIMPLE elements. + */ + mb[0] += ((off << 4) + (nsegs << 16)); + return (0); +} + +/* + * Unmap all data transfers associated with a message wrapper. + */ +void +iop_msg_unmap(struct iop_softc *sc, struct iop_msg *im) +{ + struct iop_xfer *ix; + int i; + +#ifdef I2ODEBUG + if (im->im_xfer[0].ix_size == 0) + panic("iop_msg_unmap: no transfers mapped"); +#endif + + for (ix = im->im_xfer, i = 0;;) { + bus_dmamap_sync(sc->sc_dmat, ix->ix_map, + ix->ix_flags & IX_OUT ? BUS_DMASYNC_POSTWRITE : + BUS_DMASYNC_POSTREAD); + bus_dmamap_unload(sc->sc_dmat, ix->ix_map); + + /* Only the first DMA map is static. */ + if (i != 0) + bus_dmamap_destroy(sc->sc_dmat, ix->ix_map); + if ((++ix)->ix_size == 0) + break; + if (++i >= IOP_MAX_MSG_XFERS) + break; + } +} + +/* + * Post a message frame to the IOP's inbound queue. + */ +int +iop_post(struct iop_softc *sc, u_int32_t *mb) +{ + u_int32_t mfa; + int s; + + /* ZZZ */ + if (mb[0] >> 16 > IOP_MAX_MSG_SIZE / 4) + panic("iop_post: frame too large"); + +#ifdef I2ODEBUG + { + int i; + + printf("\niop_post\n"); + for (i = 0; i < mb[0] >> 16; i++) + printf("%4d %08x\n", i, mb[i]); + } +#endif + + s = splbio(); /* XXX */ + + /* Allocate a slot with the IOP. */ + if ((mfa = iop_inl(sc, IOP_REG_IFIFO)) == IOP_MFA_EMPTY) + if ((mfa = iop_inl(sc, IOP_REG_IFIFO)) == IOP_MFA_EMPTY) { + splx(s); + printf("%s: mfa not forthcoming\n", + sc->sc_dv.dv_xname); + return (EAGAIN); + } + +#ifdef I2ODEBUG + printf("mfa = %u\n", mfa); +#endif + + /* Perform reply buffer DMA synchronisation. XXX This is rubbish. */ + if (sc->sc_curib++ == 0) + bus_dmamap_sync(sc->sc_dmat, sc->sc_rep_dmamap, + BUS_DMASYNC_PREREAD); + + /* Copy out the message frame. */ + bus_space_write_region_4(sc->sc_iot, sc->sc_ioh, mfa, mb, mb[0] >> 16); + bus_space_barrier(sc->sc_iot, sc->sc_ioh, mfa, mb[0] >> 14 & ~3, + BUS_SPACE_BARRIER_WRITE); + + /* Post the MFA back to the IOP. */ + iop_outl(sc, IOP_REG_IFIFO, mfa); + + splx(s); + return (0); +} + +/* + * Post a message to the IOP and deal with completion. + */ +int +iop_msg_post(struct iop_softc *sc, struct iop_msg *im, void *xmb, int timo) +{ + u_int32_t *mb; + int rv, s; + + mb = xmb; + + /* Terminate the scatter/gather list chain. */ + if ((im->im_flags & IM_SGLOFFADJ) != 0) + mb[(mb[0] >> 16) - 2] |= I2O_SGL_END; + + if ((rv = iop_post(sc, mb)) != 0) + return (rv); + + if ((im->im_flags & IM_DISCARD) != 0) + iop_msg_free(sc, im); + else if ((im->im_flags & IM_POLL) != 0 && timo == 0) { + /* XXX For ofifo_init(). */ + rv = 0; + } else if ((im->im_flags & (IM_POLL | IM_WAIT)) != 0) { + if ((im->im_flags & IM_POLL) != 0) + iop_msg_poll(sc, im, timo); + else + iop_msg_wait(sc, im, timo); + + s = splbio(); + if ((im->im_flags & IM_REPLIED) != 0) { + if ((im->im_flags & IM_NOSTATUS) != 0) + rv = 0; + else if ((im->im_flags & IM_FAIL) != 0) + rv = ENXIO; + else if (im->im_reqstatus != I2O_STATUS_SUCCESS) + rv = EIO; + else + rv = 0; + } else + rv = EBUSY; + splx(s); + } else + rv = 0; + + return (rv); +} + +/* + * Spin until the specified message is replied to. + */ +void +iop_msg_poll(struct iop_softc *sc, struct iop_msg *im, int timo) +{ + u_int32_t rmfa; + int s, status; + + s = splbio(); /* XXX */ + + /* Wait for completion. */ + for (timo *= 10; timo != 0; timo--) { + if ((iop_inl(sc, IOP_REG_INTR_STATUS) & IOP_INTR_OFIFO) != 0) { + /* Double read to account for IOP bug. */ + rmfa = iop_inl(sc, IOP_REG_OFIFO); + if (rmfa == IOP_MFA_EMPTY) + rmfa = iop_inl(sc, IOP_REG_OFIFO); + if (rmfa != IOP_MFA_EMPTY) { + status = iop_handle_reply(sc, rmfa); + + /* + * Return the reply frame to the IOP's + * outbound FIFO. + */ + iop_outl(sc, IOP_REG_OFIFO, rmfa); + } + } + if ((im->im_flags & IM_REPLIED) != 0) + break; + DELAY(100); + } + + if (timo == 0) { +#ifdef I2ODEBUG + printf("%s: poll - no reply\n", sc->sc_dv.dv_xname); + if (iop_status_get(sc, 1) != 0) + printf("iop_msg_poll: unable to retrieve status\n"); + else + printf("iop_msg_poll: IOP state = %d\n", + (letoh32(sc->sc_status.segnumber) >> 16) & 0xff); +#endif + } + + splx(s); +} + +/* + * Sleep until the specified message is replied to. + */ +void +iop_msg_wait(struct iop_softc *sc, struct iop_msg *im, int timo) +{ + int s, rv; + + s = splbio(); + if ((im->im_flags & IM_REPLIED) != 0) { + splx(s); + return; + } + rv = tsleep(im, PRIBIO, "iopmsg", timo * hz / 1000); + splx(s); + +#ifdef I2ODEBUG + if (rv != 0) { + printf("iop_msg_wait: tsleep() == %d\n", rv); + if (iop_status_get(sc, 0) != 0) + printf("iop_msg_wait: unable to retrieve status\n"); + else + printf("iop_msg_wait: IOP state = %d\n", + (letoh32(sc->sc_status.segnumber) >> 16) & 0xff); + } +#endif +} + +/* + * Release an unused message frame back to the IOP's inbound fifo. + */ +void +iop_release_mfa(struct iop_softc *sc, u_int32_t mfa) +{ + + /* Use the frame to issue a no-op. */ + iop_outl(sc, mfa, I2O_VERSION_11 | (4 << 16)); + iop_outl(sc, mfa + 4, I2O_MSGFUNC(I2O_TID_IOP, I2O_UTIL_NOP)); + iop_outl(sc, mfa + 8, 0); + iop_outl(sc, mfa + 12, 0); + + iop_outl(sc, IOP_REG_IFIFO, mfa); +} + +#ifdef I2ODEBUG +/* + * Dump a reply frame header. + */ +void +iop_reply_print(struct iop_softc *sc, struct i2o_reply *rb) +{ + u_int function, detail; +#ifdef I2OVERBOSE + const char *statusstr; +#endif + + function = (letoh32(rb->msgfunc) >> 24) & 0xff; + detail = letoh16(rb->detail); + + printf("%s: reply:\n", sc->sc_dv.dv_xname); + +#ifdef I2OVERBOSE + if (rb->reqstatus < sizeof(iop_status) / sizeof(iop_status[0])) + statusstr = iop_status[rb->reqstatus]; + else + statusstr = "undefined error code"; + + printf("%s: function=0x%02x status=0x%02x (%s)\n", + sc->sc_dv.dv_xname, function, rb->reqstatus, statusstr); +#else + printf("%s: function=0x%02x status=0x%02x\n", + sc->sc_dv.dv_xname, function, rb->reqstatus); +#endif + printf("%s: detail=0x%04x ictx=0x%08x tctx=0x%08x\n", + sc->sc_dv.dv_xname, detail, letoh32(rb->msgictx), + letoh32(rb->msgtctx)); + printf("%s: tidi=%d tidt=%d flags=0x%02x\n", sc->sc_dv.dv_xname, + (letoh32(rb->msgfunc) >> 12) & 4095, letoh32(rb->msgfunc) & 4095, + (letoh32(rb->msgflags) >> 8) & 0xff); +} +#endif + +/* + * Dump a transport failure reply. + */ +void +iop_tfn_print(struct iop_softc *sc, struct i2o_fault_notify *fn) +{ + + printf("%s: WARNING: transport failure:\n", sc->sc_dv.dv_xname); + + printf("%s: ictx=0x%08x tctx=0x%08x\n", sc->sc_dv.dv_xname, + letoh32(fn->msgictx), letoh32(fn->msgtctx)); + printf("%s: failurecode=0x%02x severity=0x%02x\n", + sc->sc_dv.dv_xname, fn->failurecode, fn->severity); + printf("%s: highestver=0x%02x lowestver=0x%02x\n", + sc->sc_dv.dv_xname, fn->highestver, fn->lowestver); +} + +/* + * Translate an I2O ASCII field into a C string. + */ +void +iop_strvis(struct iop_softc *sc, const char *src, int slen, char *dst, int dlen) +{ + int hc, lc, i, nit; + + dlen--; + lc = 0; + hc = 0; + i = 0; + + /* + * DPT use NUL as a space, whereas AMI use it as a terminator. The + * spec has nothing to say about it. Since AMI fields are usually + * filled with junk after the terminator, ... + */ + nit = (letoh16(sc->sc_status.orgid) != I2O_ORG_DPT); + + while (slen-- != 0 && dlen-- != 0) { + if (nit && *src == '\0') + break; + else if (*src <= 0x20 || *src >= 0x7f) { + if (hc) + dst[i++] = ' '; + } else { + hc = 1; + dst[i++] = *src; + lc = i; + } + src++; + } + + dst[lc] = '\0'; +} + +/* + * Retrieve the DEVICE_IDENTITY parameter group from the target and dump it. + */ +int +iop_print_ident(struct iop_softc *sc, int tid) +{ + struct { + struct i2o_param_op_results pr; + struct i2o_param_read_results prr; + struct i2o_param_device_identity di; + } __attribute__ ((__packed__)) p; + char buf[32]; + int rv; + + rv = iop_param_op(sc, tid, NULL, 0, I2O_PARAM_DEVICE_IDENTITY, &p, + sizeof(p)); + if (rv != 0) + return (rv); + + iop_strvis(sc, p.di.vendorinfo, sizeof(p.di.vendorinfo), buf, + sizeof(buf)); + printf(" <%s, ", buf); + iop_strvis(sc, p.di.productinfo, sizeof(p.di.productinfo), buf, + sizeof(buf)); + printf("%s, ", buf); + iop_strvis(sc, p.di.revlevel, sizeof(p.di.revlevel), buf, sizeof(buf)); + printf("%s>", buf); + + return (0); +} + +/* + * Claim or unclaim the specified TID. + */ +int +iop_util_claim(struct iop_softc *sc, struct iop_initiator *ii, int release, + int flags) +{ + struct iop_msg *im; + struct i2o_util_claim mf; + int rv, func; + + func = release ? I2O_UTIL_CLAIM_RELEASE : I2O_UTIL_CLAIM; + im = iop_msg_alloc(sc, ii, IM_WAIT); + + /* We can use the same structure, as they're identical. */ + mf.msgflags = I2O_MSGFLAGS(i2o_util_claim); + mf.msgfunc = I2O_MSGFUNC(ii->ii_tid, func); + mf.msgictx = ii->ii_ictx; + mf.msgtctx = im->im_tctx; + mf.flags = flags; + + rv = iop_msg_post(sc, im, &mf, 5000); + iop_msg_free(sc, im); + return (rv); +} + +/* + * Perform an abort. + */ +int iop_util_abort(struct iop_softc *sc, struct iop_initiator *ii, int func, + int tctxabort, int flags) +{ + struct iop_msg *im; + struct i2o_util_abort mf; + int rv; + + im = iop_msg_alloc(sc, ii, IM_WAIT); + + mf.msgflags = I2O_MSGFLAGS(i2o_util_abort); + mf.msgfunc = I2O_MSGFUNC(ii->ii_tid, I2O_UTIL_ABORT); + mf.msgictx = ii->ii_ictx; + mf.msgtctx = im->im_tctx; + mf.flags = (func << 24) | flags; + mf.tctxabort = tctxabort; + + rv = iop_msg_post(sc, im, &mf, 5000); + iop_msg_free(sc, im); + return (rv); +} + +/* + * Enable or disable reception of events for the specified device. + */ +int iop_util_eventreg(struct iop_softc *sc, struct iop_initiator *ii, int mask) +{ + struct iop_msg *im; + struct i2o_util_event_register mf; + + im = iop_msg_alloc(sc, ii, 0); + + mf.msgflags = I2O_MSGFLAGS(i2o_util_event_register); + mf.msgfunc = I2O_MSGFUNC(ii->ii_tid, I2O_UTIL_EVENT_REGISTER); + mf.msgictx = ii->ii_ictx; + mf.msgtctx = im->im_tctx; + mf.eventmask = mask; + + /* This message is replied to only when events are signalled. */ + return (iop_msg_post(sc, im, &mf, 0)); +} + +int +iopopen(dev_t dev, int flag, int mode, struct proc *p) +{ + struct iop_softc *sc; + + if (!(sc = (struct iop_softc *)device_lookup(&iop_cd, minor(dev)))) + return (ENXIO); + if ((sc->sc_flags & IOP_ONLINE) == 0) + return (ENXIO); + if ((sc->sc_flags & IOP_OPEN) != 0) + return (EBUSY); + sc->sc_flags |= IOP_OPEN; + + sc->sc_ptb = malloc(IOP_MAX_XFER * IOP_MAX_MSG_XFERS, M_DEVBUF, + M_WAITOK); + if (sc->sc_ptb == NULL) { + sc->sc_flags ^= IOP_OPEN; + return (ENOMEM); + } + + return (0); +} + +int +iopclose(dev_t dev, int flag, int mode, struct proc *p) +{ + struct iop_softc *sc; + + sc = (struct iop_softc *)device_lookup(&iop_cd, minor(dev)); /* XXX */ + free(sc->sc_ptb, M_DEVBUF); + sc->sc_flags &= ~IOP_OPEN; + return (0); +} + +int +iopioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) +{ + struct iop_softc *sc; + struct iovec *iov; + int rv, i; + + if (securelevel >= 2) + return (EPERM); + + sc = (struct iop_softc *)device_lookup(&iop_cd, minor(dev)); /* XXX */ + + switch (cmd) { + case IOPIOCPT: + return (iop_passthrough(sc, (struct ioppt *)data)); + + case IOPIOCGSTATUS: + iov = (struct iovec *)data; + i = sizeof(struct i2o_status); + if (i > iov->iov_len) + i = iov->iov_len; + else + iov->iov_len = i; + if ((rv = iop_status_get(sc, 0)) == 0) + rv = copyout(&sc->sc_status, iov->iov_base, i); + return (rv); + + case IOPIOCGLCT: + case IOPIOCGTIDMAP: + case IOPIOCRECONFIG: + break; + + default: +#if defined(DIAGNOSTIC) || defined(I2ODEBUG) + printf("%s: unknown ioctl %lx\n", sc->sc_dv.dv_xname, cmd); +#endif + return (ENOTTY); + } + + if ((rv = lockmgr(&sc->sc_conflock, LK_SHARED, NULL, p)) != 0) + return (rv); + + switch (cmd) { + case IOPIOCGLCT: + iov = (struct iovec *)data; + i = letoh16(sc->sc_lct->tablesize) << 2; + if (i > iov->iov_len) + i = iov->iov_len; + else + iov->iov_len = i; + rv = copyout(sc->sc_lct, iov->iov_base, i); + break; + + case IOPIOCRECONFIG: + rv = iop_reconfigure(sc, 0); + break; + + case IOPIOCGTIDMAP: + iov = (struct iovec *)data; + i = sizeof(struct iop_tidmap) * sc->sc_nlctent; + if (i > iov->iov_len) + i = iov->iov_len; + else + iov->iov_len = i; + rv = copyout(sc->sc_tidmap, iov->iov_base, i); + break; + } + + lockmgr(&sc->sc_conflock, LK_RELEASE, NULL, p); + return (rv); +} + +int +iop_passthrough(struct iop_softc *sc, struct ioppt *pt) +{ + struct iop_msg *im; + struct i2o_msg *mf; + struct ioppt_buf *ptb; + int rv, i, mapped; + void *buf; + + mf = NULL; + im = NULL; + mapped = 1; + + if (pt->pt_msglen > IOP_MAX_MSG_SIZE || + pt->pt_msglen > (letoh16(sc->sc_status.inboundmframesize) << 2) || + pt->pt_msglen < sizeof(struct i2o_msg) || + pt->pt_nbufs > IOP_MAX_MSG_XFERS || + pt->pt_nbufs < 0 || pt->pt_replylen < 0 || + pt->pt_timo < 1000 || pt->pt_timo > 5*60*1000) + return (EINVAL); + + for (i = 0; i < pt->pt_nbufs; i++) + if (pt->pt_bufs[i].ptb_datalen > IOP_MAX_XFER) { + rv = ENOMEM; + goto bad; + } + + mf = malloc(IOP_MAX_MSG_SIZE, M_DEVBUF, M_WAITOK); + if (mf == NULL) + return (ENOMEM); + + if ((rv = copyin(pt->pt_msg, mf, pt->pt_msglen)) != 0) + goto bad; + + im = iop_msg_alloc(sc, NULL, IM_WAIT | IM_NOSTATUS); + im->im_rb = (struct i2o_reply *)mf; + mf->msgictx = IOP_ICTX; + mf->msgtctx = im->im_tctx; + + for (i = 0; i < pt->pt_nbufs; i++) { + ptb = &pt->pt_bufs[i]; + buf = sc->sc_ptb + i * IOP_MAX_XFER; + + if ((u_int)ptb->ptb_datalen > IOP_MAX_XFER) { + rv = EINVAL; + goto bad; + } + + if (ptb->ptb_out != 0) { + rv = copyin(ptb->ptb_data, buf, ptb->ptb_datalen); + if (rv != 0) + goto bad; + } + + rv = iop_msg_map(sc, im, (u_int32_t *)mf, buf, + ptb->ptb_datalen, ptb->ptb_out != 0); + if (rv != 0) + goto bad; + mapped = 1; + } + + if ((rv = iop_msg_post(sc, im, mf, pt->pt_timo)) != 0) + goto bad; + + i = (letoh32(im->im_rb->msgflags) >> 14) & ~3; + if (i > IOP_MAX_MSG_SIZE) + i = IOP_MAX_MSG_SIZE; + if (i > pt->pt_replylen) + i = pt->pt_replylen; + if ((rv = copyout(im->im_rb, pt->pt_reply, i)) != 0) + goto bad; + + iop_msg_unmap(sc, im); + mapped = 0; + + for (i = 0; i < pt->pt_nbufs; i++) { + ptb = &pt->pt_bufs[i]; + if (ptb->ptb_out != 0) + continue; + buf = sc->sc_ptb + i * IOP_MAX_XFER; + rv = copyout(buf, ptb->ptb_data, ptb->ptb_datalen); + if (rv != 0) + break; + } + + bad: + if (mapped != 0) + iop_msg_unmap(sc, im); + if (im != NULL) + iop_msg_free(sc, im); + if (mf != NULL) + free(mf, M_DEVBUF); + return (rv); +} |