/* $OpenBSD: mfi.c,v 1.12 2006/04/16 17:15:36 marco Exp $ */ /* * Copyright (c) 2006 Marco Peereboom * * 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 "bio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if NBIO > 0 #include #endif #ifdef MFI_DEBUG uint32_t mfi_debug = 0 | MFI_D_CMD | MFI_D_INTR | MFI_D_MISC /* | MFI_D_DMA */ /* | MFI_D_IOCTL */ /* | MFI_D_RW */ ; #endif struct cfdriver mfi_cd = { NULL, "mfi", DV_DULL }; int mfi_scsi_cmd(struct scsi_xfer *); int mfi_scsi_ioctl(struct scsi_link *, u_long, caddr_t, int, struct proc *); void mfiminphys(struct buf *bp); struct scsi_adapter mfi_switch = { mfi_scsi_cmd, mfiminphys, 0, 0, mfi_scsi_ioctl }; struct scsi_device mfi_dev = { NULL, NULL, NULL, NULL }; struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); void mfi_put_ccb(struct mfi_ccb *); u_int32_t mfi_read(struct mfi_softc *, bus_size_t); void mfi_write(struct mfi_softc *, bus_size_t, u_int32_t); struct mfi_mem *mfi_allocmem(struct mfi_softc *, size_t); void mfi_freemem(struct mfi_softc *, struct mfi_mem *); int mfi_transition_firmware(struct mfi_softc *); struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; int s; s = splbio(); ccb = TAILQ_LAST(&sc->sc_ccb_freeq, mfi_queue_head); if (ccb) { TAILQ_REMOVE(&sc->sc_ccb_freeq, ccb, ccb_link); ccb->ccb_state = MFI_CCB_READY; } splx(s); return (ccb); } void mfi_put_ccb(struct mfi_ccb *ccb) { struct mfi_softc *sc = ccb->ccb_sc; int s; s = splbio(); ccb->ccb_state = MFI_CCB_FREE; ccb->ccb_xs = NULL; ccb->ccb_flags = 0; ccb->ccb_done = NULL; TAILQ_INSERT_TAIL(&sc->sc_ccb_freeq, ccb, ccb_link); splx(s); } u_int32_t mfi_read(struct mfi_softc *sc, bus_size_t r) { u_int32_t rv; bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4, BUS_SPACE_BARRIER_READ); rv = bus_space_read_4(sc->sc_iot, sc->sc_ioh, r); DNPRINTF(MFI_D_RW, "mr 0x%x 0x08%x ", r, rv); return (rv); } void mfi_write(struct mfi_softc *sc, bus_size_t r, u_int32_t v) { DNPRINTF(MFI_D_RW, "mw 0x%x 0x%08x", r, v); bus_space_write_4(sc->sc_iot, sc->sc_ioh, r, v); bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4, BUS_SPACE_BARRIER_WRITE); } struct mfi_mem * mfi_allocmem(struct mfi_softc *sc, size_t size) { struct mfi_mem *mm; int nsegs; mm = malloc(sizeof(struct mfi_mem), M_DEVBUF, M_NOWAIT); if (mm == NULL) return (NULL); memset(mm, 0, sizeof(struct mfi_mem)); mm->am_size = size; if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &mm->am_map) != 0) goto amfree; if (bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &mm->am_seg, 1, &nsegs, BUS_DMA_NOWAIT) != 0) goto destroy; if (bus_dmamem_map(sc->sc_dmat, &mm->am_seg, nsegs, size, &mm->am_kva, BUS_DMA_NOWAIT) != 0) goto free; if (bus_dmamap_load(sc->sc_dmat, mm->am_map, mm->am_kva, size, NULL, BUS_DMA_NOWAIT) != 0) goto unmap; memset(mm->am_kva, 0, size); return (mm); unmap: bus_dmamem_unmap(sc->sc_dmat, mm->am_kva, size); free: bus_dmamem_free(sc->sc_dmat, &mm->am_seg, 1); destroy: bus_dmamap_destroy(sc->sc_dmat, mm->am_map); amfree: free(mm, M_DEVBUF); return (NULL); } void mfi_freemem(struct mfi_softc *sc, struct mfi_mem *mm) { bus_dmamap_unload(sc->sc_dmat, mm->am_map); bus_dmamem_unmap(sc->sc_dmat, mm->am_kva, mm->am_size); bus_dmamem_free(sc->sc_dmat, &mm->am_seg, 1); bus_dmamap_destroy(sc->sc_dmat, mm->am_map); free(mm, M_DEVBUF); } int mfi_transition_firmware(struct mfi_softc *sc) { int32_t fw_state, cur_state; int max_wait, i; fw_state = mfi_read(sc, MFI_OMSG0) & MFI_STATE_MASK; DNPRINTF(MFI_D_CMD, "%s: mfi_transition_mfi: %#x\n", DEVNAME(sc), fw_state); while (fw_state != MFI_STATE_READY) { DNPRINTF(MFI_D_MISC, "%s: waiting for firmware to become ready\n", DEVNAME(sc)); cur_state = fw_state; switch (fw_state) { case MFI_STATE_FAULT: printf("%s: firmware fault\n", DEVNAME(sc)); return (1); case MFI_STATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_INIT_CLEAR_HANDSHAKE); max_wait = 2; break; case MFI_STATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_INIT_READY); max_wait = 10; break; case MFI_STATE_UNDEFINED: case MFI_STATE_BB_INIT: max_wait = 2; break; case MFI_STATE_FW_INIT: case MFI_STATE_DEVICE_SCAN: case MFI_STATE_FLUSH_CACHE: max_wait = 20; break; default: printf("%s: unknown firmware state %d\n", DEVNAME(sc), fw_state); return (1); } for (i = 0; i < (max_wait * 10); i++) { fw_state = mfi_read(sc, MFI_OMSG0) & MFI_STATE_MASK; if (fw_state == cur_state) DELAY(100000); else break; } if (fw_state == cur_state) { printf("%s: firmware stuck in state %#x\n", fw_state, DEVNAME(sc)); return (1); } } return (0); } void mfiminphys(struct buf *bp) { #define MFI_MAXFER 4096 if (bp->b_bcount > MFI_MAXFER) bp->b_bcount = MFI_MAXFER; minphys(bp); } int mfi_attach(struct mfi_softc *sc) { uint32_t status; if (mfi_transition_firmware(sc)) return (1); status = mfi_read(sc, MFI_OMSG0); sc->sc_max_cmds = status & MFI_STATE_MAXCMD_MASK; sc->sc_max_sgl = (status & MFI_STATE_MAXSGL_MASK) >> 16; DNPRINTF(MFI_D_MISC, "%s: max commands: %u, max sgl: %u\n", DEVNAME(sc), sc->sc_max_cmds, sc->sc_max_sgl); /* consumer/producer and reply queue memory */ sc->sc_pcq = mfi_allocmem(sc, (sizeof(uint32_t) * sc->sc_max_cmds) + sizeof(struct mfi_prod_cons)); if (sc->sc_pcq == NULL) { printf("%s: unable to allocate reply queue memory\n", DEVNAME(sc)); return (1); } /* enable interrupts */ mfi_write(sc, MFI_OMSK, MFI_ENABLE_INTR); return (0); } int mfi_intr(void *arg) { struct mfi_softc *sc = arg; struct mfi_prod_cons *pcq; uint32_t status, producer, consumer, ctx; int s, claimed = 0; status = mfi_read(sc, MFI_OSTS); if ((status & MFI_OSTS_INTR_VALID) == 0) return (claimed); /* write status back to acknowledge interrupt */ mfi_write(sc, MFI_OSTS, status); pcq = (struct mfi_prod_cons *)sc->sc_pcq->am_kva; producer = pcq->mpc_producer; consumer = pcq->mpc_consumer; s = splbio(); while (consumer != producer) { ctx = pcq->mpc_reply_q[consumer]; pcq->mpc_reply_q[consumer] = MFI_INVALID_CTX; if (ctx == MFI_INVALID_CTX) printf("%s: invalid context, p: %d c: %d\n", DEVNAME(sc), producer, consumer); else { /* remove from queue and call scsi_done */ claimed = 1; } consumer++; if (consumer == sc->sc_max_cmds) consumer = 0; } splx(s); pcq->mpc_consumer = consumer; return (claimed); } int mfi_scsi_ioctl(struct scsi_link *link, u_long cmd, caddr_t addr, int flag, struct proc *p) { #if 0 struct ami_softc *sc = (struct ami_softc *)link->adapter_softc; if (sc->sc_ioctl) return (sc->sc_ioctl(link->adapter_softc, cmd, addr)); else return (ENOTTY); #endif return (ENOTTY); } int mfi_scsi_cmd(struct scsi_xfer *xs) { #if 0 struct scsi_link *link = xs->sc_link; struct ami_softc *sc = link->adapter_softc; struct device *dev = link->device_softc; #endif return (0); }