/* $NetBSD: mfi.c,v 1.80 2024/02/02 22:26:58 andvar Exp $ */
/* $OpenBSD: mfi.c,v 1.66 2006/11/28 23:59:45 dlg Exp $ */

/*
* Copyright (c) 2012 Manuel Bouyer.
*
* 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.
*
* 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.
*/

/*
* Copyright (c) 2006 Marco Peereboom <[email protected]>
*
* 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.
*/

/*-
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*            Copyright 1994-2009 The FreeBSD Project.
*            All rights reserved.
*
* 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.
*
*    THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT``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 FREEBSD PROJECT 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.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies,either expressed or implied, of the FreeBSD Project.
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: mfi.c,v 1.80 2024/02/02 22:26:58 andvar Exp $");

#include "bio.h"

#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>
#include <sys/proc.h>
#include <sys/cpu.h>
#include <sys/conf.h>
#include <sys/kauth.h>

#include <uvm/uvm_param.h>

#include <sys/bus.h>

#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsi_spc.h>
#include <dev/scsipi/scsipi_disk.h>
#include <dev/scsipi/scsi_disk.h>
#include <dev/scsipi/scsiconf.h>

#include <dev/ic/mfireg.h>
#include <dev/ic/mfivar.h>
#include <dev/ic/mfiio.h>

#if NBIO > 0
#include <dev/biovar.h>
#endif /* NBIO > 0 */

#include "ioconf.h"

#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 */
/*                  | MFI_D_MEM */
/*                  | MFI_D_CCB */
/*                  | MFI_D_SYNC */
               ;
#endif

static void             mfi_scsipi_request(struct scsipi_channel *,
                               scsipi_adapter_req_t, void *);
static void             mfiminphys(struct buf *bp);

static struct mfi_ccb   *mfi_get_ccb(struct mfi_softc *);
static void             mfi_put_ccb(struct mfi_ccb *);
static int              mfi_init_ccb(struct mfi_softc *);

static struct mfi_mem   *mfi_allocmem(struct mfi_softc *, size_t);
static void             mfi_freemem(struct mfi_softc *, struct mfi_mem **);

static int              mfi_transition_firmware(struct mfi_softc *);
static int              mfi_initialize_firmware(struct mfi_softc *);
static int              mfi_get_info(struct mfi_softc *);
static int              mfi_get_bbu(struct mfi_softc *,
                           struct mfi_bbu_status *);
/* return codes for mfi_get_bbu */
#define MFI_BBU_GOOD    0
#define MFI_BBU_BAD     1
#define MFI_BBU_UNKNOWN 2
static uint32_t         mfi_read(struct mfi_softc *, bus_size_t);
static void             mfi_write(struct mfi_softc *, bus_size_t, uint32_t);
static int              mfi_poll(struct mfi_ccb *);
static int              mfi_create_sgl(struct mfi_ccb *, int);

/* commands */
static int              mfi_scsi_ld(struct mfi_ccb *, struct scsipi_xfer *);
static int              mfi_scsi_ld_io(struct mfi_ccb *, struct scsipi_xfer *,
                               uint64_t, uint32_t);
static void             mfi_scsi_ld_done(struct mfi_ccb *);
static void             mfi_scsi_xs_done(struct mfi_ccb *, int, int);
static int              mfi_mgmt_internal(struct mfi_softc *, uint32_t,
                           uint32_t, uint32_t, void *, const union mfi_mbox *,
                           bool);
static int              mfi_mgmt(struct mfi_ccb *,struct scsipi_xfer *,
                           uint32_t, uint32_t, uint32_t, void *,
                           const union mfi_mbox *);
static void             mfi_mgmt_done(struct mfi_ccb *);

#if NBIO > 0
static int              mfi_ioctl(device_t, u_long, void *);
static int              mfi_ioctl_inq(struct mfi_softc *, struct bioc_inq *);
static int              mfi_ioctl_vol(struct mfi_softc *, struct bioc_vol *);
static int              mfi_ioctl_disk(struct mfi_softc *, struct bioc_disk *);
static int              mfi_ioctl_alarm(struct mfi_softc *,
                               struct bioc_alarm *);
static int              mfi_ioctl_blink(struct mfi_softc *sc,
                               struct bioc_blink *);
static int              mfi_ioctl_setstate(struct mfi_softc *,
                               struct bioc_setstate *);
static int              mfi_bio_hs(struct mfi_softc *, int, int, void *);
static int              mfi_create_sensors(struct mfi_softc *);
static int              mfi_destroy_sensors(struct mfi_softc *);
static void             mfi_sensor_refresh(struct sysmon_envsys *,
                               envsys_data_t *);
#endif /* NBIO > 0 */
static bool             mfi_shutdown(device_t, int);
static bool             mfi_suspend(device_t, const pmf_qual_t *);
static bool             mfi_resume(device_t, const pmf_qual_t *);

static dev_type_open(mfifopen);
static dev_type_close(mfifclose);
static dev_type_ioctl(mfifioctl);
const struct cdevsw mfi_cdevsw = {
       .d_open = mfifopen,
       .d_close = mfifclose,
       .d_read = noread,
       .d_write = nowrite,
       .d_ioctl = mfifioctl,
       .d_stop = nostop,
       .d_tty = notty,
       .d_poll = nopoll,
       .d_mmap = nommap,
       .d_kqfilter = nokqfilter,
       .d_discard = nodiscard,
       .d_flag = D_OTHER
};

static uint32_t         mfi_xscale_fw_state(struct mfi_softc *sc);
static void             mfi_xscale_intr_ena(struct mfi_softc *sc);
static void             mfi_xscale_intr_dis(struct mfi_softc *sc);
static int              mfi_xscale_intr(struct mfi_softc *sc);
static void             mfi_xscale_post(struct mfi_softc *sc, struct mfi_ccb *ccb);

static const struct mfi_iop_ops mfi_iop_xscale = {
       mfi_xscale_fw_state,
       mfi_xscale_intr_dis,
       mfi_xscale_intr_ena,
       mfi_xscale_intr,
       mfi_xscale_post,
       mfi_scsi_ld_io,
};

static uint32_t         mfi_ppc_fw_state(struct mfi_softc *sc);
static void             mfi_ppc_intr_ena(struct mfi_softc *sc);
static void             mfi_ppc_intr_dis(struct mfi_softc *sc);
static int              mfi_ppc_intr(struct mfi_softc *sc);
static void             mfi_ppc_post(struct mfi_softc *sc, struct mfi_ccb *ccb);

static const struct mfi_iop_ops mfi_iop_ppc = {
       mfi_ppc_fw_state,
       mfi_ppc_intr_dis,
       mfi_ppc_intr_ena,
       mfi_ppc_intr,
       mfi_ppc_post,
       mfi_scsi_ld_io,
};

uint32_t        mfi_gen2_fw_state(struct mfi_softc *sc);
void            mfi_gen2_intr_ena(struct mfi_softc *sc);
void            mfi_gen2_intr_dis(struct mfi_softc *sc);
int             mfi_gen2_intr(struct mfi_softc *sc);
void            mfi_gen2_post(struct mfi_softc *sc, struct mfi_ccb *ccb);

static const struct mfi_iop_ops mfi_iop_gen2 = {
       mfi_gen2_fw_state,
       mfi_gen2_intr_dis,
       mfi_gen2_intr_ena,
       mfi_gen2_intr,
       mfi_gen2_post,
       mfi_scsi_ld_io,
};

u_int32_t       mfi_skinny_fw_state(struct mfi_softc *);
void            mfi_skinny_intr_dis(struct mfi_softc *);
void            mfi_skinny_intr_ena(struct mfi_softc *);
int             mfi_skinny_intr(struct mfi_softc *);
void            mfi_skinny_post(struct mfi_softc *, struct mfi_ccb *);

static const struct mfi_iop_ops mfi_iop_skinny = {
       mfi_skinny_fw_state,
       mfi_skinny_intr_dis,
       mfi_skinny_intr_ena,
       mfi_skinny_intr,
       mfi_skinny_post,
       mfi_scsi_ld_io,
};

static int      mfi_tbolt_init_desc_pool(struct mfi_softc *);
static int      mfi_tbolt_init_MFI_queue(struct mfi_softc *);
static void     mfi_tbolt_build_mpt_ccb(struct mfi_ccb *);
int             mfi_tbolt_scsi_ld_io(struct mfi_ccb *, struct scsipi_xfer *,
                   uint64_t, uint32_t);
static void     mfi_tbolt_scsi_ld_done(struct mfi_ccb *);
static int      mfi_tbolt_create_sgl(struct mfi_ccb *, int);
void            mfi_tbolt_sync_map_info(struct work *, void *);
static void     mfi_sync_map_complete(struct mfi_ccb *);

u_int32_t       mfi_tbolt_fw_state(struct mfi_softc *);
void            mfi_tbolt_intr_dis(struct mfi_softc *);
void            mfi_tbolt_intr_ena(struct mfi_softc *);
int             mfi_tbolt_intr(struct mfi_softc *sc);
void            mfi_tbolt_post(struct mfi_softc *, struct mfi_ccb *);

static const struct mfi_iop_ops mfi_iop_tbolt = {
       mfi_tbolt_fw_state,
       mfi_tbolt_intr_dis,
       mfi_tbolt_intr_ena,
       mfi_tbolt_intr,
       mfi_tbolt_post,
       mfi_tbolt_scsi_ld_io,
};

#define mfi_fw_state(_s)        ((_s)->sc_iop->mio_fw_state(_s))
#define mfi_intr_enable(_s)     ((_s)->sc_iop->mio_intr_ena(_s))
#define mfi_intr_disable(_s)    ((_s)->sc_iop->mio_intr_dis(_s))
#define mfi_my_intr(_s)         ((_s)->sc_iop->mio_intr(_s))
#define mfi_post(_s, _c)        ((_s)->sc_iop->mio_post((_s), (_c)))

static struct mfi_ccb *
mfi_get_ccb(struct mfi_softc *sc)
{
       struct mfi_ccb          *ccb;
       int                     s;

       s = splbio();
       ccb = TAILQ_FIRST(&sc->sc_ccb_freeq);
       if (ccb) {
               TAILQ_REMOVE(&sc->sc_ccb_freeq, ccb, ccb_link);
               ccb->ccb_state = MFI_CCB_READY;
       }
       splx(s);

       DNPRINTF(MFI_D_CCB, "%s: mfi_get_ccb: %p\n", DEVNAME(sc), ccb);
       if (__predict_false(ccb == NULL && sc->sc_running))
               aprint_error_dev(sc->sc_dev, "out of ccb\n");

       return ccb;
}

static void
mfi_put_ccb(struct mfi_ccb *ccb)
{
       struct mfi_softc        *sc = ccb->ccb_sc;
       struct mfi_frame_header *hdr = &ccb->ccb_frame->mfr_header;
       int                     s;

       DNPRINTF(MFI_D_CCB, "%s: mfi_put_ccb: %p\n", DEVNAME(sc), ccb);

       hdr->mfh_cmd_status = 0x0;
       hdr->mfh_flags = 0x0;
       ccb->ccb_state = MFI_CCB_FREE;
       ccb->ccb_xs = NULL;
       ccb->ccb_flags = 0;
       ccb->ccb_done = NULL;
       ccb->ccb_direction = 0;
       ccb->ccb_frame_size = 0;
       ccb->ccb_extra_frames = 0;
       ccb->ccb_sgl = NULL;
       ccb->ccb_data = NULL;
       ccb->ccb_len = 0;
       if (sc->sc_ioptype == MFI_IOP_TBOLT) {
               /* erase tb_request_desc but preserve SMID */
               int index = ccb->ccb_tb_request_desc.header.SMID;
               ccb->ccb_tb_request_desc.words = 0;
               ccb->ccb_tb_request_desc.header.SMID = index;
       }
       s = splbio();
       TAILQ_INSERT_TAIL(&sc->sc_ccb_freeq, ccb, ccb_link);
       splx(s);
}

static int
mfi_destroy_ccb(struct mfi_softc *sc)
{
       struct mfi_ccb          *ccb;
       uint32_t                i;

       DNPRINTF(MFI_D_CCB, "%s: mfi_destroy_ccb\n", DEVNAME(sc));


       for (i = 0; (ccb = mfi_get_ccb(sc)) != NULL; i++) {
               /* create a dma map for transfer */
               bus_dmamap_destroy(sc->sc_datadmat, ccb->ccb_dmamap);
       }

       if (i < sc->sc_max_cmds)
               return EBUSY;

       free(sc->sc_ccb, M_DEVBUF);

       return 0;
}

static int
mfi_init_ccb(struct mfi_softc *sc)
{
       struct mfi_ccb          *ccb;
       uint32_t                i;
       int                     error;
       bus_addr_t              io_req_base_phys;
       uint8_t                 *io_req_base;
       int offset;

       DNPRINTF(MFI_D_CCB, "%s: mfi_init_ccb\n", DEVNAME(sc));

       sc->sc_ccb = malloc(sizeof(struct mfi_ccb) * sc->sc_max_cmds,
           M_DEVBUF, M_WAITOK|M_ZERO);
       if (sc->sc_ioptype == MFI_IOP_TBOLT) {
               /*
                * The first 256 bytes (SMID 0) is not used.
                * Don't add to the cmd list.
                */
               io_req_base = (uint8_t *)MFIMEM_KVA(sc->sc_tbolt_reqmsgpool) +
                   MEGASAS_THUNDERBOLT_NEW_MSG_SIZE;
               io_req_base_phys = MFIMEM_DVA(sc->sc_tbolt_reqmsgpool) +
                   MEGASAS_THUNDERBOLT_NEW_MSG_SIZE;
       } else {
               io_req_base = NULL;     /* XXX: gcc */
               io_req_base_phys = 0;   /* XXX: gcc */
       }

       for (i = 0; i < sc->sc_max_cmds; i++) {
               ccb = &sc->sc_ccb[i];

               ccb->ccb_sc = sc;

               /* select i'th frame */
               ccb->ccb_frame = (union mfi_frame *)
                   ((char*)MFIMEM_KVA(sc->sc_frames) + sc->sc_frames_size * i);
               ccb->ccb_pframe =
                   MFIMEM_DVA(sc->sc_frames) + sc->sc_frames_size * i;
               ccb->ccb_frame->mfr_header.mfh_context = i;

               /* select i'th sense */
               ccb->ccb_sense = (struct mfi_sense *)
                   ((char*)MFIMEM_KVA(sc->sc_sense) + MFI_SENSE_SIZE * i);
               ccb->ccb_psense =
                   (MFIMEM_DVA(sc->sc_sense) + MFI_SENSE_SIZE * i);

               /* create a dma map for transfer */
               error = bus_dmamap_create(sc->sc_datadmat,
                   MAXPHYS, sc->sc_max_sgl, MAXPHYS, 0,
                   BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ccb->ccb_dmamap);
               if (error) {
                       aprint_error_dev(sc->sc_dev,
                           "cannot create ccb dmamap (%d)\n", error);
                       goto destroy;
               }
               if (sc->sc_ioptype == MFI_IOP_TBOLT) {
                       offset = MEGASAS_THUNDERBOLT_NEW_MSG_SIZE * i;
                       ccb->ccb_tb_io_request =
                           (struct mfi_mpi2_request_raid_scsi_io *)
                           (io_req_base + offset);
                       ccb->ccb_tb_pio_request =
                           io_req_base_phys + offset;
                       offset = MEGASAS_MAX_SZ_CHAIN_FRAME * i;
                       ccb->ccb_tb_sg_frame =
                           (mpi2_sge_io_union *)(sc->sc_reply_pool_limit +
                           offset);
                       ccb->ccb_tb_psg_frame = sc->sc_sg_frame_busaddr +
                           offset;
                       /* SMID 0 is reserved. Set SMID/index from 1 */
                       ccb->ccb_tb_request_desc.header.SMID = i + 1;
               }

               DNPRINTF(MFI_D_CCB,
                   "ccb(%d): %p frame: %p (%#lx) sense: %p (%#lx) map: %p\n",
                   ccb->ccb_frame->mfr_header.mfh_context, ccb,
                   ccb->ccb_frame, (u_long)ccb->ccb_pframe,
                   ccb->ccb_sense, (u_long)ccb->ccb_psense,
                   ccb->ccb_dmamap);

               /* add ccb to queue */
               mfi_put_ccb(ccb);
       }

       return 0;
destroy:
       /* free dma maps and ccb memory */
       while (i) {
               i--;
               ccb = &sc->sc_ccb[i];
               bus_dmamap_destroy(sc->sc_datadmat, ccb->ccb_dmamap);
       }

       free(sc->sc_ccb, M_DEVBUF);

       return 1;
}

static uint32_t
mfi_read(struct mfi_softc *sc, bus_size_t r)
{
       uint32_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, "%s: mr %#zx 0x08%x ", DEVNAME(sc), r, rv);
       return rv;
}

static void
mfi_write(struct mfi_softc *sc, bus_size_t r, uint32_t v)
{
       DNPRINTF(MFI_D_RW, "%s: mw %#zx 0x%08x", DEVNAME(sc), 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);
}

