diff options
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/ic/ami.c | 402 |
1 files changed, 398 insertions, 4 deletions
diff --git a/sys/dev/ic/ami.c b/sys/dev/ic/ami.c index 8cbf5b663c2..43a658d7e9b 100644 --- a/sys/dev/ic/ami.c +++ b/sys/dev/ic/ami.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ami.c,v 1.28 2005/02/03 17:47:27 mickey Exp $ */ +/* $OpenBSD: ami.c,v 1.29 2005/03/29 22:20:38 marco Exp $ */ /* * Copyright (c) 2001 Michael Shalayeff @@ -44,11 +44,12 @@ * Theo de Raadt. */ -/* #define AMI_DEBUG */ +#define AMI_DEBUG #include <sys/param.h> #include <sys/systm.h> #include <sys/buf.h> +#include <sys/ioctl.h> #include <sys/device.h> #include <sys/kernel.h> #include <sys/malloc.h> @@ -62,17 +63,22 @@ #include <dev/ic/amireg.h> #include <dev/ic/amivar.h> +#include <dev/biovar.h> +#include "bio.h" + #ifdef AMI_DEBUG #define AMI_DPRINTF(m,a) if (ami_debug & (m)) printf a #define AMI_D_CMD 0x0001 #define AMI_D_INTR 0x0002 #define AMI_D_MISC 0x0004 #define AMI_D_DMA 0x0008 +#define AMI_D_IOCTL 0x0010 int ami_debug = 0 - | AMI_D_CMD - | AMI_D_INTR +/* | AMI_D_CMD */ +/* | AMI_D_INTR */ /* | AMI_D_MISC */ /* | AMI_D_DMA */ + | AMI_D_IOCTL ; #else #define AMI_DPRINTF(m,a) /* m, a */ @@ -126,6 +132,14 @@ int ami_done(struct ami_softc *sc, int idx); void ami_copy_internal_data(struct scsi_xfer *xs, void *v, size_t size); int ami_inquire(struct ami_softc *sc, u_int8_t op); +#if NBIO > 0 +int ami_ioctl(struct device *, u_long, caddr_t); +int ami_ioctl_alarm(struct ami_softc *, bioc_alarm *); +int ami_ioctl_startstop( struct ami_softc *, bioc_startstop *); +int ami_ioctl_status( struct ami_softc *, bioc_status *); +int ami_ioctl_passthru(struct ami_softc *, bioc_scsicmd *); +#endif + struct ami_ccb * ami_get_ccb(sc) struct ami_softc *sc; @@ -531,7 +545,14 @@ ami_attach(sc) printf("%s: firmware buggy, limiting access to first logical " "disk\n", sc->sc_dev.dv_xname); +#if NBIO > 0 + if (bio_register(&sc->sc_dev, ami_ioctl) != 0) + printf("%s: controller registration failed", + sc->sc_dev.dv_xname); +#endif + config_found(&sc->sc_dev, &sc->sc_link, scsiprint); + #if 0 rsc = malloc(sizeof(struct ami_rawsoftc) * sc->sc_channels, M_DEVBUF, M_NOWAIT); @@ -1434,6 +1455,379 @@ ami_intr(v) return (rv); } +#if NBIO > 0 +int +ami_ioctl(dev, cmd, addr) + struct device *dev; + u_long cmd; + caddr_t addr; +{ + int error = 0; + struct ami_softc *sc = (struct ami_softc *)dev; + + if (sc->sc_quirks & AMI_BROKEN) + return ENODEV; /* can't do this to broken device for now */ + + switch (cmd) { + case BIOCPING: + ((bioc_ping *)addr)->x++; + + AMI_DPRINTF(AMI_D_IOCTL, ("%s: biocping: %x\n", + sc->sc_dev.dv_xname, ((bioc_ping *)addr)->x)); + break; + + case BIOCCAPABILITIES: + ((bioc_capabilities *)addr)->ioctls = + BIOC_ALARM | BIOC_PING | BIOC_SCSICMD | BIOC_STARTSTOP | + BIOC_STATUS; + + ((bioc_capabilities *)addr)->raid_types = + BIOC_RAID0 | BIOC_RAID1 | BIOC_RAID5 | + BIOC_RAID10 | BIOC_RAID50; + + AMI_DPRINTF(AMI_D_IOCTL, ("%s: bioccapabilities: ioctls: " + "%016llx raid_types: %08lx\n", + sc->sc_dev.dv_xname, + ((bioc_capabilities *)addr)->ioctls, + ((bioc_capabilities *)addr)->raid_types)); + break; + + case BIOCALARM: + error = ami_ioctl_alarm(sc, (bioc_alarm *)addr); + break; + + case BIOCSTARTSTOP: + AMI_DPRINTF(AMI_D_IOCTL, ("start stop unit\n")); + error = ami_ioctl_startstop(sc, (bioc_startstop *)addr); + break; + + case BIOCSTATUS: + AMI_DPRINTF(AMI_D_IOCTL, ("status\n")); + error = ami_ioctl_status(sc, (bioc_status *)addr); + break; + + case BIOCSCSICMD: + AMI_DPRINTF(AMI_D_IOCTL, ("scsi cmd\n")); + error = ami_ioctl_passthru(sc, (bioc_scsicmd *)addr); + break; + + default: + AMI_DPRINTF(AMI_D_IOCTL, ("%s: invalid ioctl\n", + sc->sc_dev.dv_xname)); + error = EINVAL; + } + + return (error); +} + +int +ami_ioctl_alarm(sc, ra) + struct ami_softc *sc; + bioc_alarm *ra; +{ + int error = 0; + struct ami_ccb *ccb; + struct ami_iocmd *cmd; + ami_lock_t lock; + void *idata; + bus_dmamap_t idatamap; + bus_dma_segment_t idataseg[1]; + paddr_t pa; + u_int8_t *p; + + + if (!(idata = ami_allocmem(sc->dmat, &idatamap, idataseg, + NBPG, 1, "ioctl data"))) { + ami_freemem(sc->dmat, &idatamap, idataseg, + NBPG, 1, "ioctl data"); + return ENOMEM; + } + + pa = idataseg[0].ds_addr; + p = idata; + + lock = AMI_LOCK_AMI(sc); + + ccb = ami_get_ccb(sc); + ccb->ccb_data = NULL; + cmd = ccb->ccb_cmd; + + cmd->acc_cmd = AMI_ALARM; + cmd->acc_io.aio_channel = 0; + cmd->acc_io.aio_param = 0; + cmd->acc_io.aio_data = htole32(pa); + + switch(ra->opcode) { + case BIOCSALARM_DISABLE: + *p = AMI_ALARM_OFF; + break; + + case BIOCSALARM_ENABLE: + *p = AMI_ALARM_ON; + break; + + case BIOCSALARM_SILENCE: + *p = AMI_ALARM_QUIET; + break; + + case BIOCGALARM_STATE: + *p = AMI_ALARM_GET; + break; + + case BIOCSALARM_TEST: + *p = AMI_ALARM_TEST; + break; + + default: + AMI_DPRINTF(AMI_D_IOCTL, ("%s: biocalarm invalid opcode %x\n", + sc->sc_dev.dv_xname, ra->opcode)); + ami_put_ccb(ccb); + AMI_UNLOCK_AMI(sc, lock); + return EINVAL; + } + + AMI_DPRINTF(AMI_D_IOCTL, ("%s: biocalarm: in: %x ", + sc->sc_dev.dv_xname, *p)); + + + if (ami_cmd(ccb, 0, 1) == 0) { + AMI_DPRINTF(AMI_D_IOCTL, ("out %x\n", *p)); + if (ra->opcode == BIOCGALARM_STATE) + ra->state = *p; + else + ra->state = 0; + } + else { + AMI_DPRINTF(AMI_D_IOCTL, ("failed\n")); + error = EINVAL; + } + + AMI_UNLOCK_AMI(sc, lock); + + ami_freemem(sc->dmat, &idatamap, idataseg, NBPG, 1, "ioctl data"); + + return (error); +} + +int +ami_ioctl_startstop(sc, bs) + struct ami_softc *sc; + bioc_startstop *bs; +{ + int error = 0; + struct ami_ccb *ccb; + struct ami_iocmd *cmd; + ami_lock_t lock; + + lock = AMI_LOCK_AMI(sc); + + ccb = ami_get_ccb(sc); + ccb->ccb_data = NULL; + cmd = ccb->ccb_cmd; + + AMI_DPRINTF(AMI_D_IOCTL, ("start/stop %d unit %d %d\n", + bs->opcode, bs->channel, bs->target)); + + if (bs->opcode == BIOCSUNIT_START) + cmd->acc_cmd = AMI_STARTU; + else if (bs->opcode == BIOCSUNIT_STOP) + cmd->acc_cmd = AMI_STOPU; + else + return EINVAL; + + /* FIXME test if channel and target are in range */ + cmd->acc_io.aio_channel = bs->channel; + cmd->acc_io.aio_param = bs->target; + cmd->acc_io.aio_pad[0] = AMI_STARTU_SYNC; + cmd->acc_io.aio_data = NULL; + + if (ami_cmd(ccb, 0, 1) == 0) { + AMI_DPRINTF(AMI_D_IOCTL, ("%s\n", + bs->opcode == BIOCSUNIT_START ? "started" : "stopped")); + } + else { + AMI_DPRINTF(AMI_D_IOCTL, ("failed\n")); + error = EINVAL; + } + + AMI_UNLOCK_AMI(sc, lock); + + return (error); +} + +int +ami_ioctl_status(sc, bs) + struct ami_softc *sc; + bioc_status *bs; +{ + int error = 0; + struct ami_ccb *ccb; + struct ami_iocmd *cmd; + ami_lock_t lock; + void *idata; + bus_dmamap_t idatamap; + bus_dma_segment_t idataseg[1]; + paddr_t pa; + u_int8_t *p; + + if (!(idata = ami_allocmem(sc->dmat, &idatamap, idataseg, + NBPG, 1, "ioctl data"))) { + ami_freemem(sc->dmat, &idatamap, idataseg, + NBPG, 1, "ioctl data"); + return ENOMEM; + } + + pa = idataseg[0].ds_addr; + p = idata; + + lock = AMI_LOCK_AMI(sc); + + ccb = ami_get_ccb(sc); + ccb->ccb_data = NULL; + cmd = ccb->ccb_cmd; + + cmd->acc_cmd = AMI_FCOP; + cmd->acc_io.aio_channel = AMI_FC_EINQ3; + cmd->acc_io.aio_param = AMI_FC_EINQ3_SOLICITED_FULL; + cmd->acc_io.aio_data = htole32(pa); + + AMI_DPRINTF(AMI_D_IOCTL, ("status %d\n", bs->opcode)); + + if (ami_cmd(ccb, 0, 1) == 0) { + AMI_DPRINTF(AMI_D_IOCTL, ("success\n")); + } + else { + AMI_DPRINTF(AMI_D_IOCTL, ("failed\n")); + error = EINVAL; + } + + AMI_UNLOCK_AMI(sc, lock); + + ami_freemem(sc->dmat, &idatamap, idataseg, NBPG, 1, "ioctl data"); + + return (error); +} + +int +ami_ioctl_passthru(sc, bp) + struct ami_softc *sc; + bioc_scsicmd *bp; +{ + int error = 0; + struct ami_ccb *ccb; + struct ami_iocmd *cmd; + struct ami_passthrough *ps; + ami_lock_t lock; + void *idata; + bus_dmamap_t idatamap; + bus_dma_segment_t idataseg[1]; + paddr_t pa; + u_int8_t i = 0; + + AMI_DPRINTF(AMI_D_IOCTL, ("in passthrough\n")); + + /* FIXME: validate channel/target pair, or let the firmware bomb it? + */ + + if (bp->cdblen > BIOC_MAX_CDB) + return (EINVAL); + + if (bp->direction == BIOC_DIRIN && + (bp->datalen == 0 || bp->data == NULL)) + /* if userland expects data give us a len and a pointer */ + return (EINVAL); + + if (bp->datalen > 1024) + return (EINVAL); /* cap at 1k for now */ + + if (!(idata = ami_allocmem(sc->dmat, &idatamap, idataseg, + NBPG, 1, "ioctl data"))) { + ami_freemem(sc->dmat, &idatamap, idataseg, + NBPG, 1, "ioctl data"); + return (ENOMEM); + } + + pa = idataseg[0].ds_addr; + ps = idata; + + lock = AMI_LOCK_AMI(sc); + + ccb = ami_get_ccb(sc); + ccb->ccb_data = NULL; + cmd = ccb->ccb_cmd; + + cmd->acc_cmd = AMI_PASSTHRU; + cmd->acc_passthru.apt_data = htole32(pa); + + memset(ps, 0, sizeof *ps); + + ps->apt_channel = bp->channel; + ps->apt_target = bp->target; + ps->apt_ncdb = bp->cdblen; + ps->apt_nsense = BIOC_MAX_SENSE; /* do not let userland dictate this */ + memcpy(&ps->apt_cdb[0], &bp->cdb[0], bp->cdblen); + + ps->apt_data = htole32(pa + sizeof *ps); + ps->apt_datalen = bp->datalen; + + if (bp->direction == BIOC_DIROUT) { + /* userland sent us some data */ + copyin(bp->data, idata + sizeof *ps, ps->apt_datalen); + } + +#ifdef AMI_DEBUG + AMI_DPRINTF(AMI_D_IOCTL, ("%s: ps->apt_channel: %x, ps->apt_target: %x " + "ps->apt_cdblen: %x ps->apt_data: %x ps->apt_datalen: %x\n%s: cdb: " + , sc->sc_dev.dv_xname, ps->apt_channel, ps->apt_target, + ps->apt_ncdb, ps->apt_data, ps->apt_datalen, sc->sc_dev.dv_xname)); + + for (i = 0; i < ps->apt_ncdb; i++) { + printf("%0x ", ps->apt_cdb[i]); + } + printf("\n"); +#endif /* AMI_DEBUG */ + + if (ami_cmd(ccb, 0, 1) == 0) { + AMI_DPRINTF(AMI_D_IOCTL, ("cdb issued\n")); + if (bp->direction == BIOC_DIRIN) { + /* userland expects data */ + bp->datalen = ps->apt_datalen; + AMI_DPRINTF(AMI_D_IOCTL, ("%s: passthrough %x\n", + sc->sc_dev.dv_xname, ps->apt_datalen)); + copyout(idata + sizeof *ps, bp->data, ps->apt_datalen); + } + } + else { + /* copy sense data back to user space */ + memcpy(&bp->sensebuf[0], &ps->apt_sense[0], ps->apt_nsense); + bp->senselen = ps->apt_nsense; + bp->status = 1; /* this needs to be checked in + * userland since error can't + * be set. Setting it prevents + * it from being coppied back to + * userland */ + +#ifdef AMI_DEBUG + AMI_DPRINTF(AMI_D_IOCTL, ("%s: passthrough failed %x %x\n%s: ", + sc->sc_dev.dv_xname, bp->status, bp->senselen, + sc->sc_dev.dv_xname)); + + for (i = 0; i < bp->senselen; i++) { + bp->sensebuf[i] = ps->apt_sense[i]; + printf("%0x ", bp->sensebuf[i]); + } + printf("\n"); +#endif /* AMI_DEBUG */ + } + + AMI_UNLOCK_AMI(sc, lock); + + ami_freemem(sc->dmat, &idatamap, idataseg, NBPG, 1, "ioctl data"); + + return (error); +} +#endif /* NBIO > 0 */ + #ifdef AMI_DEBUG void ami_print_mbox(mbox) |