root/sys/dev/raid/mrsas/mrsas_ioctl.c
/*
 * Copyright (c) 2014, LSI Corp.
 * All rights reserved.
 * Author: Marian Choy
 * Support: freebsdraid@lsi.com
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of the <ORGANIZATION> nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER 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.
 *
 * Send feedback to: <megaraidfbsd@lsi.com>
 * Mail to: LSI Corporation, 1621 Barber Lane, Milpitas, CA 95035
 *    ATTN: MegaRaid FreeBSD
 *
 * $FreeBSD: head/sys/dev/mrsas/mrsas_ioctl.c 265555 2014-05-07 16:16:49Z ambrisko $
 */

#include <dev/raid/mrsas/mrsas.h>
#include <dev/raid/mrsas/mrsas_ioctl.h>

/* 
 * Function prototypes 
 */
int mrsas_alloc_mfi_cmds(struct mrsas_softc *sc);
int mrsas_passthru(struct mrsas_softc *sc, void *arg);
void mrsas_free_ioc_cmd(struct mrsas_softc *sc);
void mrsas_free_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd);
void mrsas_dump_dcmd(struct mrsas_softc *sc, struct mrsas_dcmd_frame* dcmd);
void mrsas_dump_ioctl(struct mrsas_softc *sc, struct mrsas_iocpacket *user_ioc);
void * mrsas_alloc_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd);
static int mrsas_create_frame_pool(struct mrsas_softc *sc);
static void mrsas_alloc_cb(void *arg, bus_dma_segment_t *segs,
        int nsegs, int error);

extern struct mrsas_mfi_cmd* mrsas_get_mfi_cmd(struct mrsas_softc *sc);
extern void mrsas_release_mfi_cmd(struct mrsas_mfi_cmd *cmd);
extern int mrsas_issue_blocked_cmd(struct mrsas_softc *sc,
    struct mrsas_mfi_cmd *cmd);


/**
 * mrsas_dump_ioctl:       Print debug output for DCMDs 
 * input:                  Adapter instance soft state
 *                         DCMD frame structure
 *
 * This function is called from mrsas_passthru() to print out debug information
 * in the handling and routing of DCMD commands.
 */
void mrsas_dump_dcmd( struct mrsas_softc *sc, struct mrsas_dcmd_frame* dcmd )
{
    int i;

    device_printf(sc->mrsas_dev, "dcmd->cmd:           0x%02hhx\n", dcmd->cmd);
    device_printf(sc->mrsas_dev, "dcmd->cmd_status:    0x%02hhx\n", dcmd->cmd_status);
    device_printf(sc->mrsas_dev, "dcmd->sge_count:     0x%02hhx\n", dcmd->sge_count);
    device_printf(sc->mrsas_dev, "dcmd->context:       0x%08x\n", dcmd->context);
    device_printf(sc->mrsas_dev, "dcmd->flags:         0x%04hx\n", dcmd->flags);
    device_printf(sc->mrsas_dev, "dcmd->timeout:       0x%04hx\n", dcmd->timeout);
    device_printf(sc->mrsas_dev, "dcmd->data_xfer_len: 0x%08x\n", dcmd->data_xfer_len);
    device_printf(sc->mrsas_dev, "dcmd->opcode:        0x%08x\n", dcmd->opcode);
    device_printf(sc->mrsas_dev, "dcmd->mbox.w[0]:     0x%08x\n", dcmd->mbox.w[0]);
    device_printf(sc->mrsas_dev, "dcmd->mbox.w[1]:     0x%08x\n", dcmd->mbox.w[1]);
    device_printf(sc->mrsas_dev, "dcmd->mbox.w[2]:     0x%08x\n", dcmd->mbox.w[2]);
    for (i=0; i< MIN(MAX_IOCTL_SGE, dcmd->sge_count); i++) {
        device_printf(sc->mrsas_dev, "sgl[%02d]\n", i);
        device_printf(sc->mrsas_dev, "    sge32[%02d].phys_addr: 0x%08x\n",  
            i, dcmd->sgl.sge32[i].phys_addr);
        device_printf(sc->mrsas_dev, "    sge32[%02d].length:    0x%08x\n", 
            i, dcmd->sgl.sge32[i].length);
        device_printf(sc->mrsas_dev, "    sge64[%02d].phys_addr: 0x%08llx\n",
            i, (unsigned long long) dcmd->sgl.sge64[i].phys_addr);
        device_printf(sc->mrsas_dev, "    sge64[%02d].length:    0x%08x\n", 
            i, dcmd->sgl.sge64[i].length);
    }
}