static struct mfi_mem *
mfi_allocmem(struct mfi_softc *sc, size_t size)
{
       struct mfi_mem          *mm;
       int                     nsegs;

       DNPRINTF(MFI_D_MEM, "%s: mfi_allocmem: %zu\n", DEVNAME(sc),
           size);

       mm = malloc(sizeof(struct mfi_mem), M_DEVBUF, M_WAITOK|M_ZERO);
       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;

       DNPRINTF(MFI_D_MEM, "  kva: %p  dva: %" PRIxBUSADDR "  map: %p\n",
           mm->am_kva, mm->am_map->dm_segs[0].ds_addr, mm->am_map);

       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;
}

static void
mfi_freemem(struct mfi_softc *sc, struct mfi_mem **mmp)
{
       struct mfi_mem *mm = *mmp;

       if (mm == NULL)
               return;

       *mmp = NULL;

       DNPRINTF(MFI_D_MEM, "%s: mfi_freemem: %p\n", DEVNAME(sc), 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);
}

static int
mfi_transition_firmware(struct mfi_softc *sc)
{
       uint32_t                fw_state, cur_state;
       int                     max_wait, i;

       fw_state = mfi_fw_state(sc) & MFI_STATE_MASK;

       DNPRINTF(MFI_D_CMD, "%s: mfi_transition_firmware: %#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:
                       aprint_error_dev(sc->sc_dev, "firmware fault\n");
                       return 1;
               case MFI_STATE_WAIT_HANDSHAKE:
                       if (sc->sc_ioptype == MFI_IOP_SKINNY ||
                           sc->sc_ioptype == MFI_IOP_TBOLT)
                               mfi_write(sc, MFI_SKINNY_IDB, MFI_INIT_CLEAR_HANDSHAKE);
                       else
                               mfi_write(sc, MFI_IDB, MFI_INIT_CLEAR_HANDSHAKE);
                       max_wait = 2;
                       break;
               case MFI_STATE_OPERATIONAL:
                       if (sc->sc_ioptype == MFI_IOP_SKINNY ||
                           sc->sc_ioptype == MFI_IOP_TBOLT)
                               mfi_write(sc, MFI_SKINNY_IDB, MFI_RESET_FLAGS);
                       else
                               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;
               case MFI_STATE_BOOT_MESSAGE_PENDING:
                       if (sc->sc_ioptype == MFI_IOP_SKINNY ||
                           sc->sc_ioptype == MFI_IOP_TBOLT) {
                               mfi_write(sc, MFI_SKINNY_IDB, MFI_INIT_HOTPLUG);
                       } else {
                               mfi_write(sc, MFI_IDB, MFI_INIT_HOTPLUG);
                       }
                       max_wait = 180;
                       break;
               default:
                       aprint_error_dev(sc->sc_dev,
                           "unknown firmware state %d\n", fw_state);
                       return 1;
               }
               for (i = 0; i < (max_wait * 10); i++) {
                       fw_state = mfi_fw_state(sc) & MFI_STATE_MASK;
                       if (fw_state == cur_state)
                               DELAY(100000);
                       else
                               break;
               }
               if (fw_state == cur_state) {
                       aprint_error_dev(sc->sc_dev,
                           "firmware stuck in state %#x\n", fw_state);
                       return 1;
               }
       }

       return 0;
}

static int
mfi_initialize_firmware(struct mfi_softc *sc)
{
       struct mfi_ccb          *ccb;
       struct mfi_init_frame   *init;
       struct mfi_init_qinfo   *qinfo;

       DNPRINTF(MFI_D_MISC, "%s: mfi_initialize_firmware\n", DEVNAME(sc));

       if ((ccb = mfi_get_ccb(sc)) == NULL)
               return 1;

       init = &ccb->ccb_frame->mfr_init;
       qinfo = (struct mfi_init_qinfo *)((uint8_t *)init + MFI_FRAME_SIZE);

       memset(qinfo, 0, sizeof *qinfo);
       qinfo->miq_rq_entries = sc->sc_max_cmds + 1;
       qinfo->miq_rq_addr_lo = htole32(MFIMEM_DVA(sc->sc_pcq) +
           offsetof(struct mfi_prod_cons, mpc_reply_q));
       qinfo->miq_pi_addr_lo = htole32(MFIMEM_DVA(sc->sc_pcq) +
           offsetof(struct mfi_prod_cons, mpc_producer));
       qinfo->miq_ci_addr_lo = htole32(MFIMEM_DVA(sc->sc_pcq) +
           offsetof(struct mfi_prod_cons, mpc_consumer));

       init->mif_header.mfh_cmd = MFI_CMD_INIT;
       init->mif_header.mfh_data_len = sizeof *qinfo;
       init->mif_qinfo_new_addr_lo = htole32(ccb->ccb_pframe + MFI_FRAME_SIZE);

       DNPRINTF(MFI_D_MISC, "%s: entries: %#x rq: %#x pi: %#x ci: %#x\n",
           DEVNAME(sc),
           qinfo->miq_rq_entries, qinfo->miq_rq_addr_lo,
           qinfo->miq_pi_addr_lo, qinfo->miq_ci_addr_lo);

       if (mfi_poll(ccb)) {
               aprint_error_dev(sc->sc_dev,
                   "mfi_initialize_firmware failed\n");
               return 1;
       }

       mfi_put_ccb(ccb);

       return 0;
}

static int
mfi_get_info(struct mfi_softc *sc)
{
#ifdef MFI_DEBUG
       int i;
#endif
       DNPRINTF(MFI_D_MISC, "%s: mfi_get_info\n", DEVNAME(sc));

       if (mfi_mgmt_internal(sc, MR_DCMD_CTRL_GET_INFO, MFI_DATA_IN,
           sizeof(sc->sc_info), &sc->sc_info, NULL, cold ? true : false))
               return 1;

#ifdef MFI_DEBUG
       for (i = 0; i < sc->sc_info.mci_image_component_count; i++) {
               printf("%s: active FW %s Version %s date %s time %s\n",
                   DEVNAME(sc),
                   sc->sc_info.mci_image_component[i].mic_name,
                   sc->sc_info.mci_image_component[i].mic_version,
                   sc->sc_info.mci_image_component[i].mic_build_date,
                   sc->sc_info.mci_image_component[i].mic_build_time);
       }

       for (i = 0; i < sc->sc_info.mci_pending_image_component_count; i++) {
               printf("%s: pending FW %s Version %s date %s time %s\n",
                   DEVNAME(sc),
                   sc->sc_info.mci_pending_image_component[i].mic_name,
                   sc->sc_info.mci_pending_image_component[i].mic_version,
                   sc->sc_info.mci_pending_image_component[i].mic_build_date,
                   sc->sc_info.mci_pending_image_component[i].mic_build_time);
       }

       printf("%s: max_arms %d max_spans %d max_arrs %d max_lds %d name %s\n",
           DEVNAME(sc),
           sc->sc_info.mci_max_arms,
           sc->sc_info.mci_max_spans,
           sc->sc_info.mci_max_arrays,
           sc->sc_info.mci_max_lds,
           sc->sc_info.mci_product_name);

       printf("%s: serial %s present %#x fw time %d max_cmds %d max_sg %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_serial_number,
           sc->sc_info.mci_hw_present,
           sc->sc_info.mci_current_fw_time,
           sc->sc_info.mci_max_cmds,
           sc->sc_info.mci_max_sg_elements);

       printf("%s: max_rq %d lds_pres %d lds_deg %d lds_off %d pd_pres %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_max_request_size,
           sc->sc_info.mci_lds_present,
           sc->sc_info.mci_lds_degraded,
           sc->sc_info.mci_lds_offline,
           sc->sc_info.mci_pd_present);

       printf("%s: pd_dsk_prs %d pd_dsk_pred_fail %d pd_dsk_fail %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_pd_disks_present,
           sc->sc_info.mci_pd_disks_pred_failure,
           sc->sc_info.mci_pd_disks_failed);

       printf("%s: nvram %d mem %d flash %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_nvram_size,
           sc->sc_info.mci_memory_size,
           sc->sc_info.mci_flash_size);

       printf("%s: ram_cor %d ram_uncor %d clus_all %d clus_act %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_ram_correctable_errors,
           sc->sc_info.mci_ram_uncorrectable_errors,
           sc->sc_info.mci_cluster_allowed,
           sc->sc_info.mci_cluster_active);

       printf("%s: max_strps_io %d raid_lvl %#x adapt_ops %#x ld_ops %#x\n",
           DEVNAME(sc),
           sc->sc_info.mci_max_strips_per_io,
           sc->sc_info.mci_raid_levels,
           sc->sc_info.mci_adapter_ops,
           sc->sc_info.mci_ld_ops);

       printf("%s: strp_sz_min %d strp_sz_max %d pd_ops %#x pd_mix %#x\n",
           DEVNAME(sc),
           sc->sc_info.mci_stripe_sz_ops.min,
           sc->sc_info.mci_stripe_sz_ops.max,
           sc->sc_info.mci_pd_ops,
           sc->sc_info.mci_pd_mix_support);

       printf("%s: ecc_bucket %d pckg_prop %s\n",
           DEVNAME(sc),
           sc->sc_info.mci_ecc_bucket_count,
           sc->sc_info.mci_package_version);

       printf("%s: sq_nm %d prd_fail_poll %d intr_thrtl %d intr_thrtl_to %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_properties.mcp_seq_num,
           sc->sc_info.mci_properties.mcp_pred_fail_poll_interval,
           sc->sc_info.mci_properties.mcp_intr_throttle_cnt,
           sc->sc_info.mci_properties.mcp_intr_throttle_timeout);

       printf("%s: rbld_rate %d patr_rd_rate %d bgi_rate %d cc_rate %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_properties.mcp_rebuild_rate,
           sc->sc_info.mci_properties.mcp_patrol_read_rate,
           sc->sc_info.mci_properties.mcp_bgi_rate,
           sc->sc_info.mci_properties.mcp_cc_rate);

       printf("%s: rc_rate %d ch_flsh %d spin_cnt %d spin_dly %d clus_en %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_properties.mcp_recon_rate,
           sc->sc_info.mci_properties.mcp_cache_flush_interval,
           sc->sc_info.mci_properties.mcp_spinup_drv_cnt,
           sc->sc_info.mci_properties.mcp_spinup_delay,
           sc->sc_info.mci_properties.mcp_cluster_enable);

       printf("%s: coerc %d alarm %d dis_auto_rbld %d dis_bat_wrn %d ecc %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_properties.mcp_coercion_mode,
           sc->sc_info.mci_properties.mcp_alarm_enable,
           sc->sc_info.mci_properties.mcp_disable_auto_rebuild,
           sc->sc_info.mci_properties.mcp_disable_battery_warn,
           sc->sc_info.mci_properties.mcp_ecc_bucket_size);

       printf("%s: ecc_leak %d rest_hs %d exp_encl_dev %d\n",
           DEVNAME(sc),
           sc->sc_info.mci_properties.mcp_ecc_bucket_leak_rate,
           sc->sc_info.mci_properties.mcp_restore_hotspare_on_insertion,
           sc->sc_info.mci_properties.mcp_expose_encl_devices);

       printf("%s: vendor %#x device %#x subvendor %#x subdevice %#x\n",
           DEVNAME(sc),
           sc->sc_info.mci_pci.mip_vendor,
           sc->sc_info.mci_pci.mip_device,
           sc->sc_info.mci_pci.mip_subvendor,
           sc->sc_info.mci_pci.mip_subdevice);

       printf("%s: type %#x port_count %d port_addr ",
           DEVNAME(sc),
           sc->sc_info.mci_host.mih_type,
           sc->sc_info.mci_host.mih_port_count);

       for (i = 0; i < 8; i++)
               printf("%.0" PRIx64 " ", sc->sc_info.mci_host.mih_port_addr[i]);
       printf("\n");

       printf("%s: type %.x port_count %d port_addr ",
           DEVNAME(sc),
           sc->sc_info.mci_device.mid_type,
           sc->sc_info.mci_device.mid_port_count);

       for (i = 0; i < 8; i++) {
               printf("%.0" PRIx64 " ",
                   sc->sc_info.mci_device.mid_port_addr[i]);
       }
       printf("\n");
#endif /* MFI_DEBUG */

       return 0;
}

static int
mfi_get_bbu(struct mfi_softc *sc, struct mfi_bbu_status *stat)
{
       DNPRINTF(MFI_D_MISC, "%s: mfi_get_bbu\n", DEVNAME(sc));

       if (mfi_mgmt_internal(sc, MR_DCMD_BBU_GET_STATUS, MFI_DATA_IN,
           sizeof(*stat), stat, NULL, cold ? true : false))
               return MFI_BBU_UNKNOWN;
#ifdef MFI_DEBUG
       printf("bbu type %d, voltage %d, current %d, temperature %d, "
           "status 0x%x\n", stat->battery_type, stat->voltage, stat->current,
           stat->temperature, stat->fw_status);
       printf("details: ");
       switch (stat->battery_type) {
       case MFI_BBU_TYPE_IBBU:
               printf("guage %d relative charge %d charger state %d "
                   "charger ctrl %d\n", stat->detail.ibbu.gas_guage_status,
                   stat->detail.ibbu.relative_charge ,
                   stat->detail.ibbu.charger_system_state ,
                   stat->detail.ibbu.charger_system_ctrl);
               printf("\tcurrent %d abs charge %d max error %d\n",
                   stat->detail.ibbu.charging_current ,
                   stat->detail.ibbu.absolute_charge ,
                   stat->detail.ibbu.max_error);
               break;
       case MFI_BBU_TYPE_BBU:
               printf("guage %d relative charge %d charger state %d\n",
                   stat->detail.ibbu.gas_guage_status,
                   stat->detail.bbu.relative_charge ,
                   stat->detail.bbu.charger_status );
               printf("\trem capacity %d fyll capacity %d SOH %d\n",
                   stat->detail.bbu.remaining_capacity ,
                   stat->detail.bbu.full_charge_capacity ,
                   stat->detail.bbu.is_SOH_good);
               break;
       default:
               printf("\n");
       }
#endif
       switch (stat->battery_type) {
       case MFI_BBU_TYPE_BBU:
               return (stat->detail.bbu.is_SOH_good ?
                   MFI_BBU_GOOD : MFI_BBU_BAD);
       case MFI_BBU_TYPE_NONE:
               return MFI_BBU_UNKNOWN;
       default:
               if (stat->fw_status &
                   (MFI_BBU_STATE_PACK_MISSING |
                    MFI_BBU_STATE_VOLTAGE_LOW |
                    MFI_BBU_STATE_TEMPERATURE_HIGH |
                    MFI_BBU_STATE_LEARN_CYC_FAIL |
                    MFI_BBU_STATE_LEARN_CYC_TIMEOUT |
                    MFI_BBU_STATE_I2C_ERR_DETECT))
                       return MFI_BBU_BAD;
               return MFI_BBU_GOOD;
       }
}

static void
mfiminphys(struct buf *bp)
{
       DNPRINTF(MFI_D_MISC, "mfiminphys: %d\n", bp->b_bcount);

       /* XXX currently using MFI_MAXFER = MAXPHYS */
       if (bp->b_bcount > MFI_MAXFER)
               bp->b_bcount = MFI_MAXFER;
       minphys(bp);
}

int
mfi_rescan(device_t self, const char *ifattr, const int *locators)
{
       struct mfi_softc *sc = device_private(self);

       if (sc->sc_child != NULL)
               return 0;

       sc->sc_child = config_found(self, &sc->sc_chan, scsiprint, CFARGS_NONE);

       return 0;
}

void
mfi_childdetached(device_t self, device_t child)
{
       struct mfi_softc *sc = device_private(self);

       KASSERT(self == sc->sc_dev);
       KASSERT(child == sc->sc_child);

       if (child == sc->sc_child)
               sc->sc_child = NULL;
}

int
mfi_detach(struct mfi_softc *sc, int flags)
{
       int                     error;

       DNPRINTF(MFI_D_MISC, "%s: mfi_detach\n", DEVNAME(sc));

       if ((error = config_detach_children(sc->sc_dev, flags)) != 0)
               return error;

#if NBIO > 0
       mfi_destroy_sensors(sc);
       bio_unregister(sc->sc_dev);
#endif /* NBIO > 0 */

       mfi_intr_disable(sc);
       mfi_shutdown(sc->sc_dev, 0);

       if (sc->sc_ioptype == MFI_IOP_TBOLT) {
               workqueue_destroy(sc->sc_ldsync_wq);
               mfi_put_ccb(sc->sc_ldsync_ccb);
               mfi_freemem(sc, &sc->sc_tbolt_reqmsgpool);
               mfi_freemem(sc, &sc->sc_tbolt_ioc_init);
               mfi_freemem(sc, &sc->sc_tbolt_verbuf);
       }

       if ((error = mfi_destroy_ccb(sc)) != 0)
               return error;

       mfi_freemem(sc, &sc->sc_sense);

       mfi_freemem(sc, &sc->sc_frames);

       mfi_freemem(sc, &sc->sc_pcq);

       return 0;
}

static bool
mfi_shutdown(device_t dev, int how)
{
       struct mfi_softc        *sc = device_private(dev);
       union mfi_mbox          mbox;
       int s = splbio();

       DNPRINTF(MFI_D_MISC, "%s: mfi_shutdown\n", DEVNAME(sc));
       if (sc->sc_running) {
               memset(&mbox, 0, sizeof(mbox));
               mbox.b[0] = MR_FLUSH_CTRL_CACHE | MR_FLUSH_DISK_CACHE;
               if (mfi_mgmt_internal(sc, MR_DCMD_CTRL_CACHE_FLUSH,
                   MFI_DATA_NONE, 0, NULL, &mbox, true)) {
                       aprint_error_dev(dev, "shutdown: cache flush failed\n");
                       goto fail;
               }

               mbox.b[0] = 0;
               if (mfi_mgmt_internal(sc, MR_DCMD_CTRL_SHUTDOWN,
                   MFI_DATA_NONE, 0, NULL, &mbox, true)) {
                       aprint_error_dev(dev, "shutdown: "
                           "firmware shutdown failed\n");
                       goto fail;
               }
               sc->sc_running = false;
       }
       splx(s);
       return true;
fail:
       splx(s);
       return false;
}

static bool
mfi_suspend(device_t dev, const pmf_qual_t *q)
{
       /* XXX to be implemented */
       return false;
}

static bool
mfi_resume(device_t dev, const pmf_qual_t *q)
{
       /* XXX to be implemented */
       return false;
}

int
mfi_attach(struct mfi_softc *sc, enum mfi_iop iop)
{
       struct scsipi_adapter *adapt = &sc->sc_adapt;
       struct scsipi_channel *chan = &sc->sc_chan;
       uint32_t                status, frames, max_sgl;
       int                     i;

       DNPRINTF(MFI_D_MISC, "%s: mfi_attach\n", DEVNAME(sc));

       sc->sc_ioptype = iop;

       switch (iop) {
       case MFI_IOP_XSCALE:
               sc->sc_iop = &mfi_iop_xscale;
               break;
       case MFI_IOP_PPC:
               sc->sc_iop = &mfi_iop_ppc;
               break;
       case MFI_IOP_GEN2:
               sc->sc_iop = &mfi_iop_gen2;
               break;
       case MFI_IOP_SKINNY:
               sc->sc_iop = &mfi_iop_skinny;
               break;
       case MFI_IOP_TBOLT:
               sc->sc_iop = &mfi_iop_tbolt;
               break;
       default:
               panic("%s: unknown iop %d", DEVNAME(sc), iop);
       }

       if (mfi_transition_firmware(sc))
               return 1;

       TAILQ_INIT(&sc->sc_ccb_freeq);

       status = mfi_fw_state(sc);
       sc->sc_max_cmds = status & MFI_STATE_MAXCMD_MASK;
       max_sgl = (status & MFI_STATE_MAXSGL_MASK) >> 16;
       if (sc->sc_ioptype == MFI_IOP_TBOLT) {
               sc->sc_max_sgl = uimin(max_sgl, (128 * 1024) / PAGE_SIZE + 1);
               sc->sc_sgl_size = sizeof(struct mfi_sg_ieee);
       } else if (sc->sc_64bit_dma) {
               sc->sc_max_sgl = uimin(max_sgl, (128 * 1024) / PAGE_SIZE + 1);
               sc->sc_sgl_size = sizeof(struct mfi_sg64);
       } else {
               sc->sc_max_sgl = max_sgl;
               sc->sc_sgl_size = sizeof(struct mfi_sg32);
       }
       if (sc->sc_ioptype == MFI_IOP_SKINNY)
               sc->sc_sgl_size = sizeof(struct mfi_sg_ieee);
       DNPRINTF(MFI_D_MISC, "%s: max commands: %u, max sgl: %u\n",
           DEVNAME(sc), sc->sc_max_cmds, sc->sc_max_sgl);

       if (sc->sc_ioptype == MFI_IOP_TBOLT) {
               uint32_t tb_mem_size;
               /* for Alignment */
               tb_mem_size = MEGASAS_THUNDERBOLT_MSG_ALIGNMENT;

               tb_mem_size +=
                   MEGASAS_THUNDERBOLT_NEW_MSG_SIZE * (sc->sc_max_cmds + 1);
               sc->sc_reply_pool_size =
                   ((sc->sc_max_cmds + 1 + 15) / 16) * 16;
               tb_mem_size +=
                   MEGASAS_THUNDERBOLT_REPLY_SIZE * sc->sc_reply_pool_size;

               /* this is for SGL's */
               tb_mem_size += MEGASAS_MAX_SZ_CHAIN_FRAME * sc->sc_max_cmds;
               sc->sc_tbolt_reqmsgpool = mfi_allocmem(sc, tb_mem_size);
               if (sc->sc_tbolt_reqmsgpool == NULL) {
                       aprint_error_dev(sc->sc_dev,
                           "unable to allocate thunderbolt "
                           "request message pool\n");
                       goto nopcq;
               }
               if (mfi_tbolt_init_desc_pool(sc)) {
                       aprint_error_dev(sc->sc_dev,
                           "Thunderbolt pool preparation error\n");
                       goto nopcq;
               }

               /*
                * Allocate DMA memory mapping for MPI2 IOC Init descriptor,
                * we are taking it different from what we have allocated for
                * Request and reply descriptors to avoid confusion later
                */
               sc->sc_tbolt_ioc_init = mfi_allocmem(sc,
                   sizeof(struct mpi2_ioc_init_request));
               if (sc->sc_tbolt_ioc_init == NULL) {
                       aprint_error_dev(sc->sc_dev,
                           "unable to allocate thunderbolt IOC init memory");
                       goto nopcq;
               }

               sc->sc_tbolt_verbuf = mfi_allocmem(sc,
                   MEGASAS_MAX_NAME*sizeof(bus_addr_t));
               if (sc->sc_tbolt_verbuf == NULL) {
                       aprint_error_dev(sc->sc_dev,
                           "unable to allocate thunderbolt version buffer\n");
                       goto nopcq;
               }

       }
       /* 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) {
               aprint_error_dev(sc->sc_dev,
                   "unable to allocate reply queue memory\n");
               goto nopcq;
       }
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_pcq), 0,
           sizeof(uint32_t) * sc->sc_max_cmds + sizeof(struct mfi_prod_cons),
           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

       /* frame memory */
       frames = (sc->sc_sgl_size * sc->sc_max_sgl + MFI_FRAME_SIZE - 1) /
           MFI_FRAME_SIZE + 1;
       sc->sc_frames_size = frames * MFI_FRAME_SIZE;
       sc->sc_frames = mfi_allocmem(sc, sc->sc_frames_size * sc->sc_max_cmds);
       if (sc->sc_frames == NULL) {
               aprint_error_dev(sc->sc_dev,
                   "unable to allocate frame memory\n");
               goto noframe;
       }
       /* XXX hack, fix this */
       if (MFIMEM_DVA(sc->sc_frames) & 0x3f) {
               aprint_error_dev(sc->sc_dev,
                   "improper frame alignment (%#llx) FIXME\n",
                   (long long int)MFIMEM_DVA(sc->sc_frames));
               goto noframe;
       }

       /* sense memory */
       sc->sc_sense = mfi_allocmem(sc, sc->sc_max_cmds * MFI_SENSE_SIZE);
       if (sc->sc_sense == NULL) {
               aprint_error_dev(sc->sc_dev,
                   "unable to allocate sense memory\n");
               goto nosense;
       }

       /* now that we have all memory bits go initialize ccbs */
       if (mfi_init_ccb(sc)) {
               aprint_error_dev(sc->sc_dev, "could not init ccb list\n");
               goto noinit;
       }

       /* kickstart firmware with all addresses and pointers */
       if (sc->sc_ioptype == MFI_IOP_TBOLT) {
               if (mfi_tbolt_init_MFI_queue(sc)) {
                       aprint_error_dev(sc->sc_dev,
                           "could not initialize firmware\n");
                       goto noinit;
               }
       } else {
               if (mfi_initialize_firmware(sc)) {
                       aprint_error_dev(sc->sc_dev,
                           "could not initialize firmware\n");
                       goto noinit;
               }
       }
       sc->sc_running = true;

       if (mfi_get_info(sc)) {
               aprint_error_dev(sc->sc_dev,
                   "could not retrieve controller information\n");
               goto noinit;
       }
       aprint_normal_dev(sc->sc_dev,
           "%s version %s\n",
           sc->sc_info.mci_product_name,
           sc->sc_info.mci_package_version);


       aprint_normal_dev(sc->sc_dev, "logical drives %d, %dMB RAM, ",
           sc->sc_info.mci_lds_present,
           sc->sc_info.mci_memory_size);
       sc->sc_bbuok = false;
       if (sc->sc_info.mci_hw_present & MFI_INFO_HW_BBU) {
               struct mfi_bbu_status   bbu_stat;
               int mfi_bbu_status = mfi_get_bbu(sc, &bbu_stat);
               aprint_normal("BBU type ");
               switch (bbu_stat.battery_type) {
               case MFI_BBU_TYPE_BBU:
                       aprint_normal("BBU");
                       break;
               case MFI_BBU_TYPE_IBBU:
                       aprint_normal("IBBU");
                       break;
               default:
                       aprint_normal("unknown type %d", bbu_stat.battery_type);
               }
               aprint_normal(", status ");
               switch (mfi_bbu_status) {
               case MFI_BBU_GOOD:
                       aprint_normal("good\n");
                       sc->sc_bbuok = true;
                       break;
               case MFI_BBU_BAD:
                       aprint_normal("bad\n");
                       break;
               case MFI_BBU_UNKNOWN:
                       aprint_normal("unknown\n");
                       break;
               default:
                       panic("mfi_bbu_status");
               }
       } else {
               aprint_normal("BBU not present\n");
       }

       sc->sc_ld_cnt = sc->sc_info.mci_lds_present;
       sc->sc_max_ld = sc->sc_ld_cnt;
       for (i = 0; i < sc->sc_ld_cnt; i++)
               sc->sc_ld[i].ld_present = 1;

       memset(adapt, 0, sizeof(*adapt));
       adapt->adapt_dev = sc->sc_dev;
       adapt->adapt_nchannels = 1;
       /* keep a few commands for management */
       if (sc->sc_max_cmds > 4)
               adapt->adapt_openings = sc->sc_max_cmds - 4;
       else
               adapt->adapt_openings = sc->sc_max_cmds;
       adapt->adapt_max_periph = adapt->adapt_openings;
       adapt->adapt_request = mfi_scsipi_request;
       adapt->adapt_minphys = mfiminphys;

       memset(chan, 0, sizeof(*chan));
       chan->chan_adapter = adapt;
       chan->chan_bustype = &scsi_sas_bustype;
       chan->chan_channel = 0;
       chan->chan_flags = 0;
       chan->chan_nluns = 8;
       chan->chan_ntargets = MFI_MAX_LD;
       chan->chan_id = MFI_MAX_LD;

       mfi_rescan(sc->sc_dev, NULL, NULL);

       /* enable interrupts */
       mfi_intr_enable(sc);

#if NBIO > 0
       if (bio_register(sc->sc_dev, mfi_ioctl) != 0)
               panic("%s: controller registration failed", DEVNAME(sc));
       if (mfi_create_sensors(sc) != 0)
               aprint_error_dev(sc->sc_dev, "unable to create sensors\n");
#endif /* NBIO > 0 */
       if (!pmf_device_register1(sc->sc_dev, mfi_suspend, mfi_resume,
           mfi_shutdown)) {
               aprint_error_dev(sc->sc_dev,
                   "couldn't establish power handler\n");
       }

       return 0;
noinit:
       mfi_freemem(sc, &sc->sc_sense);
nosense:
       mfi_freemem(sc, &sc->sc_frames);
noframe:
       mfi_freemem(sc, &sc->sc_pcq);
nopcq:
       if (sc->sc_ioptype == MFI_IOP_TBOLT) {
               if (sc->sc_tbolt_reqmsgpool)
                       mfi_freemem(sc, &sc->sc_tbolt_reqmsgpool);
               if (sc->sc_tbolt_verbuf)
                       mfi_freemem(sc, &sc->sc_tbolt_verbuf);
       }
       return 1;
}

static int
mfi_poll(struct mfi_ccb *ccb)
{
       struct mfi_softc *sc = ccb->ccb_sc;
       struct mfi_frame_header *hdr;
       int                     to = 0;
       int                     rv = 0;

       DNPRINTF(MFI_D_CMD, "%s: mfi_poll\n", DEVNAME(sc));

       hdr = &ccb->ccb_frame->mfr_header;
       hdr->mfh_cmd_status = 0xff;
       if (!sc->sc_MFA_enabled)
               hdr->mfh_flags |= MFI_FRAME_DONT_POST_IN_REPLY_QUEUE;

       /* no callback, caller is supposed to do the cleanup */
       ccb->ccb_done = NULL;

       mfi_post(sc, ccb);
       if (sc->sc_MFA_enabled) {
               /*
                * depending on the command type, result may be posted
                * to *hdr, or not. In addition it seems there's
                * no way to avoid posting the SMID to the reply queue.
                * So pool using the interrupt routine.
                */
                while (ccb->ccb_state != MFI_CCB_DONE) {
                       delay(1000);
                       if (to++ > 5000) { /* XXX 5 seconds busywait sucks */
                               rv = 1;
                               break;
                       }
                       mfi_tbolt_intrh(sc);
                }
       } else {
               bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_frames),
                   ccb->ccb_pframe - MFIMEM_DVA(sc->sc_frames),
                   sc->sc_frames_size, BUS_DMASYNC_POSTREAD);

               while (hdr->mfh_cmd_status == 0xff) {
                       delay(1000);
                       if (to++ > 5000) { /* XXX 5 seconds busywait sucks */
                               rv = 1;
                               break;
                       }
                       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_frames),
                           ccb->ccb_pframe - MFIMEM_DVA(sc->sc_frames),
                           sc->sc_frames_size, BUS_DMASYNC_POSTREAD);
               }
       }
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_frames),
           ccb->ccb_pframe - MFIMEM_DVA(sc->sc_frames),
           sc->sc_frames_size, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);

       if (ccb->ccb_data != NULL) {
               DNPRINTF(MFI_D_INTR, "%s: mfi_mgmt_done sync\n",
                   DEVNAME(sc));
               bus_dmamap_sync(sc->sc_datadmat, ccb->ccb_dmamap, 0,
                   ccb->ccb_dmamap->dm_mapsize,
                   (ccb->ccb_direction & MFI_DATA_IN) ?
                   BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);

               bus_dmamap_unload(sc->sc_datadmat, ccb->ccb_dmamap);
       }

       if (rv != 0) {
               aprint_error_dev(sc->sc_dev, "timeout on ccb %d\n",
                   hdr->mfh_context);
               ccb->ccb_flags |= MFI_CCB_F_ERR;
               return 1;
       }

       return 0;
}

int
mfi_intr(void *arg)
{
       struct mfi_softc        *sc = arg;
       struct mfi_prod_cons    *pcq;
       struct mfi_ccb          *ccb;
       uint32_t                producer, consumer, ctx;
       int                     claimed = 0;

       if (!mfi_my_intr(sc))
               return 0;

       pcq = MFIMEM_KVA(sc->sc_pcq);

       DNPRINTF(MFI_D_INTR, "%s: mfi_intr %p %p\n", DEVNAME(sc), sc, pcq);

       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_pcq), 0,
           sizeof(uint32_t) * sc->sc_max_cmds + sizeof(struct mfi_prod_cons),
           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);

       producer = pcq->mpc_producer;
       consumer = pcq->mpc_consumer;

       while (consumer != producer) {
               DNPRINTF(MFI_D_INTR, "%s: mfi_intr pi %#x ci %#x\n",
                   DEVNAME(sc), producer, consumer);

               ctx = pcq->mpc_reply_q[consumer];
               pcq->mpc_reply_q[consumer] = MFI_INVALID_CTX;
               if (ctx == MFI_INVALID_CTX)
                       aprint_error_dev(sc->sc_dev,
                           "invalid context, p: %d c: %d\n",
                           producer, consumer);
               else {
                       /* XXX remove from queue and call scsi_done */
                       ccb = &sc->sc_ccb[ctx];
                       DNPRINTF(MFI_D_INTR, "%s: mfi_intr context %#x\n",
                           DEVNAME(sc), ctx);
                       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_frames),
                           ccb->ccb_pframe - MFIMEM_DVA(sc->sc_frames),
                           sc->sc_frames_size,
                           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
                       ccb->ccb_done(ccb);

                       claimed = 1;
               }
               consumer++;
               if (consumer == (sc->sc_max_cmds + 1))
                       consumer = 0;
       }

       pcq->mpc_consumer = consumer;
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_pcq), 0,
           sizeof(uint32_t) * sc->sc_max_cmds + sizeof(struct mfi_prod_cons),
           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

       return claimed;
}