/**
 * mrsas_dump_ioctl:       Print debug output for ioctl 
 * input:                  Adapter instance soft state
 *                         iocpacket structure 
 *
 * This function is called from mrsas_passthru() to print out debug information
 * in the handling and routing of ioctl commands.
 */
void mrsas_dump_ioctl(struct mrsas_softc *sc, struct mrsas_iocpacket *user_ioc)
{
    union mrsas_frame *in_cmd = (union mrsas_frame *) &(user_ioc->frame.raw);
    struct mrsas_dcmd_frame* dcmd = (struct mrsas_dcmd_frame *) &(in_cmd->dcmd);
    int i;
 
    device_printf(sc->mrsas_dev, 
        "====== In %s() ======================================\n", __func__);
    device_printf(sc->mrsas_dev, "host_no:    0x%04hx\n", user_ioc->host_no);
    device_printf(sc->mrsas_dev, " __pad1:    0x%04hx\n", user_ioc->__pad1);
    device_printf(sc->mrsas_dev, "sgl_off:    0x%08x\n",  user_ioc->sgl_off);
    device_printf(sc->mrsas_dev, "sge_count:  0x%08x\n",  user_ioc->sge_count);
    device_printf(sc->mrsas_dev, "sense_off:  0x%08x\n",  user_ioc->sense_off);
    device_printf(sc->mrsas_dev, "sense_len:  0x%08x\n",  user_ioc->sense_len);

    mrsas_dump_dcmd(sc, dcmd);

    for (i=0; i< MIN(MAX_IOCTL_SGE, user_ioc->sge_count); i++) {
        device_printf(sc->mrsas_dev, "sge[%02d]\n", i);
        device_printf(sc->mrsas_dev, 
            "    iov_base: %p\n", user_ioc->sgl[i].iov_base);
        device_printf(sc->mrsas_dev, "    iov_len:  %p\n", 
            (void*)user_ioc->sgl[i].iov_len);
    }
    device_printf(sc->mrsas_dev, 
        "==================================================================\n");
}

/**
 * mrsas_passthru:        Handle pass-through commands 
 * input:                 Adapter instance soft state
 *                        argument pointer
 *
 * This function is called from mrsas_ioctl() to handle pass-through and 
 * ioctl commands to Firmware.  
 */