static int
mfi_scsi_ld_io(struct mfi_ccb *ccb, struct scsipi_xfer *xs, uint64_t blockno,
   uint32_t blockcnt)
{
       struct scsipi_periph *periph = xs->xs_periph;
       struct mfi_io_frame   *io;

       DNPRINTF(MFI_D_CMD, "%s: mfi_scsi_ld_io: %d\n",
           device_xname(periph->periph_channel->chan_adapter->adapt_dev),
           periph->periph_target);

       if (!xs->data)
               return 1;

       io = &ccb->ccb_frame->mfr_io;
       if (xs->xs_control & XS_CTL_DATA_IN) {
               io->mif_header.mfh_cmd = MFI_CMD_LD_READ;
               ccb->ccb_direction = MFI_DATA_IN;
       } else {
               io->mif_header.mfh_cmd = MFI_CMD_LD_WRITE;
               ccb->ccb_direction = MFI_DATA_OUT;
       }
       io->mif_header.mfh_target_id = periph->periph_target;
       io->mif_header.mfh_timeout = 0;
       io->mif_header.mfh_flags = 0;
       io->mif_header.mfh_sense_len = MFI_SENSE_SIZE;
       io->mif_header.mfh_data_len= blockcnt;
       io->mif_lba_hi = (blockno >> 32);
       io->mif_lba_lo = (blockno & 0xffffffff);
       io->mif_sense_addr_lo = htole32(ccb->ccb_psense);
       io->mif_sense_addr_hi = 0;

       ccb->ccb_done = mfi_scsi_ld_done;
       ccb->ccb_xs = xs;
       ccb->ccb_frame_size = MFI_IO_FRAME_SIZE;
       ccb->ccb_sgl = &io->mif_sgl;
       ccb->ccb_data = xs->data;
       ccb->ccb_len = xs->datalen;

       if (mfi_create_sgl(ccb, (xs->xs_control & XS_CTL_NOSLEEP) ?
           BUS_DMA_NOWAIT : BUS_DMA_WAITOK))
               return 1;

       return 0;
}

static void
mfi_scsi_ld_done(struct mfi_ccb *ccb)
{
       struct mfi_frame_header *hdr = &ccb->ccb_frame->mfr_header;
       mfi_scsi_xs_done(ccb, hdr->mfh_cmd_status, hdr->mfh_scsi_status);
}

static void
mfi_scsi_xs_done(struct mfi_ccb *ccb, int status, int scsi_status)
{
       struct scsipi_xfer      *xs = ccb->ccb_xs;
       struct mfi_softc        *sc = ccb->ccb_sc;

       DNPRINTF(MFI_D_INTR, "%s: mfi_scsi_xs_done %p %p\n",
           DEVNAME(sc), ccb, ccb->ccb_frame);

       if (xs->data != NULL) {
               DNPRINTF(MFI_D_INTR, "%s: mfi_scsi_xs_done sync\n",
                   DEVNAME(sc));
               bus_dmamap_sync(sc->sc_datadmat, ccb->ccb_dmamap, 0,
                   ccb->ccb_dmamap->dm_mapsize,
                   (xs->xs_control & XS_CTL_DATA_IN) ?
                   BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);

               bus_dmamap_unload(sc->sc_datadmat, ccb->ccb_dmamap);
       }

       if (status != MFI_STAT_OK) {
               xs->error = XS_DRIVER_STUFFUP;
               DNPRINTF(MFI_D_INTR, "%s: mfi_scsi_xs_done stuffup %#x\n",
                   DEVNAME(sc), status);

               if (scsi_status != 0) {
                       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_sense),
                           ccb->ccb_psense - MFIMEM_DVA(sc->sc_sense),
                           MFI_SENSE_SIZE, BUS_DMASYNC_POSTREAD);
                       DNPRINTF(MFI_D_INTR,
                           "%s: mfi_scsi_xs_done sense %#x %p %p\n",
                           DEVNAME(sc), scsi_status,
                           &xs->sense, ccb->ccb_sense);
                       memset(&xs->sense, 0, sizeof(xs->sense));
                       memcpy(&xs->sense, ccb->ccb_sense,
                           sizeof(struct scsi_sense_data));
                       xs->error = XS_SENSE;
               }
       } else {
               xs->error = XS_NOERROR;
               xs->status = SCSI_OK;
               xs->resid = 0;
       }

       mfi_put_ccb(ccb);
       scsipi_done(xs);
}

static int
mfi_scsi_ld(struct mfi_ccb *ccb, struct scsipi_xfer *xs)
{
       struct mfi_pass_frame   *pf;
       struct scsipi_periph *periph = xs->xs_periph;

       DNPRINTF(MFI_D_CMD, "%s: mfi_scsi_ld: %d\n",
           device_xname(periph->periph_channel->chan_adapter->adapt_dev),
           periph->periph_target);

       pf = &ccb->ccb_frame->mfr_pass;
       pf->mpf_header.mfh_cmd = MFI_CMD_LD_SCSI_IO;
       pf->mpf_header.mfh_target_id = periph->periph_target;
       pf->mpf_header.mfh_lun_id = 0;
       pf->mpf_header.mfh_cdb_len = xs->cmdlen;
       pf->mpf_header.mfh_timeout = 0;
       pf->mpf_header.mfh_data_len= xs->datalen; /* XXX */
       pf->mpf_header.mfh_sense_len = MFI_SENSE_SIZE;

       pf->mpf_sense_addr_hi = 0;
       pf->mpf_sense_addr_lo = htole32(ccb->ccb_psense);

       memset(pf->mpf_cdb, 0, 16);
       memcpy(pf->mpf_cdb, &xs->cmdstore, xs->cmdlen);

       ccb->ccb_done = mfi_scsi_ld_done;
       ccb->ccb_xs = xs;
       ccb->ccb_frame_size = MFI_PASS_FRAME_SIZE;
       ccb->ccb_sgl = &pf->mpf_sgl;

       if (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT))
               ccb->ccb_direction = (xs->xs_control & XS_CTL_DATA_IN) ?
                   MFI_DATA_IN : MFI_DATA_OUT;
       else
               ccb->ccb_direction = MFI_DATA_NONE;

       if (xs->data) {
               ccb->ccb_data = xs->data;
               ccb->ccb_len = xs->datalen;

               if (mfi_create_sgl(ccb, (xs->xs_control & XS_CTL_NOSLEEP) ?
                   BUS_DMA_NOWAIT : BUS_DMA_WAITOK))
                       return 1;
       }

       return 0;
}

static void
mfi_scsipi_request(struct scsipi_channel *chan, scsipi_adapter_req_t req,
   void *arg)
{
       struct scsipi_periph    *periph;
       struct scsipi_xfer      *xs;
       struct scsipi_adapter   *adapt = chan->chan_adapter;
       struct mfi_softc        *sc = device_private(adapt->adapt_dev);
       struct mfi_ccb          *ccb;
       struct scsi_rw_6        *rw;
       struct scsipi_rw_10     *rwb;
       struct scsipi_rw_12     *rw12;
       struct scsipi_rw_16     *rw16;
       union mfi_mbox          mbox;
       uint64_t                blockno;
       uint32_t                blockcnt;
       uint8_t                 target;
       int                     s;

       switch (req) {
       case ADAPTER_REQ_GROW_RESOURCES:
               /* Not supported. */
               return;
       case ADAPTER_REQ_SET_XFER_MODE:
       {
               struct scsipi_xfer_mode *xm = arg;
               xm->xm_mode = PERIPH_CAP_TQING;
               xm->xm_period = 0;
               xm->xm_offset = 0;
               scsipi_async_event(&sc->sc_chan, ASYNC_EVENT_XFER_MODE, xm);
               return;
       }
       case ADAPTER_REQ_RUN_XFER:
               break;
       }

       xs = arg;

       periph = xs->xs_periph;
       target = periph->periph_target;

       DNPRINTF(MFI_D_CMD, "%s: mfi_scsipi_request req %d opcode: %#x "
           "target %d lun %d\n", DEVNAME(sc), req, xs->cmd->opcode,
           periph->periph_target, periph->periph_lun);

       s = splbio();
       if (target >= MFI_MAX_LD || !sc->sc_ld[target].ld_present ||
           periph->periph_lun != 0) {
               DNPRINTF(MFI_D_CMD, "%s: invalid target %d\n",
                   DEVNAME(sc), target);
               xs->error = XS_SELTIMEOUT;
               scsipi_done(xs);
               splx(s);
               return;
       }
       if ((xs->cmd->opcode == SCSI_SYNCHRONIZE_CACHE_10 ||
           xs->cmd->opcode == SCSI_SYNCHRONIZE_CACHE_16) && sc->sc_bbuok) {
               /* the cache is stable storage, don't flush */
               xs->error = XS_NOERROR;
               xs->status = SCSI_OK;
               xs->resid = 0;
               scsipi_done(xs);
               splx(s);
               return;
       }

       if ((ccb = mfi_get_ccb(sc)) == NULL) {
               DNPRINTF(MFI_D_CMD, "%s: mfi_scsipi_request no ccb\n", DEVNAME(sc));
               xs->error = XS_RESOURCE_SHORTAGE;
               scsipi_done(xs);
               splx(s);
               return;
       }

       switch (xs->cmd->opcode) {
       /* IO path */
       case READ_16:
       case WRITE_16:
               rw16 = (struct scsipi_rw_16 *)xs->cmd;
               blockno = _8btol(rw16->addr);
               blockcnt = _4btol(rw16->length);
               if (sc->sc_iop->mio_ld_io(ccb, xs, blockno, blockcnt)) {
                       goto stuffup;
               }
               break;

       case READ_12:
       case WRITE_12:
               rw12 = (struct scsipi_rw_12 *)xs->cmd;
               blockno = _4btol(rw12->addr);
               blockcnt = _4btol(rw12->length);
               if (sc->sc_iop->mio_ld_io(ccb, xs, blockno, blockcnt)) {
                       goto stuffup;
               }
               break;

       case READ_10:
       case WRITE_10:
               rwb = (struct scsipi_rw_10 *)xs->cmd;
               blockno = _4btol(rwb->addr);
               blockcnt = _2btol(rwb->length);
               if (sc->sc_iop->mio_ld_io(ccb, xs, blockno, blockcnt)) {
                       goto stuffup;
               }
               break;

       case SCSI_READ_6_COMMAND:
       case SCSI_WRITE_6_COMMAND:
               rw = (struct scsi_rw_6 *)xs->cmd;
               blockno = _3btol(rw->addr) & (SRW_TOPADDR << 16 | 0xffff);
               blockcnt = rw->length ? rw->length : 0x100;
               if (sc->sc_iop->mio_ld_io(ccb, xs, blockno, blockcnt)) {
                       goto stuffup;
               }
               break;

       case SCSI_SYNCHRONIZE_CACHE_10:
       case SCSI_SYNCHRONIZE_CACHE_16:
               memset(&mbox, 0, sizeof(mbox));
               mbox.b[0] = MR_FLUSH_CTRL_CACHE | MR_FLUSH_DISK_CACHE;
               if (mfi_mgmt(ccb, xs,
                   MR_DCMD_CTRL_CACHE_FLUSH, MFI_DATA_NONE, 0, NULL, &mbox)) {
                       goto stuffup;
               }
               break;

       /* hand it of to the firmware and let it deal with it */
       case SCSI_TEST_UNIT_READY:
               /* save off sd? after autoconf */
               if (!cold)      /* XXX bogus */
                       strlcpy(sc->sc_ld[target].ld_dev, device_xname(sc->sc_dev),
                           sizeof(sc->sc_ld[target].ld_dev));
               /* FALLTHROUGH */

       default:
               if (mfi_scsi_ld(ccb, xs)) {
                       goto stuffup;
               }
               break;
       }

       DNPRINTF(MFI_D_CMD, "%s: start io %d\n", DEVNAME(sc), target);

       if (xs->xs_control & XS_CTL_POLL) {
               if (mfi_poll(ccb)) {
                       /* XXX check for sense in ccb->ccb_sense? */
                       aprint_error_dev(sc->sc_dev,
                           "mfi_scsipi_request poll failed\n");
                       memset(&xs->sense, 0, sizeof(xs->sense));
                       xs->sense.scsi_sense.response_code =
                           SSD_RCODE_VALID | SSD_RCODE_CURRENT;
                       xs->sense.scsi_sense.flags = SKEY_ILLEGAL_REQUEST;
                       xs->sense.scsi_sense.asc = 0x20; /* invalid opcode */
                       xs->error = XS_SENSE;
                       xs->status = SCSI_CHECK;
               } else {
                       DNPRINTF(MFI_D_DMA,
                           "%s: mfi_scsipi_request poll complete %d\n",
                           DEVNAME(sc), ccb->ccb_dmamap->dm_nsegs);
                       xs->error = XS_NOERROR;
                       xs->status = SCSI_OK;
                       xs->resid = 0;
               }
               mfi_put_ccb(ccb);
               scsipi_done(xs);
               splx(s);
               return;
       }

       mfi_post(sc, ccb);

       DNPRINTF(MFI_D_DMA, "%s: mfi_scsipi_request queued %d\n", DEVNAME(sc),
           ccb->ccb_dmamap->dm_nsegs);

       splx(s);
       return;

stuffup:
       mfi_put_ccb(ccb);
       xs->error = XS_DRIVER_STUFFUP;
       scsipi_done(xs);
       splx(s);
}

static int
mfi_create_sgl(struct mfi_ccb *ccb, int flags)
{
       struct mfi_softc        *sc = ccb->ccb_sc;
       struct mfi_frame_header *hdr;
       bus_dma_segment_t       *sgd;
       union mfi_sgl           *sgl;
       int                     error, i;

       DNPRINTF(MFI_D_DMA, "%s: mfi_create_sgl %p\n", DEVNAME(sc),
           ccb->ccb_data);

       if (!ccb->ccb_data)
               return 1;

       KASSERT(flags == BUS_DMA_NOWAIT || !cpu_intr_p());
       error = bus_dmamap_load(sc->sc_datadmat, ccb->ccb_dmamap,
           ccb->ccb_data, ccb->ccb_len, NULL, flags);
       if (error) {
               if (error == EFBIG) {
                       aprint_error_dev(sc->sc_dev, "more than %d dma segs\n",
                           sc->sc_max_sgl);
               } else {
                       aprint_error_dev(sc->sc_dev,
                           "error %d loading dma map\n", error);
               }
               return 1;
       }

       hdr = &ccb->ccb_frame->mfr_header;
       sgl = ccb->ccb_sgl;
       sgd = ccb->ccb_dmamap->dm_segs;
       for (i = 0; i < ccb->ccb_dmamap->dm_nsegs; i++) {
               if (((sc->sc_ioptype == MFI_IOP_SKINNY) ||
                       (sc->sc_ioptype == MFI_IOP_TBOLT)) &&
                   (hdr->mfh_cmd == MFI_CMD_PD_SCSI_IO ||
                    hdr->mfh_cmd == MFI_CMD_LD_READ ||
                    hdr->mfh_cmd == MFI_CMD_LD_WRITE)) {
                       sgl->sg_ieee[i].addr = htole64(sgd[i].ds_addr);
                       sgl->sg_ieee[i].len = htole32(sgd[i].ds_len);
                       sgl->sg_ieee[i].flags = 0;
                       DNPRINTF(MFI_D_DMA, "%s: addr: %#" PRIx64 " len: %#"
                           PRIx32 "\n",
                           DEVNAME(sc), sgl->sg64[i].addr, sgl->sg64[i].len);
                       hdr->mfh_flags |= MFI_FRAME_IEEE_SGL | MFI_FRAME_SGL64;
               } else if (sc->sc_64bit_dma) {
                       sgl->sg64[i].addr = htole64(sgd[i].ds_addr);
                       sgl->sg64[i].len = htole32(sgd[i].ds_len);
                       DNPRINTF(MFI_D_DMA, "%s: addr: %#" PRIx64 " len: %#"
                           PRIx32 "\n",
                           DEVNAME(sc), sgl->sg64[i].addr, sgl->sg64[i].len);
                       hdr->mfh_flags |= MFI_FRAME_SGL64;
               } else {
                       sgl->sg32[i].addr = htole32(sgd[i].ds_addr);
                       sgl->sg32[i].len = htole32(sgd[i].ds_len);
                       DNPRINTF(MFI_D_DMA, "%s: addr: %#x  len: %#x\n",
                           DEVNAME(sc), sgl->sg32[i].addr, sgl->sg32[i].len);
                       hdr->mfh_flags |= MFI_FRAME_SGL32;
               }
       }

       if (ccb->ccb_direction == MFI_DATA_IN) {
               hdr->mfh_flags |= MFI_FRAME_DIR_READ;
               bus_dmamap_sync(sc->sc_datadmat, ccb->ccb_dmamap, 0,
                   ccb->ccb_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
       } else {
               hdr->mfh_flags |= MFI_FRAME_DIR_WRITE;
               bus_dmamap_sync(sc->sc_datadmat, ccb->ccb_dmamap, 0,
                   ccb->ccb_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);
       }

       hdr->mfh_sg_count = ccb->ccb_dmamap->dm_nsegs;
       ccb->ccb_frame_size += sc->sc_sgl_size * ccb->ccb_dmamap->dm_nsegs;
       ccb->ccb_extra_frames = (ccb->ccb_frame_size - 1) / MFI_FRAME_SIZE;

       DNPRINTF(MFI_D_DMA, "%s: sg_count: %d  frame_size: %d  frames_size: %d"
           "  dm_nsegs: %d  extra_frames: %d\n",
           DEVNAME(sc),
           hdr->mfh_sg_count,
           ccb->ccb_frame_size,
           sc->sc_frames_size,
           ccb->ccb_dmamap->dm_nsegs,
           ccb->ccb_extra_frames);

       return 0;
}

static int
mfi_mgmt_internal(struct mfi_softc *sc, uint32_t opc, uint32_t dir,
   uint32_t len, void *buf, const union mfi_mbox *mbox, bool poll)
{
       struct mfi_ccb          *ccb;
       int                     rv = 1;

       if ((ccb = mfi_get_ccb(sc)) == NULL)
               return rv;
       rv = mfi_mgmt(ccb, NULL, opc, dir, len, buf, mbox);
       if (rv)
               return rv;

       if (poll) {
               rv = 1;
               if (mfi_poll(ccb))
                       goto done;
       } else {
               mfi_post(sc, ccb);

               DNPRINTF(MFI_D_MISC, "%s: mfi_mgmt_internal sleeping\n",
                   DEVNAME(sc));
               while (ccb->ccb_state != MFI_CCB_DONE)
                       tsleep(ccb, PRIBIO, "mfi_mgmt", 0);

               if (ccb->ccb_flags & MFI_CCB_F_ERR)
                       goto done;
       }
       rv = 0;

done:
       mfi_put_ccb(ccb);
       return rv;
}

static int
mfi_mgmt(struct mfi_ccb *ccb, struct scsipi_xfer *xs, uint32_t opc,
   uint32_t dir, uint32_t len, void *buf, const union mfi_mbox *mbox)
{
       struct mfi_dcmd_frame   *dcmd;

       DNPRINTF(MFI_D_MISC, "%s: mfi_mgmt %#x\n", DEVNAME(ccb->ccb_sc), opc);

       dcmd = &ccb->ccb_frame->mfr_dcmd;
       memset(dcmd->mdf_mbox.b, 0, MFI_MBOX_SIZE);
       dcmd->mdf_header.mfh_cmd = MFI_CMD_DCMD;
       dcmd->mdf_header.mfh_timeout = 0;

       dcmd->mdf_opcode = opc;
       dcmd->mdf_header.mfh_data_len = 0;
       ccb->ccb_direction = dir;
       ccb->ccb_xs = xs;
       ccb->ccb_done = mfi_mgmt_done;

       ccb->ccb_frame_size = MFI_DCMD_FRAME_SIZE;

       /* handle special opcodes */
       if (mbox)
               memcpy(dcmd->mdf_mbox.b, mbox, MFI_MBOX_SIZE);

       if (dir != MFI_DATA_NONE) {
               dcmd->mdf_header.mfh_data_len = len;
               ccb->ccb_data = buf;
               ccb->ccb_len = len;
               ccb->ccb_sgl = &dcmd->mdf_sgl;

               if (mfi_create_sgl(ccb, BUS_DMA_WAITOK))
                       return 1;
       }
       return 0;
}

static void
mfi_mgmt_done(struct mfi_ccb *ccb)
{
       struct scsipi_xfer      *xs = ccb->ccb_xs;
       struct mfi_softc        *sc = ccb->ccb_sc;
       struct mfi_frame_header *hdr = &ccb->ccb_frame->mfr_header;

       DNPRINTF(MFI_D_INTR, "%s: mfi_mgmt_done %#lx %#lx\n",
           DEVNAME(sc), (u_long)ccb, (u_long)ccb->ccb_frame);

       if (ccb->ccb_data != NULL) {
               DNPRINTF(MFI_D_INTR, "%s: mfi_mgmt_done sync\n",
                   DEVNAME(sc));
               bus_dmamap_sync(sc->sc_datadmat, ccb->ccb_dmamap, 0,
                   ccb->ccb_dmamap->dm_mapsize,
                   (ccb->ccb_direction & MFI_DATA_IN) ?
                   BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);

               bus_dmamap_unload(sc->sc_datadmat, ccb->ccb_dmamap);
       }

       if (hdr->mfh_cmd_status != MFI_STAT_OK)
               ccb->ccb_flags |= MFI_CCB_F_ERR;

       ccb->ccb_state = MFI_CCB_DONE;
       if (xs) {
               if (hdr->mfh_cmd_status != MFI_STAT_OK) {
                       xs->error = XS_DRIVER_STUFFUP;
               } else {
                       xs->error = XS_NOERROR;
                       xs->status = SCSI_OK;
                       xs->resid = 0;
               }
               mfi_put_ccb(ccb);
               scsipi_done(xs);
       } else
               wakeup(ccb);
}

#if NBIO > 0
int
mfi_ioctl(device_t dev, u_long cmd, void *addr)
{
       struct mfi_softc *sc = device_private(dev);
       int error = 0;
       int s;

       KERNEL_LOCK(1, curlwp);
       s = splbio();

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl ", DEVNAME(sc));

       switch (cmd) {
       case BIOCINQ:
               DNPRINTF(MFI_D_IOCTL, "inq\n");
               error = mfi_ioctl_inq(sc, (struct bioc_inq *)addr);
               break;

       case BIOCVOL:
               DNPRINTF(MFI_D_IOCTL, "vol\n");
               error = mfi_ioctl_vol(sc, (struct bioc_vol *)addr);
               break;

       case BIOCDISK:
               DNPRINTF(MFI_D_IOCTL, "disk\n");
               error = mfi_ioctl_disk(sc, (struct bioc_disk *)addr);
               break;

       case BIOCALARM:
               DNPRINTF(MFI_D_IOCTL, "alarm\n");
               error = mfi_ioctl_alarm(sc, (struct bioc_alarm *)addr);
               break;

       case BIOCBLINK:
               DNPRINTF(MFI_D_IOCTL, "blink\n");
               error = mfi_ioctl_blink(sc, (struct bioc_blink *)addr);
               break;

       case BIOCSETSTATE:
               DNPRINTF(MFI_D_IOCTL, "setstate\n");
               error = mfi_ioctl_setstate(sc, (struct bioc_setstate *)addr);
               break;

       default:
               DNPRINTF(MFI_D_IOCTL, " invalid ioctl\n");
               error = EINVAL;
       }
       splx(s);
       KERNEL_UNLOCK_ONE(curlwp);

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl return %x\n", DEVNAME(sc), error);
       return error;
}

static int
mfi_ioctl_inq(struct mfi_softc *sc, struct bioc_inq *bi)
{
       struct mfi_conf         *cfg;
       int                     rv = EINVAL;

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_inq\n", DEVNAME(sc));

       if (mfi_get_info(sc)) {
               DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_inq failed\n",
                   DEVNAME(sc));
               return EIO;
       }

       /* get figures */
       cfg = malloc(sizeof *cfg, M_DEVBUF, M_WAITOK);
       if (mfi_mgmt_internal(sc, MR_DCMD_CONF_GET, MFI_DATA_IN,
           sizeof *cfg, cfg, NULL, false))
               goto freeme;

       strlcpy(bi->bi_dev, DEVNAME(sc), sizeof(bi->bi_dev));
       bi->bi_novol = cfg->mfc_no_ld + cfg->mfc_no_hs;
       bi->bi_nodisk = sc->sc_info.mci_pd_disks_present;

       rv = 0;
freeme:
       free(cfg, M_DEVBUF);
       return rv;
}

static int
mfi_ioctl_vol(struct mfi_softc *sc, struct bioc_vol *bv)
{
       int                     i, per, rv = EINVAL;
       union mfi_mbox          mbox;

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_vol %#x\n",
           DEVNAME(sc), bv->bv_volid);

       if (mfi_mgmt_internal(sc, MR_DCMD_LD_GET_LIST, MFI_DATA_IN,
           sizeof(sc->sc_ld_list), &sc->sc_ld_list, NULL, false))
               goto done;

       i = bv->bv_volid;
       memset(&mbox, 0, sizeof(mbox));
       mbox.b[0] = sc->sc_ld_list.mll_list[i].mll_ld.mld_target;
       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_vol target %#x\n",
           DEVNAME(sc), mbox.b[0]);

       if (mfi_mgmt_internal(sc, MR_DCMD_LD_GET_INFO, MFI_DATA_IN,
           sizeof(sc->sc_ld_details), &sc->sc_ld_details, &mbox, false))
               goto done;

       if (bv->bv_volid >= sc->sc_ld_list.mll_no_ld) {
               /* go do hotspares */
               rv = mfi_bio_hs(sc, bv->bv_volid, MFI_MGMT_VD, bv);
               goto done;
       }

       strlcpy(bv->bv_dev, sc->sc_ld[i].ld_dev, sizeof(bv->bv_dev));

       switch (sc->sc_ld_list.mll_list[i].mll_state) {
       case MFI_LD_OFFLINE:
               bv->bv_status = BIOC_SVOFFLINE;
               break;

       case MFI_LD_PART_DEGRADED:
       case MFI_LD_DEGRADED:
               bv->bv_status = BIOC_SVDEGRADED;
               break;

       case MFI_LD_ONLINE:
               bv->bv_status = BIOC_SVONLINE;
               break;

       default:
               bv->bv_status = BIOC_SVINVALID;
               DNPRINTF(MFI_D_IOCTL, "%s: invalid logical disk state %#x\n",
                   DEVNAME(sc),
                   sc->sc_ld_list.mll_list[i].mll_state);
       }

       /* additional status can modify MFI status */
       switch (sc->sc_ld_details.mld_progress.mlp_in_prog) {
       case MFI_LD_PROG_CC:
               bv->bv_status = BIOC_SVSCRUB;
               per = (int)sc->sc_ld_details.mld_progress.mlp_cc.mp_progress;
               bv->bv_percent = (per * 100) / 0xffff;
               bv->bv_seconds =
                   sc->sc_ld_details.mld_progress.mlp_cc.mp_elapsed_seconds;
               break;

       case MFI_LD_PROG_BGI:
               bv->bv_status = BIOC_SVSCRUB;
               per = (int)sc->sc_ld_details.mld_progress.mlp_bgi.mp_progress;
               bv->bv_percent = (per * 100) / 0xffff;
               bv->bv_seconds =
                   sc->sc_ld_details.mld_progress.mlp_bgi.mp_elapsed_seconds;
               break;

       case MFI_LD_PROG_FGI:
       case MFI_LD_PROG_RECONSTRUCT:
               /* nothing yet */
               break;
       }

       /*
        * The RAID levels are determined per the SNIA DDF spec, this is only
        * a subset that is valid for the MFI controller.
        */
       bv->bv_level = sc->sc_ld_details.mld_cfg.mlc_parm.mpa_pri_raid;
       if (sc->sc_ld_details.mld_cfg.mlc_parm.mpa_sec_raid ==
           MFI_DDF_SRL_SPANNED)
               bv->bv_level *= 10;

       bv->bv_nodisk = sc->sc_ld_details.mld_cfg.mlc_parm.mpa_no_drv_per_span *
           sc->sc_ld_details.mld_cfg.mlc_parm.mpa_span_depth;

       bv->bv_size = sc->sc_ld_details.mld_size * 512; /* bytes per block */
       bv->bv_stripe_size =
           (512 << sc->sc_ld_details.mld_cfg.mlc_parm.mpa_stripe_size)
           / 1024; /* in KB */

       rv = 0;
done:
       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_vol done %x\n",
           DEVNAME(sc), rv);
       return rv;
}

static int
mfi_ioctl_disk(struct mfi_softc *sc, struct bioc_disk *bd)
{
       struct mfi_conf         *cfg;
       struct mfi_array        *ar;
       struct mfi_ld_cfg       *ld;
       struct mfi_pd_details   *pd;
       struct scsipi_inquiry_data *inqbuf;
       char                    vend[8+16+4+1];
       int                     i, rv = EINVAL;
       int                     arr, vol, disk;
       uint32_t                size;
       union mfi_mbox          mbox;

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_disk %#x\n",
           DEVNAME(sc), bd->bd_diskid);

       pd = malloc(sizeof *pd, M_DEVBUF, M_WAITOK | M_ZERO);

       /* send single element command to retrieve size for full structure */
       cfg = malloc(sizeof *cfg, M_DEVBUF, M_WAITOK);
       if (mfi_mgmt_internal(sc, MR_DCMD_CONF_GET, MFI_DATA_IN,
           sizeof *cfg, cfg, NULL, false))
               goto freeme;

       size = cfg->mfc_size;
       free(cfg, M_DEVBUF);

       /* memory for read config */
       cfg = malloc(size, M_DEVBUF, M_WAITOK|M_ZERO);
       if (mfi_mgmt_internal(sc, MR_DCMD_CONF_GET, MFI_DATA_IN,
           size, cfg, NULL, false))
               goto freeme;

       ar = cfg->mfc_array;

       /* calculate offset to ld structure */
       ld = (struct mfi_ld_cfg *)(
           ((uint8_t *)cfg) + offsetof(struct mfi_conf, mfc_array) +
           cfg->mfc_array_size * cfg->mfc_no_array);

       vol = bd->bd_volid;

       if (vol >= cfg->mfc_no_ld) {
               /* do hotspares */
               rv = mfi_bio_hs(sc, bd->bd_volid, MFI_MGMT_SD, bd);
               goto freeme;
       }

       /* find corresponding array for ld */
       for (i = 0, arr = 0; i < vol; i++)
               arr += ld[i].mlc_parm.mpa_span_depth;

       /* offset disk into pd list */
       disk = bd->bd_diskid % ld[vol].mlc_parm.mpa_no_drv_per_span;

       /* offset array index into the next spans */
       arr += bd->bd_diskid / ld[vol].mlc_parm.mpa_no_drv_per_span;

       bd->bd_target = ar[arr].pd[disk].mar_enc_slot;
       switch (ar[arr].pd[disk].mar_pd_state){
       case MFI_PD_UNCONFIG_GOOD:
               bd->bd_status = BIOC_SDUNUSED;
               break;

       case MFI_PD_HOTSPARE: /* XXX dedicated hotspare part of array? */
               bd->bd_status = BIOC_SDHOTSPARE;
               break;

       case MFI_PD_OFFLINE:
               bd->bd_status = BIOC_SDOFFLINE;
               break;

       case MFI_PD_FAILED:
               bd->bd_status = BIOC_SDFAILED;
               break;

       case MFI_PD_REBUILD:
               bd->bd_status = BIOC_SDREBUILD;
               break;

       case MFI_PD_ONLINE:
               bd->bd_status = BIOC_SDONLINE;
               break;

       case MFI_PD_UNCONFIG_BAD: /* XXX define new state in bio */
       default:
               bd->bd_status = BIOC_SDINVALID;
               break;
       }

       /* get the remaining fields */
       memset(&mbox, 0, sizeof(mbox));
       mbox.s[0] = ar[arr].pd[disk].mar_pd.mfp_id;
       memset(pd, 0, sizeof(*pd));
       if (mfi_mgmt_internal(sc, MR_DCMD_PD_GET_INFO, MFI_DATA_IN,
           sizeof *pd, pd, &mbox, false))
               goto freeme;

       bd->bd_size = pd->mpd_size * 512; /* bytes per block */

       /* if pd->mpd_enc_idx is 0 then it is not in an enclosure */
       bd->bd_channel = pd->mpd_enc_idx;

       inqbuf = (struct scsipi_inquiry_data *)&pd->mpd_inq_data;
       memcpy(vend, inqbuf->vendor, sizeof vend - 1);
       vend[sizeof vend - 1] = '\0';
       strlcpy(bd->bd_vendor, vend, sizeof(bd->bd_vendor));

       /* XXX find a way to retrieve serial nr from drive */
       /* XXX find a way to get bd_procdev */

       rv = 0;
freeme:
       free(pd, M_DEVBUF);
       free(cfg, M_DEVBUF);

       return rv;
}

static int
mfi_ioctl_alarm(struct mfi_softc *sc, struct bioc_alarm *ba)
{
       uint32_t                opc, dir = MFI_DATA_NONE;
       int                     rv = 0;
       int8_t                  ret;

       switch (ba->ba_opcode) {
       case BIOC_SADISABLE:
               opc = MR_DCMD_SPEAKER_DISABLE;
               break;

       case BIOC_SAENABLE:
               opc = MR_DCMD_SPEAKER_ENABLE;
               break;

       case BIOC_SASILENCE:
               opc = MR_DCMD_SPEAKER_SILENCE;
               break;

       case BIOC_GASTATUS:
               opc = MR_DCMD_SPEAKER_GET;
               dir = MFI_DATA_IN;
               break;

       case BIOC_SATEST:
               opc = MR_DCMD_SPEAKER_TEST;
               break;

       default:
               DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_alarm biocalarm invalid "
                   "opcode %x\n", DEVNAME(sc), ba->ba_opcode);
               return EINVAL;
       }

       if (mfi_mgmt_internal(sc, opc, dir, sizeof(ret), &ret, NULL, false))
               rv = EINVAL;
       else
               if (ba->ba_opcode == BIOC_GASTATUS)
                       ba->ba_status = ret;
               else
                       ba->ba_status = 0;

       return rv;
}

static int
mfi_ioctl_blink(struct mfi_softc *sc, struct bioc_blink *bb)
{
       int                     i, found, rv = EINVAL;
       union mfi_mbox          mbox;
       uint32_t                cmd;
       struct mfi_pd_list      *pd;

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_blink %x\n", DEVNAME(sc),
           bb->bb_status);

       /* channel 0 means not in an enclosure so can't be blinked */
       if (bb->bb_channel == 0)
               return EINVAL;

       pd = malloc(sizeof(*pd), M_DEVBUF, M_WAITOK);

       if (mfi_mgmt_internal(sc, MR_DCMD_PD_GET_LIST, MFI_DATA_IN,
           sizeof(*pd), pd, NULL, false))
               goto done;

       for (i = 0, found = 0; i < pd->mpl_no_pd; i++)
               if (bb->bb_channel == pd->mpl_address[i].mpa_enc_index &&
                   bb->bb_target == pd->mpl_address[i].mpa_enc_slot) {
                       found = 1;
                       break;
               }

       if (!found)
               goto done;

       memset(&mbox, 0, sizeof(mbox));
       mbox.s[0] = pd->mpl_address[i].mpa_pd_id;

       switch (bb->bb_status) {
       case BIOC_SBUNBLINK:
               cmd = MR_DCMD_PD_UNBLINK;
               break;

       case BIOC_SBBLINK:
               cmd = MR_DCMD_PD_BLINK;
               break;

       case BIOC_SBALARM:
       default:
               DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_blink biocblink invalid "
                   "opcode %x\n", DEVNAME(sc), bb->bb_status);
               goto done;
       }


       if (mfi_mgmt_internal(sc, cmd, MFI_DATA_NONE, 0, NULL, &mbox, false))
               goto done;

       rv = 0;