int mrsas_passthru( struct mrsas_softc *sc, void *arg )
{
    struct mrsas_iocpacket *user_ioc = (struct mrsas_iocpacket *)arg;
    union  mrsas_frame *in_cmd = (union mrsas_frame *) &(user_ioc->frame.raw);
    struct mrsas_mfi_cmd *cmd = NULL;
    bus_dma_tag_t ioctl_data_tag[MAX_IOCTL_SGE];
    bus_dmamap_t ioctl_data_dmamap[MAX_IOCTL_SGE];
    void *ioctl_data_mem[MAX_IOCTL_SGE];  // ioctl data virtual addr
    bus_addr_t ioctl_data_phys_addr[MAX_IOCTL_SGE]; // ioctl data phys addr
    bus_dma_tag_t ioctl_sense_tag = 0;
    bus_dmamap_t ioctl_sense_dmamap = 0;
    void *ioctl_sense_mem = NULL;  
    bus_addr_t ioctl_sense_phys_addr = 0; 
    int i, adapter, ioctl_data_size, ioctl_sense_size, ret=0;
    struct mrsas_sge32 *kern_sge32;
    unsigned long *sense_ptr;

    /* For debug - uncomment the following line for debug output */
    //mrsas_dump_ioctl(sc, user_ioc); 

    /* 
     * Check for NOP from MegaCli... MegaCli can issue a DCMD of 0.  In this 
     * case do nothing and return 0 to it as status.
     */
    if (in_cmd->dcmd.opcode == 0) {
        device_printf(sc->mrsas_dev, "In %s() Got a NOP\n", __func__);
        user_ioc->frame.hdr.cmd_status = MFI_STAT_OK;
        return (0);
    }

    /* Validate host_no */
    adapter = user_ioc->host_no;
    if (adapter != device_get_unit(sc->mrsas_dev)) {
        device_printf(sc->mrsas_dev, "In %s() IOCTL not for me!\n", __func__);
        return(ENOENT);
    }

    /* Validate SGL length */
    if (user_ioc->sge_count > MAX_IOCTL_SGE) { 
        device_printf(sc->mrsas_dev, "In %s() SGL is too long (%d > 8).\n", 
            __func__, user_ioc->sge_count);
        return(ENOENT);
    }

    /* Get a command */
    cmd = mrsas_get_mfi_cmd(sc);
    if (!cmd) {
        device_printf(sc->mrsas_dev, "Failed to get a free cmd for IOCTL\n");
        return(ENOMEM);
    }

    /*
     * User's IOCTL packet has 2 frames (maximum). Copy those two
     * frames into our cmd's frames. cmd->frame's context will get
     * overwritten when we copy from user's frames. So set that value
     * alone separately
     */
    memcpy(cmd->frame, user_ioc->frame.raw, 2 * MEGAMFI_FRAME_SIZE);
    cmd->frame->hdr.context = cmd->index;
    cmd->frame->hdr.pad_0 = 0;
    cmd->frame->hdr.flags &= ~(MFI_FRAME_IEEE | MFI_FRAME_SGL64 |
                               MFI_FRAME_SENSE64);

    /*
     * The management interface between applications and the fw uses
     * MFI frames. E.g, RAID configuration changes, LD property changes
     * etc are accomplishes through different kinds of MFI frames. The
     * driver needs to care only about substituting user buffers with
     * kernel buffers in SGLs. The location of SGL is embedded in the
     * struct iocpacket itself.
     */
    kern_sge32 = (struct mrsas_sge32 *)
        ((unsigned long)cmd->frame + user_ioc->sgl_off);

    /*
     * For each user buffer, create a mirror buffer and copy in
     */
    for (i=0; i < user_ioc->sge_count; i++) {
        if (!user_ioc->sgl[i].iov_len)
            continue;
        ioctl_data_size = user_ioc->sgl[i].iov_len;
        if (bus_dma_tag_create( sc->mrsas_parent_tag,   // parent
                                1, 0,                   // algnmnt, boundary
                                BUS_SPACE_MAXADDR_32BIT,// lowaddr
                                BUS_SPACE_MAXADDR,      // highaddr
                                ioctl_data_size,        // maxsize
                                1,                      // msegments
                                ioctl_data_size,        // maxsegsize
                                BUS_DMA_ALLOCNOW,       // flags
                                &ioctl_data_tag[i])) {
            device_printf(sc->mrsas_dev, "Cannot allocate ioctl data tag\n");
            return (ENOMEM);
        }
        if (bus_dmamem_alloc(ioctl_data_tag[i], (void **)&ioctl_data_mem[i],
                (BUS_DMA_NOWAIT | BUS_DMA_ZERO), &ioctl_data_dmamap[i])) {
            device_printf(sc->mrsas_dev, "Cannot allocate ioctl data mem\n");
            return (ENOMEM);
        }
        if (bus_dmamap_load(ioctl_data_tag[i], ioctl_data_dmamap[i],
                ioctl_data_mem[i], ioctl_data_size, mrsas_alloc_cb,
                &ioctl_data_phys_addr[i], BUS_DMA_NOWAIT)) {
            device_printf(sc->mrsas_dev, "Cannot load ioctl data mem\n");
            return (ENOMEM);
        }

        /* Save the physical address and length */
        kern_sge32[i].phys_addr = (u_int32_t)ioctl_data_phys_addr[i];
        kern_sge32[i].length = user_ioc->sgl[i].iov_len;

        /* Copy in data from user space */
        ret = copyin(user_ioc->sgl[i].iov_base, ioctl_data_mem[i], 
                        user_ioc->sgl[i].iov_len);
        if (ret) {
            device_printf(sc->mrsas_dev, "IOCTL copyin failed!\n");
            goto out;
        }
    }

    ioctl_sense_size = user_ioc->sense_len;
    if (user_ioc->sense_len) {
        if (bus_dma_tag_create( sc->mrsas_parent_tag,   // parent
                                1, 0,                   // algnmnt, boundary
                                BUS_SPACE_MAXADDR_32BIT,// lowaddr
                                BUS_SPACE_MAXADDR,      // highaddr
                                ioctl_sense_size,       // maxsize
                                1,                      // msegments
                                ioctl_sense_size,       // maxsegsize
                                BUS_DMA_ALLOCNOW,       // flags
                                &ioctl_sense_tag)) {
            device_printf(sc->mrsas_dev, "Cannot allocate ioctl sense tag\n");
            return (ENOMEM);
        }
        if (bus_dmamem_alloc(ioctl_sense_tag, (void **)&ioctl_sense_mem,
                (BUS_DMA_NOWAIT | BUS_DMA_ZERO), &ioctl_sense_dmamap)) {
            device_printf(sc->mrsas_dev, "Cannot allocate ioctl data mem\n");
            return (ENOMEM);
        }
        if (bus_dmamap_load(ioctl_sense_tag, ioctl_sense_dmamap,
                ioctl_sense_mem, ioctl_sense_size, mrsas_alloc_cb,
                &ioctl_sense_phys_addr, BUS_DMA_NOWAIT)) {
            device_printf(sc->mrsas_dev, "Cannot load ioctl sense mem\n");
            return (ENOMEM);
        }
        sense_ptr = 
            (unsigned long *)((unsigned long)cmd->frame + user_ioc->sense_off);
        *sense_ptr = ioctl_sense_phys_addr;
    }

    /*
     * Set the sync_cmd flag so that the ISR knows not to complete this
     * cmd to the SCSI mid-layer
     */
    cmd->sync_cmd = 1;
    mrsas_issue_blocked_cmd(sc, cmd);
    cmd->sync_cmd = 0;

    /*
     * copy out the kernel buffers to user buffers
     */
    for (i = 0; i < user_ioc->sge_count; i++) {
        ret = copyout(ioctl_data_mem[i], user_ioc->sgl[i].iov_base, 
            user_ioc->sgl[i].iov_len);
        if (ret) {
            device_printf(sc->mrsas_dev, "IOCTL copyout failed!\n");
            goto out;
        }
    }

    /*
     * copy out the sense
     */
    if (user_ioc->sense_len) {
        /*
         * sense_buff points to the location that has the user
         * sense buffer address
         */
        sense_ptr = (unsigned long *) ((unsigned long)user_ioc->frame.raw +
                      user_ioc->sense_off);
        ret = copyout(ioctl_sense_mem, (unsigned long*)*sense_ptr, 
                      user_ioc->sense_len); 
        if (ret) {
            device_printf(sc->mrsas_dev, "IOCTL sense copyout failed!\n");
            goto out;
        }
    }

    /*
     * Return command status to user space
     */
    memcpy(&user_ioc->frame.hdr.cmd_status, &cmd->frame->hdr.cmd_status, 
            sizeof(u_int8_t));

out:
    /* 
     * Release sense buffer 
     */
    if (ioctl_sense_phys_addr)
        bus_dmamap_unload(ioctl_sense_tag, ioctl_sense_dmamap);
    if (ioctl_sense_mem)
        bus_dmamem_free(ioctl_sense_tag, ioctl_sense_mem, ioctl_sense_dmamap);
    if (ioctl_sense_tag)
        bus_dma_tag_destroy(ioctl_sense_tag);

    /* 
     * Release data buffers 
     */
    for (i = 0; i < user_ioc->sge_count; i++) {
        if (!user_ioc->sgl[i].iov_len)
            continue;
        if (ioctl_data_phys_addr[i])
            bus_dmamap_unload(ioctl_data_tag[i], ioctl_data_dmamap[i]);
        if (ioctl_data_mem[i] != NULL)
            bus_dmamem_free(ioctl_data_tag[i], ioctl_data_mem[i], 
                ioctl_data_dmamap[i]);
        if (ioctl_data_tag[i] != NULL)
            bus_dma_tag_destroy(ioctl_data_tag[i]);
    }

    /* Free command */
    mrsas_release_mfi_cmd(cmd);

    return(ret);
}