done:
       free(pd, M_DEVBUF);
       return rv;
}

static int
mfi_ioctl_setstate(struct mfi_softc *sc, struct bioc_setstate *bs)
{
       struct mfi_pd_list      *pd;
       int                     i, found, rv = EINVAL;
       union mfi_mbox          mbox;

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_setstate %x\n", DEVNAME(sc),
           bs->bs_status);

       pd = malloc(sizeof(*pd), M_DEVBUF, M_WAITOK);

       if (mfi_mgmt_internal(sc, MR_DCMD_PD_GET_LIST, MFI_DATA_IN,
           sizeof(*pd), pd, NULL, false))
               goto done;

       for (i = 0, found = 0; i < pd->mpl_no_pd; i++)
               if (bs->bs_channel == pd->mpl_address[i].mpa_enc_index &&
                   bs->bs_target == pd->mpl_address[i].mpa_enc_slot) {
                       found = 1;
                       break;
               }

       if (!found)
               goto done;

       memset(&mbox, 0, sizeof(mbox));
       mbox.s[0] = pd->mpl_address[i].mpa_pd_id;

       switch (bs->bs_status) {
       case BIOC_SSONLINE:
               mbox.b[4] = MFI_PD_ONLINE;
               break;

       case BIOC_SSOFFLINE:
               mbox.b[4] = MFI_PD_OFFLINE;
               break;

       case BIOC_SSHOTSPARE:
               mbox.b[4] = MFI_PD_HOTSPARE;
               break;
/*
       case BIOC_SSREBUILD:
               break;
*/
       default:
               DNPRINTF(MFI_D_IOCTL, "%s: mfi_ioctl_setstate invalid "
                   "opcode %x\n", DEVNAME(sc), bs->bs_status);
               goto done;
       }


       if (mfi_mgmt_internal(sc, MR_DCMD_PD_SET_STATE, MFI_DATA_NONE,
           0, NULL, &mbox, false))
               goto done;

       rv = 0;
done:
       free(pd, M_DEVBUF);
       return rv;
}

static int
mfi_bio_hs(struct mfi_softc *sc, int volid, int type, void *bio_hs)
{
       struct mfi_conf         *cfg;
       struct mfi_hotspare     *hs;
       struct mfi_pd_details   *pd;
       struct bioc_disk        *sdhs;
       struct bioc_vol         *vdhs;
       struct scsipi_inquiry_data *inqbuf;
       char                    vend[8+16+4+1];
       int                     i, rv = EINVAL;
       uint32_t                size;
       union mfi_mbox          mbox;

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_vol_hs %d\n", DEVNAME(sc), volid);

       if (!bio_hs)
               return EINVAL;

       pd = malloc(sizeof *pd, M_DEVBUF, M_WAITOK | M_ZERO);

       /* send single element command to retrieve size for full structure */
       cfg = malloc(sizeof *cfg, M_DEVBUF, M_WAITOK);
       if (mfi_mgmt_internal(sc, MR_DCMD_CONF_GET, MFI_DATA_IN,
           sizeof *cfg, cfg, NULL, false))
               goto freeme;

       size = cfg->mfc_size;
       free(cfg, M_DEVBUF);

       /* memory for read config */
       cfg = malloc(size, M_DEVBUF, M_WAITOK|M_ZERO);
       if (mfi_mgmt_internal(sc, MR_DCMD_CONF_GET, MFI_DATA_IN,
           size, cfg, NULL, false))
               goto freeme;

       /* calculate offset to hs structure */
       hs = (struct mfi_hotspare *)(
           ((uint8_t *)cfg) + offsetof(struct mfi_conf, mfc_array) +
           cfg->mfc_array_size * cfg->mfc_no_array +
           cfg->mfc_ld_size * cfg->mfc_no_ld);

       if (volid < cfg->mfc_no_ld)
               goto freeme; /* not a hotspare */

       if (volid > (cfg->mfc_no_ld + cfg->mfc_no_hs))
               goto freeme; /* not a hotspare */

       /* offset into hotspare structure */
       i = volid - cfg->mfc_no_ld;

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_vol_hs i %d volid %d no_ld %d no_hs %d "
           "hs %p cfg %p id %02x\n", DEVNAME(sc), i, volid, cfg->mfc_no_ld,
           cfg->mfc_no_hs, hs, cfg, hs[i].mhs_pd.mfp_id);

       /* get pd fields */
       memset(&mbox, 0, sizeof(mbox));
       mbox.s[0] = hs[i].mhs_pd.mfp_id;
       if (mfi_mgmt_internal(sc, MR_DCMD_PD_GET_INFO, MFI_DATA_IN,
           sizeof *pd, pd, &mbox, false)) {
               DNPRINTF(MFI_D_IOCTL, "%s: mfi_vol_hs illegal PD\n",
                   DEVNAME(sc));
               goto freeme;
       }

       switch (type) {
       case MFI_MGMT_VD:
               vdhs = bio_hs;
               vdhs->bv_status = BIOC_SVONLINE;
               vdhs->bv_size = pd->mpd_size * 512; /* bytes per block */
               vdhs->bv_level = -1; /* hotspare */
               vdhs->bv_nodisk = 1;
               break;

       case MFI_MGMT_SD:
               sdhs = bio_hs;
               sdhs->bd_status = BIOC_SDHOTSPARE;
               sdhs->bd_size = pd->mpd_size * 512; /* bytes per block */
               sdhs->bd_channel = pd->mpd_enc_idx;
               sdhs->bd_target = pd->mpd_enc_slot;
               inqbuf = (struct scsipi_inquiry_data *)&pd->mpd_inq_data;
               memcpy(vend, inqbuf->vendor, sizeof(vend) - 1);
               vend[sizeof vend - 1] = '\0';
               strlcpy(sdhs->bd_vendor, vend, sizeof(sdhs->bd_vendor));
               break;

       default:
               goto freeme;
       }

       DNPRINTF(MFI_D_IOCTL, "%s: mfi_vol_hs 6\n", DEVNAME(sc));
       rv = 0;
freeme:
       free(pd, M_DEVBUF);
       free(cfg, M_DEVBUF);

       return rv;
}

static int
mfi_destroy_sensors(struct mfi_softc *sc)
{
       if (sc->sc_sme == NULL)
               return 0;
       sysmon_envsys_unregister(sc->sc_sme);
       sc->sc_sme = NULL;
       free(sc->sc_sensor, M_DEVBUF);
       return 0;
}

static int
mfi_create_sensors(struct mfi_softc *sc)
{
       int i;
       int nsensors = sc->sc_ld_cnt + 1;
       int rv;

       sc->sc_sme = sysmon_envsys_create();
       sc->sc_sensor = malloc(sizeof(envsys_data_t) * nsensors,
           M_DEVBUF, M_WAITOK | M_ZERO);

       /* BBU */
       sc->sc_sensor[0].units = ENVSYS_INDICATOR;
       sc->sc_sensor[0].state = ENVSYS_SINVALID;
       sc->sc_sensor[0].value_cur = 0;
       /* Enable monitoring for BBU state changes, if present */
       if (sc->sc_info.mci_hw_present & MFI_INFO_HW_BBU)
               sc->sc_sensor[0].flags |= ENVSYS_FMONCRITICAL;
       snprintf(sc->sc_sensor[0].desc,
           sizeof(sc->sc_sensor[0].desc), "%s BBU", DEVNAME(sc));
       if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[0]))
               goto out;

       for (i = 1; i < nsensors; i++) {
               sc->sc_sensor[i].units = ENVSYS_DRIVE;
               sc->sc_sensor[i].state = ENVSYS_SINVALID;
               sc->sc_sensor[i].value_cur = ENVSYS_DRIVE_EMPTY;
               /* Enable monitoring for drive state changes */
               sc->sc_sensor[i].flags |= ENVSYS_FMONSTCHANGED;
               /* logical drives */
               snprintf(sc->sc_sensor[i].desc,
                   sizeof(sc->sc_sensor[i].desc), "%s:%d",
                   DEVNAME(sc), i - 1);
               if (sysmon_envsys_sensor_attach(sc->sc_sme,
                                               &sc->sc_sensor[i]))
                       goto out;
       }

       sc->sc_sme->sme_name = DEVNAME(sc);
       sc->sc_sme->sme_cookie = sc;
       sc->sc_sme->sme_refresh = mfi_sensor_refresh;
       rv = sysmon_envsys_register(sc->sc_sme);
       if (rv != 0) {
               aprint_error_dev(sc->sc_dev,
                   "unable to register with sysmon (rv = %d)\n", rv);
               goto out;
       }
       return 0;

out:
       free(sc->sc_sensor, M_DEVBUF);
       sysmon_envsys_destroy(sc->sc_sme);
       sc->sc_sme = NULL;
       return EINVAL;
}

static void
mfi_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
       struct mfi_softc        *sc = sme->sme_cookie;
       struct bioc_vol         bv;
       int s;
       int error;

       if (edata->sensor >= sc->sc_ld_cnt + 1)
               return;

       if (edata->sensor == 0) {
               /* BBU */
               struct mfi_bbu_status   bbu_stat;
               int bbu_status;
               if ((sc->sc_info.mci_hw_present & MFI_INFO_HW_BBU) == 0)
                       return;

               KERNEL_LOCK(1, curlwp);
               s = splbio();
               bbu_status = mfi_get_bbu(sc, &bbu_stat);
               splx(s);
               KERNEL_UNLOCK_ONE(curlwp);
               switch (bbu_status) {
               case MFI_BBU_GOOD:
                       edata->value_cur = 1;
                       edata->state = ENVSYS_SVALID;
                       if (!sc->sc_bbuok)
                               aprint_normal_dev(sc->sc_dev,
                                   "BBU state changed to good\n");
                       sc->sc_bbuok = true;
                       break;
               case MFI_BBU_BAD:
                       edata->value_cur = 0;
                       edata->state = ENVSYS_SCRITICAL;
                       if (sc->sc_bbuok)
                               aprint_normal_dev(sc->sc_dev,
                                   "BBU state changed to bad\n");
                       sc->sc_bbuok = false;
                       break;
               case MFI_BBU_UNKNOWN:
               default:
                       edata->value_cur = 0;
                       edata->state = ENVSYS_SINVALID;
                       sc->sc_bbuok = false;
                       break;
               }
               return;
       }

       memset(&bv, 0, sizeof(bv));
       bv.bv_volid = edata->sensor - 1;
       KERNEL_LOCK(1, curlwp);
       s = splbio();
       error = mfi_ioctl_vol(sc, &bv);
       splx(s);
       KERNEL_UNLOCK_ONE(curlwp);
       if (error)
               bv.bv_status = BIOC_SVINVALID;

       bio_vol_to_envsys(edata, &bv);
}

#endif /* NBIO > 0 */

static uint32_t
mfi_xscale_fw_state(struct mfi_softc *sc)
{
       return mfi_read(sc, MFI_OMSG0);
}

static void
mfi_xscale_intr_dis(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_OMSK, 0);
}

static void
mfi_xscale_intr_ena(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_OMSK, MFI_ENABLE_INTR);
}

static int
mfi_xscale_intr(struct mfi_softc *sc)
{
       uint32_t status;

       status = mfi_read(sc, MFI_OSTS);
       if (!ISSET(status, MFI_OSTS_INTR_VALID))
               return 0;

       /* write status back to acknowledge interrupt */
       mfi_write(sc, MFI_OSTS, status);
       return 1;
}

static void
mfi_xscale_post(struct mfi_softc *sc, struct mfi_ccb *ccb)
{
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_frames),
           ccb->ccb_pframe - MFIMEM_DVA(sc->sc_frames),
           sc->sc_frames_size, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_sense),
           ccb->ccb_psense - MFIMEM_DVA(sc->sc_sense),
           MFI_SENSE_SIZE, BUS_DMASYNC_PREREAD);

       mfi_write(sc, MFI_IQP, (ccb->ccb_pframe >> 3) |
           ccb->ccb_extra_frames);
       ccb->ccb_state = MFI_CCB_RUNNING;
}

static uint32_t
mfi_ppc_fw_state(struct mfi_softc *sc)
{
       return mfi_read(sc, MFI_OSP);
}

static void
mfi_ppc_intr_dis(struct mfi_softc *sc)
{
       /* Taking a wild guess --dyoung */
       mfi_write(sc, MFI_OMSK, ~(uint32_t)0x0);
       mfi_write(sc, MFI_ODC, 0xffffffff);
}

static void
mfi_ppc_intr_ena(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_ODC, 0xffffffff);
       mfi_write(sc, MFI_OMSK, ~0x80000004);
}

static int
mfi_ppc_intr(struct mfi_softc *sc)
{
       uint32_t status;

       status = mfi_read(sc, MFI_OSTS);
       if (!ISSET(status, MFI_OSTS_PPC_INTR_VALID))
               return 0;

       /* write status back to acknowledge interrupt */
       mfi_write(sc, MFI_ODC, status);
       return 1;
}

static void
mfi_ppc_post(struct mfi_softc *sc, struct mfi_ccb *ccb)
{
       mfi_write(sc, MFI_IQP, 0x1 | ccb->ccb_pframe |
           (ccb->ccb_extra_frames << 1));
       ccb->ccb_state = MFI_CCB_RUNNING;
}

u_int32_t
mfi_gen2_fw_state(struct mfi_softc *sc)
{
       return (mfi_read(sc, MFI_OSP));
}

void
mfi_gen2_intr_dis(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_OMSK, 0xffffffff);
       mfi_write(sc, MFI_ODC, 0xffffffff);
}

void
mfi_gen2_intr_ena(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_ODC, 0xffffffff);
       mfi_write(sc, MFI_OMSK, ~MFI_OSTS_GEN2_INTR_VALID);
}

int
mfi_gen2_intr(struct mfi_softc *sc)
{
       u_int32_t status;

       status = mfi_read(sc, MFI_OSTS);
       if (!ISSET(status, MFI_OSTS_GEN2_INTR_VALID))
               return (0);

       /* write status back to acknowledge interrupt */
       mfi_write(sc, MFI_ODC, status);

       return (1);
}

void
mfi_gen2_post(struct mfi_softc *sc, struct mfi_ccb *ccb)
{
       mfi_write(sc, MFI_IQP, 0x1 | ccb->ccb_pframe |
           (ccb->ccb_extra_frames << 1));
       ccb->ccb_state = MFI_CCB_RUNNING;
}

u_int32_t
mfi_skinny_fw_state(struct mfi_softc *sc)
{
       return (mfi_read(sc, MFI_OSP));
}

void
mfi_skinny_intr_dis(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_OMSK, 0);
}

void
mfi_skinny_intr_ena(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_OMSK, ~0x00000001);
}

int
mfi_skinny_intr(struct mfi_softc *sc)
{
       u_int32_t status;

       status = mfi_read(sc, MFI_OSTS);
       if (!ISSET(status, MFI_OSTS_SKINNY_INTR_VALID))
               return (0);

       /* write status back to acknowledge interrupt */
       mfi_write(sc, MFI_OSTS, status);

       return (1);
}

void
mfi_skinny_post(struct mfi_softc *sc, struct mfi_ccb *ccb)
{
       mfi_write(sc, MFI_IQPL, 0x1 | ccb->ccb_pframe |
           (ccb->ccb_extra_frames << 1));
       mfi_write(sc, MFI_IQPH, 0x00000000);
       ccb->ccb_state = MFI_CCB_RUNNING;
}

#define MFI_FUSION_ENABLE_INTERRUPT_MASK        (0x00000008)

void
mfi_tbolt_intr_ena(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_OMSK, ~MFI_FUSION_ENABLE_INTERRUPT_MASK);
       mfi_read(sc, MFI_OMSK);
}

void
mfi_tbolt_intr_dis(struct mfi_softc *sc)
{
       mfi_write(sc, MFI_OMSK, 0xFFFFFFFF);
       mfi_read(sc, MFI_OMSK);
}

int
mfi_tbolt_intr(struct mfi_softc *sc)
{
       int32_t status;

       status = mfi_read(sc, MFI_OSTS);

       if (ISSET(status, 0x1)) {
               mfi_write(sc, MFI_OSTS, status);
               mfi_read(sc, MFI_OSTS);
               if (ISSET(status, MFI_STATE_CHANGE_INTERRUPT))
                       return 0;
               return 1;
       }
       if (!ISSET(status, MFI_FUSION_ENABLE_INTERRUPT_MASK))
               return 0;
       mfi_read(sc, MFI_OSTS);
       return 1;
}

u_int32_t
mfi_tbolt_fw_state(struct mfi_softc *sc)
{
       return mfi_read(sc, MFI_OSP);
}

void
mfi_tbolt_post(struct mfi_softc *sc, struct mfi_ccb *ccb)
{
       if (sc->sc_MFA_enabled) {
               if ((ccb->ccb_flags & MFI_CCB_F_TBOLT) == 0)
                       mfi_tbolt_build_mpt_ccb(ccb);
               mfi_write(sc, MFI_IQPL,
                   ccb->ccb_tb_request_desc.words & 0xFFFFFFFF);
               mfi_write(sc, MFI_IQPH,
                   ccb->ccb_tb_request_desc.words >> 32);
               ccb->ccb_state = MFI_CCB_RUNNING;
               return;
       }
       uint64_t bus_add = ccb->ccb_pframe;
       bus_add |= (MFI_REQ_DESCRIPT_FLAGS_MFA
           << MFI_REQ_DESCRIPT_FLAGS_TYPE_SHIFT);
       mfi_write(sc, MFI_IQPL, bus_add);
       mfi_write(sc, MFI_IQPH, bus_add >> 32);
       ccb->ccb_state = MFI_CCB_RUNNING;
}

static void
mfi_tbolt_build_mpt_ccb(struct mfi_ccb *ccb)
{
       union mfi_mpi2_request_descriptor *req_desc = &ccb->ccb_tb_request_desc;
       struct mfi_mpi2_request_raid_scsi_io *io_req = ccb->ccb_tb_io_request;
       struct mpi25_ieee_sge_chain64 *mpi25_ieee_chain;

       io_req->Function = MPI2_FUNCTION_PASSTHRU_IO_REQUEST;
       io_req->SGLOffset0 =
           offsetof(struct mfi_mpi2_request_raid_scsi_io, SGL) / 4;
       io_req->ChainOffset =
           offsetof(struct mfi_mpi2_request_raid_scsi_io, SGL) / 16;

       mpi25_ieee_chain =
           (struct mpi25_ieee_sge_chain64 *)&io_req->SGL.IeeeChain;
       mpi25_ieee_chain->Address = ccb->ccb_pframe;

       /*
         In MFI pass thru, nextChainOffset will always be zero to
         indicate the end of the chain.
       */
       mpi25_ieee_chain->Flags= MPI2_IEEE_SGE_FLAGS_CHAIN_ELEMENT
               | MPI2_IEEE_SGE_FLAGS_IOCPLBNTA_ADDR;

       /* setting the length to the maximum length */
       mpi25_ieee_chain->Length = 1024;

       req_desc->header.RequestFlags = (MPI2_REQ_DESCRIPT_FLAGS_SCSI_IO <<
           MFI_REQ_DESCRIPT_FLAGS_TYPE_SHIFT);
       ccb->ccb_flags |= MFI_CCB_F_TBOLT;
       bus_dmamap_sync(ccb->ccb_sc->sc_dmat,
           MFIMEM_MAP(ccb->ccb_sc->sc_tbolt_reqmsgpool),
           ccb->ccb_tb_pio_request -
            MFIMEM_DVA(ccb->ccb_sc->sc_tbolt_reqmsgpool),
           MEGASAS_THUNDERBOLT_NEW_MSG_SIZE,
           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}

/*
* Description:
*      This function will prepare message pools for the Thunderbolt controller
*/
static int
mfi_tbolt_init_desc_pool(struct mfi_softc *sc)
{
       uint32_t     offset = 0;
       uint8_t      *addr = MFIMEM_KVA(sc->sc_tbolt_reqmsgpool);

       /* Request Descriptors alignment restrictions */
       KASSERT(((uintptr_t)addr & 0xFF) == 0);

       /* Skip request message pool */
       addr = &addr[MEGASAS_THUNDERBOLT_NEW_MSG_SIZE * (sc->sc_max_cmds + 1)];

       /* Reply Frame Pool is initialized */
       sc->sc_reply_frame_pool = (struct mfi_mpi2_reply_header *) addr;
       KASSERT(((uintptr_t)addr & 0xFF) == 0);

       offset = (uintptr_t)sc->sc_reply_frame_pool
           - (uintptr_t)MFIMEM_KVA(sc->sc_tbolt_reqmsgpool);
       sc->sc_reply_frame_busaddr =
           MFIMEM_DVA(sc->sc_tbolt_reqmsgpool) + offset;

       /* initializing reply address to 0xFFFFFFFF */
       memset((uint8_t *)sc->sc_reply_frame_pool, 0xFF,
              (MEGASAS_THUNDERBOLT_REPLY_SIZE * sc->sc_reply_pool_size));

       /* Skip Reply Frame Pool */
       addr += MEGASAS_THUNDERBOLT_REPLY_SIZE * sc->sc_reply_pool_size;
       sc->sc_reply_pool_limit = (void *)addr;

       offset = MEGASAS_THUNDERBOLT_REPLY_SIZE * sc->sc_reply_pool_size;
       sc->sc_sg_frame_busaddr = sc->sc_reply_frame_busaddr + offset;

       /* initialize the last_reply_idx to 0 */
       sc->sc_last_reply_idx = 0;
       offset = (sc->sc_sg_frame_busaddr + (MEGASAS_MAX_SZ_CHAIN_FRAME *
           sc->sc_max_cmds)) - MFIMEM_DVA(sc->sc_tbolt_reqmsgpool);
       KASSERT(offset <= sc->sc_tbolt_reqmsgpool->am_size);
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_tbolt_reqmsgpool), 0,
           MFIMEM_MAP(sc->sc_tbolt_reqmsgpool)->dm_mapsize,
           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
       return 0;
}

/*
* This routine prepare and issue INIT2 frame to the Firmware
*/

static int
mfi_tbolt_init_MFI_queue(struct mfi_softc *sc)
{
       struct mpi2_ioc_init_request   *mpi2IocInit;
       struct mfi_init_frame           *mfi_init;
       struct mfi_ccb                  *ccb;
       bus_addr_t                      phyAddress;
       mfi_address                     *mfiAddressTemp;
       int                             s;
       char                            *verbuf;
       char                            wqbuf[10];

       /* Check if initialization is already completed */
       if (sc->sc_MFA_enabled) {
               return 1;
       }

       mpi2IocInit =
           (struct mpi2_ioc_init_request *)MFIMEM_KVA(sc->sc_tbolt_ioc_init);

       s = splbio();
       if ((ccb = mfi_get_ccb(sc)) == NULL) {
               splx(s);
               return (EBUSY);
       }


       mfi_init = &ccb->ccb_frame->mfr_init;

       memset(mpi2IocInit, 0, sizeof(struct mpi2_ioc_init_request));
       mpi2IocInit->Function  = MPI2_FUNCTION_IOC_INIT;
       mpi2IocInit->WhoInit   = MPI2_WHOINIT_HOST_DRIVER;

       /* set MsgVersion and HeaderVersion host driver was built with */
       mpi2IocInit->MsgVersion = MPI2_VERSION;
       mpi2IocInit->HeaderVersion = MPI2_HEADER_VERSION;
       mpi2IocInit->SystemRequestFrameSize = MEGASAS_THUNDERBOLT_NEW_MSG_SIZE/4;
       mpi2IocInit->ReplyDescriptorPostQueueDepth =
           (uint16_t)sc->sc_reply_pool_size;
       mpi2IocInit->ReplyFreeQueueDepth = 0; /* Not supported by MR. */

       /* Get physical address of reply frame pool */
       phyAddress = sc->sc_reply_frame_busaddr;
       mfiAddressTemp =
           (mfi_address *)&mpi2IocInit->ReplyDescriptorPostQueueAddress;
       mfiAddressTemp->u.addressLow = (uint32_t)phyAddress;
       mfiAddressTemp->u.addressHigh = (uint32_t)((uint64_t)phyAddress >> 32);

       /* Get physical address of request message pool */
       phyAddress =  MFIMEM_DVA(sc->sc_tbolt_reqmsgpool);
       mfiAddressTemp = (mfi_address *)&mpi2IocInit->SystemRequestFrameBaseAddress;
       mfiAddressTemp->u.addressLow = (uint32_t)phyAddress;
       mfiAddressTemp->u.addressHigh = (uint32_t)((uint64_t)phyAddress >> 32);

       mpi2IocInit->ReplyFreeQueueAddress =  0; /* Not supported by MR. */
       mpi2IocInit->TimeStamp = time_uptime;

       verbuf = MFIMEM_KVA(sc->sc_tbolt_verbuf);
       snprintf(verbuf, strlen(MEGASAS_VERSION) + 2, "%s\n",
           MEGASAS_VERSION);
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_tbolt_verbuf), 0,
           MFIMEM_MAP(sc->sc_tbolt_verbuf)->dm_mapsize, BUS_DMASYNC_PREWRITE);
       mfi_init->driver_ver_lo = htole32(MFIMEM_DVA(sc->sc_tbolt_verbuf));
       mfi_init->driver_ver_hi =
                   htole32((uint64_t)MFIMEM_DVA(sc->sc_tbolt_verbuf) >> 32);

       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_tbolt_ioc_init), 0,
           MFIMEM_MAP(sc->sc_tbolt_ioc_init)->dm_mapsize,
           BUS_DMASYNC_PREWRITE);
       /* Get the physical address of the mpi2 ioc init command */
       phyAddress =  MFIMEM_DVA(sc->sc_tbolt_ioc_init);
       mfi_init->mif_qinfo_new_addr_lo = htole32(phyAddress);
       mfi_init->mif_qinfo_new_addr_hi = htole32((uint64_t)phyAddress >> 32);

       mfi_init->mif_header.mfh_cmd = MFI_CMD_INIT;
       mfi_init->mif_header.mfh_data_len = sizeof(struct mpi2_ioc_init_request);
       if (mfi_poll(ccb) != 0) {
               aprint_error_dev(sc->sc_dev, "failed to send IOC init2 "
                   "command at 0x%" PRIx64 "\n",
                   (uint64_t)ccb->ccb_pframe);
               splx(s);
               return 1;
       }
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_tbolt_verbuf), 0,
           MFIMEM_MAP(sc->sc_tbolt_verbuf)->dm_mapsize, BUS_DMASYNC_POSTWRITE);
       bus_dmamap_sync(sc->sc_dmat, MFIMEM_MAP(sc->sc_tbolt_ioc_init), 0,
           MFIMEM_MAP(sc->sc_tbolt_ioc_init)->dm_mapsize,
           BUS_DMASYNC_POSTWRITE);
       mfi_put_ccb(ccb);
       splx(s);

       if (mfi_init->mif_header.mfh_cmd_status == 0) {
               sc->sc_MFA_enabled = 1;
       }
       else {
               aprint_error_dev(sc->sc_dev, "Init command Failed %x\n",
                   mfi_init->mif_header.mfh_cmd_status);
               return 1;
       }

       snprintf(wqbuf, sizeof(wqbuf), "%swq", DEVNAME(sc));
       if (workqueue_create(&sc->sc_ldsync_wq, wqbuf, mfi_tbolt_sync_map_info,
           sc, PRIBIO, IPL_BIO, 0) != 0) {
               aprint_error_dev(sc->sc_dev, "workqueue_create failed\n");
               return 1;
       }
       workqueue_enqueue(sc->sc_ldsync_wq, &sc->sc_ldsync_wk, NULL);
       return 0;
}