/**
 * mrsas_alloc_mfi_cmds:  Allocates the command packets
 * input:                 Adapter instance soft state
 *
 * Each IOCTL or passthru command that is issued to the FW are wrapped in a
 * local data structure called mrsas_mfi_cmd.  The frame embedded in this 
 * mrsas_mfi is issued to FW. The array is used only to look up the 
 * mrsas_mfi_cmd given the context. The free commands are maintained in a
 * linked list.
 */
int mrsas_alloc_mfi_cmds(struct mrsas_softc *sc)
{
    int i, j;
    u_int32_t max_cmd;
    struct mrsas_mfi_cmd *cmd;

    max_cmd = MRSAS_MAX_MFI_CMDS;

    /*
     * sc->mfi_cmd_list is an array of struct mrsas_mfi_cmd pointers. Allocate the
     * dynamic array first and then allocate individual commands.
     */
    sc->mfi_cmd_list = kmalloc(sizeof(struct mrsas_mfi_cmd*)*max_cmd, M_MRSAS, M_NOWAIT);
    if (!sc->mfi_cmd_list) {
        device_printf(sc->mrsas_dev, "Cannot alloc memory for mfi_cmd cmd_list.\n");
        return(ENOMEM);
    }
    memset(sc->mfi_cmd_list, 0, sizeof(struct mrsas_mfi_cmd *)*max_cmd);
    for (i = 0; i < max_cmd; i++) {
        sc->mfi_cmd_list[i] = kmalloc(sizeof(struct mrsas_mfi_cmd), 
                                 M_MRSAS, M_NOWAIT);
        if (!sc->mfi_cmd_list[i]) {
            for (j = 0; j < i; j++)
                kfree(sc->mfi_cmd_list[j],M_MRSAS);
            kfree(sc->mfi_cmd_list, M_MRSAS);
            sc->mfi_cmd_list = NULL;
            return(ENOMEM);
        }
    }

    for (i = 0; i < max_cmd; i++) {
        cmd = sc->mfi_cmd_list[i];
        memset(cmd, 0, sizeof(struct mrsas_mfi_cmd));
        cmd->index = i;
        cmd->ccb_ptr = NULL;
        cmd->sc = sc; 
        TAILQ_INSERT_TAIL(&(sc->mrsas_mfi_cmd_list_head), cmd, next);
    }

    /* create a frame pool and assign one frame to each command */
    if (mrsas_create_frame_pool(sc)) {
        device_printf(sc->mrsas_dev, "Cannot allocate DMA frame pool.\n");
        for (i = 0; i < MRSAS_MAX_MFI_CMDS; i++) { // Free the frames
            cmd = sc->mfi_cmd_list[i];
            mrsas_free_frame(sc, cmd);
        }
        if (sc->mficmd_frame_tag != NULL)
            bus_dma_tag_destroy(sc->mficmd_frame_tag);
        return(ENOMEM);
    }

    return(0);
}

/**
 * mrsas_create_frame_pool -   Creates DMA pool for cmd frames
 * input:                      Adapter soft state
 *
 * Each command packet has an embedded DMA memory buffer that is used for
 * filling MFI frame and the SG list that immediately follows the frame. This
 * function creates those DMA memory buffers for each command packet by using
 * PCI pool facility. pad_0 is initialized to 0 to prevent corrupting value 
 * of context and could cause FW crash.
 */
static int mrsas_create_frame_pool(struct mrsas_softc *sc)
{
    int i;
    struct mrsas_mfi_cmd *cmd;

    if (bus_dma_tag_create( sc->mrsas_parent_tag,   // parent
                            1, 0,                   // algnmnt, boundary
                            BUS_SPACE_MAXADDR_32BIT,// lowaddr
                            BUS_SPACE_MAXADDR,      // highaddr
                            MRSAS_MFI_FRAME_SIZE,   // maxsize
                            1,                      // msegments
                            MRSAS_MFI_FRAME_SIZE,   // maxsegsize
                            BUS_DMA_ALLOCNOW,       // flags
                            &sc->mficmd_frame_tag)) {
        device_printf(sc->mrsas_dev, "Cannot create MFI frame tag\n");
        return (ENOMEM);
    }

    for (i = 0; i < MRSAS_MAX_MFI_CMDS; i++) {
        cmd = sc->mfi_cmd_list[i];
        cmd->frame = mrsas_alloc_frame(sc, cmd);
        if (cmd->frame == NULL) {
            device_printf(sc->mrsas_dev, "Cannot alloc MFI frame memory\n");
            return (ENOMEM);
        } 
        memset(cmd->frame, 0, MRSAS_MFI_FRAME_SIZE); 
        cmd->frame->io.context = cmd->index;
        cmd->frame->io.pad_0 = 0;
    }

    return(0);
}