int
mfi_tbolt_intrh(void *arg)
{
       struct mfi_softc        *sc = arg;
       struct mfi_ccb          *ccb;
       union mfi_mpi2_reply_descriptor *desc;
       int smid, num_completed;

       if (!mfi_tbolt_intr(sc))
               return 0;

       DNPRINTF(MFI_D_INTR, "%s: mfi_tbolt_intrh %#lx %#lx\n", DEVNAME(sc),
           (u_long)sc, (u_long)sc->sc_last_reply_idx);

       KASSERT(sc->sc_last_reply_idx < sc->sc_reply_pool_size);

       desc = (union mfi_mpi2_reply_descriptor *)
           ((uintptr_t)sc->sc_reply_frame_pool +
            sc->sc_last_reply_idx * MEGASAS_THUNDERBOLT_REPLY_SIZE);

       bus_dmamap_sync(sc->sc_dmat,
           MFIMEM_MAP(sc->sc_tbolt_reqmsgpool),
           MEGASAS_THUNDERBOLT_NEW_MSG_SIZE * (sc->sc_max_cmds + 1),
           MEGASAS_THUNDERBOLT_REPLY_SIZE * sc->sc_reply_pool_size,
           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
       num_completed = 0;
       while ((desc->header.ReplyFlags & MPI2_RPY_DESCRIPT_FLAGS_TYPE_MASK) !=
           MPI2_RPY_DESCRIPT_FLAGS_UNUSED) {
               smid = desc->header.SMID;
               KASSERT(smid > 0 && smid <= sc->sc_max_cmds);
               ccb = &sc->sc_ccb[smid - 1];
               DNPRINTF(MFI_D_INTR,
                   "%s: mfi_tbolt_intr SMID %#x reply_idx %#x "
                   "desc %#" PRIx64 " ccb %p\n", DEVNAME(sc), smid,
                   sc->sc_last_reply_idx, desc->words, ccb);
               KASSERT(ccb->ccb_state == MFI_CCB_RUNNING);
               if (ccb->ccb_flags & MFI_CCB_F_TBOLT_IO &&
                   ccb->ccb_tb_io_request->ChainOffset != 0) {
                       bus_dmamap_sync(sc->sc_dmat,
                           MFIMEM_MAP(sc->sc_tbolt_reqmsgpool),
                           ccb->ccb_tb_psg_frame -
                               MFIMEM_DVA(sc->sc_tbolt_reqmsgpool),
                           MEGASAS_MAX_SZ_CHAIN_FRAME,  BUS_DMASYNC_POSTREAD);
               }
               if (ccb->ccb_flags & MFI_CCB_F_TBOLT_IO) {
                       bus_dmamap_sync(sc->sc_dmat,
                           MFIMEM_MAP(sc->sc_tbolt_reqmsgpool),
                           ccb->ccb_tb_pio_request -
                               MFIMEM_DVA(sc->sc_tbolt_reqmsgpool),
                           MEGASAS_THUNDERBOLT_NEW_MSG_SIZE,
                           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
               }
               if (ccb->ccb_done)
                       ccb->ccb_done(ccb);
               else
                       ccb->ccb_state = MFI_CCB_DONE;
               sc->sc_last_reply_idx++;
               if (sc->sc_last_reply_idx >= sc->sc_reply_pool_size) {
                       sc->sc_last_reply_idx = 0;
               }
               desc->words = ~0x0;
               /* Get the next reply descriptor */
               desc = (union mfi_mpi2_reply_descriptor *)
                   ((uintptr_t)sc->sc_reply_frame_pool +
                    sc->sc_last_reply_idx * MEGASAS_THUNDERBOLT_REPLY_SIZE);
               num_completed++;
       }
       if (num_completed == 0)
               return 0;

       bus_dmamap_sync(sc->sc_dmat,
           MFIMEM_MAP(sc->sc_tbolt_reqmsgpool),
           MEGASAS_THUNDERBOLT_NEW_MSG_SIZE * (sc->sc_max_cmds + 1),
           MEGASAS_THUNDERBOLT_REPLY_SIZE * sc->sc_reply_pool_size,
           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
       mfi_write(sc, MFI_RPI, sc->sc_last_reply_idx);
       return 1;
}


int
mfi_tbolt_scsi_ld_io(struct mfi_ccb *ccb, struct scsipi_xfer *xs,
   uint64_t blockno, uint32_t blockcnt)
{
       struct scsipi_periph *periph = xs->xs_periph;
       struct mfi_mpi2_request_raid_scsi_io    *io_req;
       int sge_count;

       DNPRINTF(MFI_D_CMD, "%s: mfi_tbolt_scsi_ld_io: %d\n",
           device_xname(periph->periph_channel->chan_adapter->adapt_dev),
           periph->periph_target);

       if (!xs->data)
               return 1;

       ccb->ccb_done = mfi_tbolt_scsi_ld_done;
       ccb->ccb_xs = xs;
       ccb->ccb_data = xs->data;
       ccb->ccb_len = xs->datalen;

       io_req = ccb->ccb_tb_io_request;

       /* Just the CDB length,rest of the Flags are zero */
       io_req->IoFlags = xs->cmdlen;
       memset(io_req->CDB.CDB32, 0, 32);
       memcpy(io_req->CDB.CDB32, &xs->cmdstore, xs->cmdlen);

       io_req->RaidContext.TargetID = periph->periph_target;
       io_req->RaidContext.Status = 0;
       io_req->RaidContext.exStatus = 0;
       io_req->RaidContext.timeoutValue = MFI_FUSION_FP_DEFAULT_TIMEOUT;
       io_req->Function = MPI2_FUNCTION_LD_IO_REQUEST;
       io_req->DevHandle = periph->periph_target;

       ccb->ccb_tb_request_desc.header.RequestFlags =
           (MFI_REQ_DESCRIPT_FLAGS_LD_IO << MFI_REQ_DESCRIPT_FLAGS_TYPE_SHIFT);
       io_req->DataLength = blockcnt * MFI_SECTOR_LEN;

       if (xs->xs_control & XS_CTL_DATA_IN) {
               io_req->Control = MPI2_SCSIIO_CONTROL_READ;
               ccb->ccb_direction = MFI_DATA_IN;
       } else {
               io_req->Control = MPI2_SCSIIO_CONTROL_WRITE;
               ccb->ccb_direction = MFI_DATA_OUT;
       }

       sge_count = mfi_tbolt_create_sgl(ccb,
           (xs->xs_control & XS_CTL_NOSLEEP) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK
           );
       if (sge_count < 0)
               return 1;
       KASSERT(sge_count <= ccb->ccb_sc->sc_max_sgl);
       io_req->RaidContext.numSGE = sge_count;
       io_req->SGLFlags = MPI2_SGE_FLAGS_64_BIT_ADDRESSING;
       io_req->SGLOffset0 =
           offsetof(struct mfi_mpi2_request_raid_scsi_io, SGL) / 4;

       io_req->SenseBufferLowAddress = htole32(ccb->ccb_psense);
       io_req->SenseBufferLength = MFI_SENSE_SIZE;

       ccb->ccb_flags |= MFI_CCB_F_TBOLT | MFI_CCB_F_TBOLT_IO;
       bus_dmamap_sync(ccb->ccb_sc->sc_dmat,
           MFIMEM_MAP(ccb->ccb_sc->sc_tbolt_reqmsgpool),
           ccb->ccb_tb_pio_request -
            MFIMEM_DVA(ccb->ccb_sc->sc_tbolt_reqmsgpool),
           MEGASAS_THUNDERBOLT_NEW_MSG_SIZE,
           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

       return 0;
}


static void
mfi_tbolt_scsi_ld_done(struct mfi_ccb *ccb)
{
       struct mfi_mpi2_request_raid_scsi_io *io_req = ccb->ccb_tb_io_request;
       mfi_scsi_xs_done(ccb, io_req->RaidContext.Status,
           io_req->RaidContext.exStatus);
}

static int
mfi_tbolt_create_sgl(struct mfi_ccb *ccb, int flags)
{
       struct mfi_softc        *sc = ccb->ccb_sc;
       bus_dma_segment_t       *sgd;
       int                     error, i, sge_idx, sge_count;
       struct mfi_mpi2_request_raid_scsi_io *io_req;
       struct mpi25_ieee_sge_chain64 *sgl_ptr;

       DNPRINTF(MFI_D_DMA, "%s: mfi_tbolt_create_sgl %#lx\n", DEVNAME(sc),
           (u_long)ccb->ccb_data);

       if (!ccb->ccb_data)
               return -1;

       KASSERT(flags == BUS_DMA_NOWAIT || !cpu_intr_p());
       error = bus_dmamap_load(sc->sc_datadmat, ccb->ccb_dmamap,
           ccb->ccb_data, ccb->ccb_len, NULL, flags);
       if (error) {
               if (error == EFBIG)
                       aprint_error_dev(sc->sc_dev, "more than %d dma segs\n",
                           sc->sc_max_sgl);
               else
                       aprint_error_dev(sc->sc_dev,
                           "error %d loading dma map\n", error);
               return -1;
       }

       io_req = ccb->ccb_tb_io_request;
       sgl_ptr = &io_req->SGL.IeeeChain.Chain64;
       sge_count = ccb->ccb_dmamap->dm_nsegs;
       sgd = ccb->ccb_dmamap->dm_segs;
       KASSERT(sge_count <= sc->sc_max_sgl);
       KASSERT(sge_count <=
           (MEGASAS_THUNDERBOLT_MAX_SGE_IN_MAINMSG - 1 +
            MEGASAS_THUNDERBOLT_MAX_SGE_IN_CHAINMSG));

       if (sge_count > MEGASAS_THUNDERBOLT_MAX_SGE_IN_MAINMSG) {
               /* One element to store the chain info */
               sge_idx = MEGASAS_THUNDERBOLT_MAX_SGE_IN_MAINMSG - 1;
               DNPRINTF(MFI_D_DMA,
                   "mfi sge_idx %d sge_count %d io_req paddr %jx\n",
                   sge_idx, sge_count, (uintmax_t)ccb->ccb_tb_pio_request);
       } else {
               sge_idx = sge_count;
       }

       for (i = 0; i < sge_idx; i++) {
               sgl_ptr->Address = htole64(sgd[i].ds_addr);
               sgl_ptr->Length = htole32(sgd[i].ds_len);
               sgl_ptr->Flags = 0;
               if (sge_idx < sge_count) {
                       DNPRINTF(MFI_D_DMA,
                           "sgl %p %d 0x%" PRIx64 " len 0x%" PRIx32
                           " flags 0x%x\n", sgl_ptr, i,
                           sgl_ptr->Address, sgl_ptr->Length,
                           sgl_ptr->Flags);
               }
               sgl_ptr++;
       }
       io_req->ChainOffset = 0;
       if (sge_idx < sge_count) {
               struct mpi25_ieee_sge_chain64 *sg_chain;
               io_req->ChainOffset = MEGASAS_THUNDERBOLT_CHAIN_OFF_MAINMSG;
               sg_chain = sgl_ptr;
               /* Prepare chain element */
               sg_chain->NextChainOffset = 0;
               sg_chain->Flags = (MPI2_IEEE_SGE_FLAGS_CHAIN_ELEMENT |
                   MPI2_IEEE_SGE_FLAGS_IOCPLBNTA_ADDR);
               sg_chain->Length =  (sizeof(mpi2_sge_io_union) *
                   (sge_count - sge_idx));
               sg_chain->Address = ccb->ccb_tb_psg_frame;
               DNPRINTF(MFI_D_DMA,
                   "sgl %p chain 0x%" PRIx64 " len 0x%" PRIx32
                   " flags 0x%x\n", sg_chain, sg_chain->Address,
                   sg_chain->Length, sg_chain->Flags);
               sgl_ptr = &ccb->ccb_tb_sg_frame->IeeeChain.Chain64;
               for (; i < sge_count; i++) {
                       sgl_ptr->Address = htole64(sgd[i].ds_addr);
                       sgl_ptr->Length = htole32(sgd[i].ds_len);
                       sgl_ptr->Flags = 0;
                       DNPRINTF(MFI_D_DMA,
                           "sgl %p %d 0x%" PRIx64 " len 0x%" PRIx32
                           " flags 0x%x\n", sgl_ptr, i, sgl_ptr->Address,
                           sgl_ptr->Length, sgl_ptr->Flags);
                       sgl_ptr++;
               }
               bus_dmamap_sync(sc->sc_dmat,
                   MFIMEM_MAP(sc->sc_tbolt_reqmsgpool),
                   ccb->ccb_tb_psg_frame - MFIMEM_DVA(sc->sc_tbolt_reqmsgpool),
                   MEGASAS_MAX_SZ_CHAIN_FRAME,  BUS_DMASYNC_PREREAD);
       }

       if (ccb->ccb_direction == MFI_DATA_IN) {
               bus_dmamap_sync(sc->sc_datadmat, ccb->ccb_dmamap, 0,
                   ccb->ccb_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
       } else {
               bus_dmamap_sync(sc->sc_datadmat, ccb->ccb_dmamap, 0,
                   ccb->ccb_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);
       }
       return sge_count;
}

/*
* The ThunderBolt HW has an option for the driver to directly
* access the underlying disks and operate on the RAID.  To
* do this there needs to be a capability to keep the RAID controller
* and driver in sync.  The FreeBSD driver does not take advantage
* of this feature since it adds a lot of complexity and slows down
* performance.  Performance is gained by using the controller's
* cache etc.
*
* Even though this driver doesn't access the disks directly, an
* AEN like command is used to inform the RAID firmware to "sync"
* with all LD's via the MFI_DCMD_LD_MAP_GET_INFO command.  This
* command in write mode will return when the RAID firmware has
* detected a change to the RAID state.  Examples of this type
* of change are removing a disk.  Once the command returns then
* the driver needs to acknowledge this and "sync" all LD's again.
* This repeats until we shutdown.  Then we need to cancel this
* pending command.
*
* If this is not done right the RAID firmware will not remove a
* pulled drive and the RAID won't go degraded etc.  Effectively,
* stopping any RAID management to functions.
*
* Doing another LD sync, requires the use of an event since the
* driver needs to do a mfi_wait_command and can't do that in an
* interrupt thread.
*
* The driver could get the RAID state via the MFI_DCMD_LD_MAP_GET_INFO
* That requires a bunch of structure and it is simpler to just do
* the MFI_DCMD_LD_GET_LIST versus walking the RAID map.
*/

void
mfi_tbolt_sync_map_info(struct work *w, void *v)
{
       struct mfi_softc *sc = v;
       int i;
       struct mfi_ccb *ccb = NULL;
       union mfi_mbox mbox;
       struct mfi_ld *ld_sync;
       size_t ld_size;
       int s;

       DNPRINTF(MFI_D_SYNC, "%s: mfi_tbolt_sync_map_info\n", DEVNAME(sc));
again:
       ld_sync = NULL;
       s = splbio();
       if (sc->sc_ldsync_ccb != NULL) {
               splx(s);
               return;
       }

       if (mfi_mgmt_internal(sc, MR_DCMD_LD_GET_LIST, MFI_DATA_IN,
           sizeof(sc->sc_ld_list), &sc->sc_ld_list, NULL, false)) {
               aprint_error_dev(sc->sc_dev, "MR_DCMD_LD_GET_LIST failed\n");
               goto err;
       }

       ld_size = sizeof(*ld_sync) * sc->sc_ld_list.mll_no_ld;

       ld_sync = malloc(ld_size, M_DEVBUF, M_WAITOK | M_ZERO);
       if (ld_sync == NULL) {
               aprint_error_dev(sc->sc_dev, "Failed to allocate sync\n");
               goto err;
       }
       for (i = 0; i < sc->sc_ld_list.mll_no_ld; i++) {
               ld_sync[i] = sc->sc_ld_list.mll_list[i].mll_ld;
       }

       if ((ccb = mfi_get_ccb(sc)) == NULL) {
               aprint_error_dev(sc->sc_dev, "Failed to get sync command\n");
               goto err;
       }
       sc->sc_ldsync_ccb = ccb;

       memset(&mbox, 0, sizeof(mbox));
       mbox.b[0] = sc->sc_ld_list.mll_no_ld;
       mbox.b[1] = MFI_DCMD_MBOX_PEND_FLAG;
       if (mfi_mgmt(ccb, NULL, MR_DCMD_LD_MAP_GET_INFO, MFI_DATA_OUT,
           ld_size, ld_sync, &mbox)) {
               aprint_error_dev(sc->sc_dev, "Failed to create sync command\n");
               goto err;
       }
       /*
        * we won't sleep on this command, so we have to override
        * the callback set up by mfi_mgmt()
        */
       ccb->ccb_done = mfi_sync_map_complete;

       mfi_post(sc, ccb);
       splx(s);
       return;

err:
       if (ld_sync)
               free(ld_sync, M_DEVBUF);
       if (ccb)
               mfi_put_ccb(ccb);
       sc->sc_ldsync_ccb = NULL;
       splx(s);
       kpause("ldsyncp", 0, hz, NULL);
       goto again;
}

static void
mfi_sync_map_complete(struct mfi_ccb *ccb)
{
       struct mfi_softc *sc = ccb->ccb_sc;
       bool aborted = !sc->sc_running;

       DNPRINTF(MFI_D_SYNC, "%s: mfi_sync_map_complete\n",
           DEVNAME(ccb->ccb_sc));
       KASSERT(sc->sc_ldsync_ccb == ccb);
       mfi_mgmt_done(ccb);
       free(ccb->ccb_data, M_DEVBUF);
       if (ccb->ccb_flags & MFI_CCB_F_ERR) {
               aprint_error_dev(sc->sc_dev, "sync command failed\n");
               aborted = true;
       }
       mfi_put_ccb(ccb);
       sc->sc_ldsync_ccb = NULL;

       /* set it up again so the driver can catch more events */
       if (!aborted) {
               workqueue_enqueue(sc->sc_ldsync_wq, &sc->sc_ldsync_wk, NULL);
       }
}

static int
mfifopen(dev_t dev, int flag, int mode, struct lwp *l)
{
       struct mfi_softc *sc;

       if ((sc = device_lookup_private(&mfi_cd, minor(dev))) == NULL)
               return (ENXIO);
       return (0);
}

static int
mfifclose(dev_t dev, int flag, int mode, struct lwp *l)
{
       return (0);
}

static int
mfifioctl(dev_t dev, u_long cmd, void *data, int flag,
   struct lwp *l)
{
       struct mfi_softc *sc;
       struct mfi_ioc_packet *ioc = data;
       uint8_t *udata;
       struct mfi_ccb *ccb = NULL;
       int ctx, i, s, error;
       union mfi_sense_ptr sense_ptr;

       switch (cmd) {
       case MFI_CMD:
               sc = device_lookup_private(&mfi_cd, ioc->mfi_adapter_no);
               break;
       default:
               return ENOTTY;
       }
       if (sc == NULL)
               return (ENXIO);
       if (sc->sc_opened)
               return (EBUSY);

       switch (cmd) {
       case MFI_CMD:
               error = kauth_authorize_device_passthru(l->l_cred, dev,
                   KAUTH_REQ_DEVICE_RAWIO_PASSTHRU_ALL, data);
               if (error)
                       return error;
               if (ioc->mfi_sge_count > MAX_IOCTL_SGE)
                       return EINVAL;
               s = splbio();
               if ((ccb = mfi_get_ccb(sc)) == NULL)
                       return ENOMEM;
               ccb->ccb_data = NULL;
               ctx = ccb->ccb_frame->mfr_header.mfh_context;
               memcpy(ccb->ccb_frame, ioc->mfi_frame.raw,
                  sizeof(*ccb->ccb_frame));
               ccb->ccb_frame->mfr_header.mfh_context = ctx;
               ccb->ccb_frame->mfr_header.mfh_scsi_status = 0;
               ccb->ccb_frame->mfr_header.mfh_pad0 = 0;
               ccb->ccb_frame_size =
                   (sizeof(union mfi_sgl) * ioc->mfi_sge_count) +
                   ioc->mfi_sgl_off;
               if (ioc->mfi_sge_count > 0) {
                       ccb->ccb_sgl = (union mfi_sgl *)
                           &ccb->ccb_frame->mfr_bytes[ioc->mfi_sgl_off];
               }
               if (ccb->ccb_frame->mfr_header.mfh_flags & MFI_FRAME_DIR_READ)
                       ccb->ccb_direction = MFI_DATA_IN;
               if (ccb->ccb_frame->mfr_header.mfh_flags & MFI_FRAME_DIR_WRITE)
                       ccb->ccb_direction = MFI_DATA_OUT;
               ccb->ccb_len = ccb->ccb_frame->mfr_header.mfh_data_len;
               if (ccb->ccb_len > MAXPHYS) {
                       error = ENOMEM;
                       goto out;
               }
               if (ccb->ccb_len &&
                   (ccb->ccb_direction & (MFI_DATA_IN | MFI_DATA_OUT)) != 0) {
                       udata = malloc(ccb->ccb_len, M_DEVBUF, M_WAITOK|M_ZERO);
                       if (udata == NULL) {
                               error = ENOMEM;
                               goto out;
                       }
                       ccb->ccb_data = udata;
                       if (ccb->ccb_direction & MFI_DATA_OUT) {
                               for (i = 0; i < ioc->mfi_sge_count; i++) {
                                       error = copyin(ioc->mfi_sgl[i].iov_base,
                                           udata, ioc->mfi_sgl[i].iov_len);
                                       if (error)
                                               goto out;
                                       udata = &udata[
                                           ioc->mfi_sgl[i].iov_len];
                               }
                       }
                       if (mfi_create_sgl(ccb, BUS_DMA_WAITOK)) {
                               error = EIO;
                               goto out;
                       }
               }
               if (ccb->ccb_frame->mfr_header.mfh_cmd == MFI_CMD_PD_SCSI_IO) {
                       ccb->ccb_frame->mfr_io.mif_sense_addr_lo =
                           htole32(ccb->ccb_psense);
                       ccb->ccb_frame->mfr_io.mif_sense_addr_hi = 0;
               }
               ccb->ccb_done = mfi_mgmt_done;
               mfi_post(sc, ccb);
               while (ccb->ccb_state != MFI_CCB_DONE)
                       tsleep(ccb, PRIBIO, "mfi_fioc", 0);

               if (ccb->ccb_direction & MFI_DATA_IN) {
                       udata = ccb->ccb_data;
                       for (i = 0; i < ioc->mfi_sge_count; i++) {
                               error = copyout(udata,
                                   ioc->mfi_sgl[i].iov_base,
                                   ioc->mfi_sgl[i].iov_len);
                               if (error)
                                       goto out;
                               udata = &udata[
                                   ioc->mfi_sgl[i].iov_len];
                       }
               }
               if (ioc->mfi_sense_len) {
                       memcpy(&sense_ptr.sense_ptr_data[0],
                       &ioc->mfi_frame.raw[ioc->mfi_sense_off],
                       sizeof(sense_ptr.sense_ptr_data));
                       error = copyout(ccb->ccb_sense,
                           sense_ptr.user_space,
                           sizeof(sense_ptr.sense_ptr_data));
                       if (error)
                               goto out;
               }
               memcpy(ioc->mfi_frame.raw, ccb->ccb_frame,
                  sizeof(*ccb->ccb_frame));
               break;
       default:
               printf("mfifioctl unhandled cmd 0x%lx\n", cmd);
               return ENOTTY;
       }

out:
       if (ccb->ccb_data)
               free(ccb->ccb_data, M_DEVBUF);
       if (ccb)
               mfi_put_ccb(ccb);
       splx(s);
       return error;
}