/**
 * mrsas_alloc_frame -   Allocates MFI Frames
 * input:                Adapter soft state
 *
 * Create bus DMA memory tag and dmamap and load memory for MFI frames. 
 * Returns virtual memory pointer to allocated region. 
 */
void *mrsas_alloc_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd)
{
    u_int32_t frame_size = MRSAS_MFI_FRAME_SIZE;

    if (bus_dmamem_alloc(sc->mficmd_frame_tag, (void **)&cmd->frame_mem,
                    BUS_DMA_NOWAIT, &cmd->frame_dmamap)) {
        device_printf(sc->mrsas_dev, "Cannot alloc MFI frame memory\n");
        return (NULL);
    }
    if (bus_dmamap_load(sc->mficmd_frame_tag, cmd->frame_dmamap,
                        cmd->frame_mem, frame_size, mrsas_alloc_cb,
                        &cmd->frame_phys_addr, BUS_DMA_NOWAIT)) {
        device_printf(sc->mrsas_dev, "Cannot load IO request memory\n");
        return (NULL);
    }
  
    return(cmd->frame_mem);
}

/*
 * mrsas_alloc_cb:  Callback function of bus_dmamap_load()
 * input:           callback argument,
 *                  machine dependent type that describes DMA segments,
 *                  number of segments,
 *                  error code.
 *
 * This function is for the driver to receive mapping information resultant
 * of the bus_dmamap_load(). The information is actually not being used, 
 * but the address is saved anyway. 
 */
static void mrsas_alloc_cb(void *arg, bus_dma_segment_t *segs,
        int nsegs, int error)
{
    bus_addr_t *addr;

    addr = arg;
    *addr = segs[0].ds_addr;
}

/**
 * mrsas_free_frames:    Frees memory for  MFI frames
 * input:                Adapter soft state
 *
 * Deallocates MFI frames memory.  Called from mrsas_free_mem() during 
 * detach and error case during creation of frame pool.
 */
void mrsas_free_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd)
{
    if (cmd->frame_phys_addr)
        bus_dmamap_unload(sc->mficmd_frame_tag, cmd->frame_dmamap);
    if (cmd->frame_mem != NULL)
        bus_dmamem_free(sc->mficmd_frame_tag, cmd->frame_mem, cmd->frame_dmamap);
}