/*
 * Copyright (c) 2015, AVAGO Tech. All rights reserved. Author: Marian Choy
 * Copyright (c) 2014, LSI Corp. All rights reserved. Author: Marian Choy
 * Support: freebsdraid@avagotech.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@avagotech.com> Mail to: AVAGO TECHNOLOGIES, 1621
 * Barber Lane, Milpitas, CA 95035 ATTN: MegaRaid FreeBSD
 *
 */

#include <sys/cdefs.h>
#include <sys/abi_compat.h>
#include <dev/mrsas/mrsas.h>
#include <dev/mrsas/mrsas_ioctl.h>

struct mrsas_passthru_cmd {
	struct mrsas_sge64 *kern_sge;
	struct mrsas_softc *sc;
	struct mrsas_mfi_cmd *cmd;
	bus_dma_tag_t ioctl_data_tag;
	bus_dmamap_t ioctl_data_dmamap;

	u_int32_t error_code;
	u_int32_t sge_count;
	int complete;
};

/*
 * Function prototypes
 */
int	mrsas_alloc_mfi_cmds(struct mrsas_softc *sc);
int	mrsas_passthru(struct mrsas_softc *sc, void *arg, u_long ioctlCmd);
void	mrsas_free_ioc_cmd(struct mrsas_softc *sc);
void	mrsas_free_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd);
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_data_load_cb:  Callback entry point
 * input:                               Pointer to command packet as argument
 *                                              Pointer to segment
 *                                              Number of segments Error
 *
 * This is the callback function of the bus dma map load.  It builds the SG
 * list.
 */
static void
mrsas_passthru_load_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
        struct mrsas_passthru_cmd *cb = (struct mrsas_passthru_cmd *)arg;
        struct mrsas_softc *sc = cb->sc;
	int i = 0;

	if (error) {
		cb->error_code = error;
		if (error == EFBIG) {
			device_printf(sc->mrsas_dev, "mrsas_passthru_load_cb: "
			    "error=%d EFBIG\n", error);
			cb->complete = 1;
			return;
		} else {
			device_printf(sc->mrsas_dev, "mrsas_passthru_load_cb: "
			    "error=%d UNKNOWN\n", error);
		}
	}
	if (nseg > MAX_IOCTL_SGE) {
		cb->error_code = EFBIG;
		device_printf(sc->mrsas_dev, "mrsas_passthru_load_cb: "
		    "too many segments: %d\n", nseg);
		cb->complete = 1;
		return;
	}

	for (i = 0; i < nseg; i++) {
		cb->kern_sge[i].phys_addr = htole64(segs[i].ds_addr);
		cb->kern_sge[i].length = htole32(segs[i].ds_len);
	}
	cb->sge_count = nseg;

	bus_dmamap_sync(cb->ioctl_data_tag, cb->ioctl_data_dmamap,
            BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

	cb->complete = 1;
}

/*
 * 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, u_long ioctlCmd)
{
	struct mrsas_iocpacket *user_ioc = (struct mrsas_iocpacket *)arg;

#ifdef COMPAT_FREEBSD32
	struct mrsas_iocpacket32 *user_ioc32 = (struct mrsas_iocpacket32 *)arg;

#endif
	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];
	bus_addr_t ioctl_data_phys_addr[MAX_IOCTL_SGE];
	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, ioctl_data_size = 0, ioctl_sense_size, ret = 0;
	struct mrsas_sge32 *kern_sge32;
	unsigned long *sense_ptr;
	uint8_t *iov_base_ptrin = NULL;
	size_t iov_len = 0;

	/*
	 * 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 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 *)
	    ((uintptr_t)cmd->frame + user_ioc->sgl_off);

	memset(ioctl_data_tag, 0, (sizeof(bus_dma_tag_t) * MAX_IOCTL_SGE));
	memset(ioctl_data_dmamap, 0, (sizeof(bus_dmamap_t) * MAX_IOCTL_SGE));
	memset(ioctl_data_mem, 0, (sizeof(void *) * MAX_IOCTL_SGE));
	memset(ioctl_data_phys_addr, 0, (sizeof(bus_addr_t) * MAX_IOCTL_SGE));

	/*
	 * For each user buffer, create a mirror buffer and copy in
	 */
	for (i = 0; i < user_ioc->sge_count; i++) {
		if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
			if (!user_ioc->sgl[i].iov_len)
				continue;
			ioctl_data_size = user_ioc->sgl[i].iov_len;
#ifdef COMPAT_FREEBSD32
		} else {
			if (!user_ioc32->sgl[i].iov_len)
				continue;
			ioctl_data_size = user_ioc32->sgl[i].iov_len;
#endif
		}
		if (bus_dma_tag_create(sc->mrsas_parent_tag,
		    1, 0,
		    BUS_SPACE_MAXADDR_32BIT,
		    BUS_SPACE_MAXADDR,
		    NULL, NULL,
		    ioctl_data_size,
		    1,
		    ioctl_data_size,
		    BUS_DMA_ALLOCNOW,
		    NULL, NULL,
		    &ioctl_data_tag[i])) {
			device_printf(sc->mrsas_dev, "Cannot allocate ioctl data tag\n");
			ret = ENOMEM;
			goto out;
		}
		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");
			ret = ENOMEM;
			goto out;
		}
		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");
			ret = ENOMEM;
			goto out;
		}
		/* Save the physical address and length */
		kern_sge32[i].phys_addr = (u_int32_t)ioctl_data_phys_addr[i];

		if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
			kern_sge32[i].length = user_ioc->sgl[i].iov_len;

			iov_base_ptrin = user_ioc->sgl[i].iov_base;
			iov_len = user_ioc->sgl[i].iov_len;
#ifdef COMPAT_FREEBSD32
		} else {
			kern_sge32[i].length = user_ioc32->sgl[i].iov_len;

			iov_base_ptrin = PTRIN(user_ioc32->sgl[i].iov_base);
			iov_len = user_ioc32->sgl[i].iov_len;
#endif
		}

		/* Copy in data from user space */
		ret = copyin(iov_base_ptrin, ioctl_data_mem[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,
		    1, 0,
		    BUS_SPACE_MAXADDR_32BIT,
		    BUS_SPACE_MAXADDR,
		    NULL, NULL,
		    ioctl_sense_size,
		    1,
		    ioctl_sense_size,
		    BUS_DMA_ALLOCNOW,
		    NULL, NULL,
		    &ioctl_sense_tag)) {
			device_printf(sc->mrsas_dev, "Cannot allocate ioctl sense tag\n");
			ret = ENOMEM;
			goto out;
		}
		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 sense mem\n");
			ret = ENOMEM;
			goto out;
		}
		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");
			ret = ENOMEM;
			goto out;
		}
		sense_ptr =
		    (unsigned long *)((uintptr_t)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;
	ret = mrsas_issue_blocked_cmd(sc, cmd);
	if (ret == ETIMEDOUT) {
		mrsas_dprint(sc, MRSAS_OCR,
		    "IOCTL command is timed out, initiating OCR\n");
		sc->do_timedout_reset = MFI_DCMD_TIMEOUT_OCR;
		ret = EAGAIN;
		goto out;
	}
	cmd->sync_cmd = 0;

	/*
	 * copy out the kernel buffers to user buffers
	 */
	for (i = 0; i < user_ioc->sge_count; i++) {
		if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
			iov_base_ptrin = user_ioc->sgl[i].iov_base;
			iov_len = user_ioc->sgl[i].iov_len;
#ifdef COMPAT_FREEBSD32
		} else {
			iov_base_ptrin = PTRIN(user_ioc32->sgl[i].iov_base);
			iov_len = user_ioc32->sgl[i].iov_len;
#endif
		}

		ret = copyout(ioctl_data_mem[i], iov_base_ptrin, 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 *)((uintptr_t)user_ioc->frame.raw +
		    user_ioc->sense_off);
		ret = copyout(ioctl_sense_mem, (unsigned long *)(uintptr_t)*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 (user_ioc->sense_len) {
		if (ioctl_sense_phys_addr)
			bus_dmamap_unload(ioctl_sense_tag, ioctl_sense_dmamap);
		if (ioctl_sense_mem != NULL)
			bus_dmamem_free(ioctl_sense_tag, ioctl_sense_mem, ioctl_sense_dmamap);
		if (ioctl_sense_tag != NULL)
			bus_dma_tag_destroy(ioctl_sense_tag);
	}
	/*
	 * Release data buffers
	 */
	for (i = 0; i < user_ioc->sge_count; i++) {
		if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
			if (!user_ioc->sgl[i].iov_len)
				continue;
#ifdef COMPAT_FREEBSD32
		} else {
			if (!user_ioc32->sgl[i].iov_len)
				continue;
#endif
		}
		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_user_command:    Handle user mode DCMD and buffer
 * input:                 Adapter instance soft state
 *                        argument pointer
 *
 * This function is called from mrsas_ioctl() DCMDs to firmware for mfiutil
 */
int
mrsas_user_command(struct mrsas_softc *sc, struct mfi_ioc_passthru *ioc)
{
	struct mrsas_mfi_cmd *cmd;
	struct mrsas_dcmd_frame *dcmd;
	struct mrsas_passthru_cmd *passcmd;
	bus_dma_tag_t ioctl_data_tag;
	bus_dmamap_t ioctl_data_dmamap;
	bus_addr_t ioctl_data_phys_addr;
	struct mrsas_sge64 *kern_sge;
	int ret, ioctl_data_size;
	char *ioctl_temp_data_mem;

	ret = 0;
	ioctl_temp_data_mem = NULL;
	passcmd = NULL;
	ioctl_data_phys_addr = 0;
	dcmd = NULL;
	cmd = NULL;
	ioctl_data_tag = NULL;
	ioctl_data_dmamap = NULL;
	ioctl_data_dmamap = NULL;

	/* 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);
	}

	/*
	 * Frame is DCMD
	 */
	dcmd = (struct mrsas_dcmd_frame *)cmd->frame;
	memcpy(dcmd, &ioc->ioc_frame, sizeof(struct mrsas_dcmd_frame));

	ioctl_data_size = ioc->buf_size;

	cmd->frame->hdr.context = cmd->index;
	cmd->frame->hdr.pad_0 = 0;
	cmd->frame->hdr.flags = MFI_FRAME_DIR_BOTH;
	if (sizeof(bus_addr_t) == 8)
		cmd->frame->hdr.flags |= MFI_FRAME_SGL64 | MFI_FRAME_SENSE64;

	kern_sge = (struct mrsas_sge64 *)(&dcmd->sgl);

	if (ioctl_data_size == 0) {
		kern_sge[0].phys_addr = 0;
		kern_sge[0].length = 0;
	} else {
		ioctl_temp_data_mem = malloc(ioc->buf_size, M_MRSAS, M_WAITOK);

		/* Copy in data from user space */
		ret = copyin(ioc->buf, ioctl_temp_data_mem, ioc->buf_size);
		if (ret) {
			device_printf(sc->mrsas_dev, "IOCTL copyin failed!\n");
			goto out;
		}

		/*
		 * Allocate a temporary struct to hold parameters for the
		 * callback
		 */
		passcmd = malloc(sizeof(struct mrsas_passthru_cmd), M_MRSAS,
		    M_WAITOK);
		passcmd->complete = 0;
		passcmd->sc = sc;
		passcmd->cmd = cmd;
		passcmd->kern_sge = kern_sge;

		/*
		 * Create a dma tag for passthru buffers
		 */
		if (bus_dma_tag_create(sc->mrsas_parent_tag,   /* parent */
		    1, 0,                   /* algnmnt, boundary */
		    BUS_SPACE_MAXADDR,      /* lowaddr */
		    BUS_SPACE_MAXADDR,      /* highaddr */
		    NULL, NULL,             /* filter, filterarg */
		    ioctl_data_size,        /* maxsize */
		    MAX_IOCTL_SGE,          /* msegments */
		    ioctl_data_size,        /* maxsegsize */
		    BUS_DMA_ALLOCNOW,       /* flags */
		    busdma_lock_mutex,      /* lockfunc */
		    &sc->ioctl_lock,        /* lockarg */
		    &ioctl_data_tag)) {
			device_printf(sc->mrsas_dev,
			   "Cannot allocate ioctl data tag %d\n",
			    ioc->buf_size);
			ret = ENOMEM;
			goto out;
		}

		/* Create memmap */
		if (bus_dmamap_create(ioctl_data_tag, 0, &ioctl_data_dmamap)) {
			device_printf(sc->mrsas_dev, "Cannot create ioctl "
			    "passthru dmamap\n");
			ret = ENOMEM;
			goto out;
		}

		passcmd->ioctl_data_tag = ioctl_data_tag;
		passcmd->ioctl_data_dmamap = ioctl_data_dmamap;

		/* Map data buffer into bus space */
		if (bus_dmamap_load(ioctl_data_tag, ioctl_data_dmamap,
		    ioctl_temp_data_mem, ioc->buf_size, mrsas_passthru_load_cb,
		    passcmd, BUS_DMA_NOWAIT)) {
			device_printf(sc->mrsas_dev, "Cannot load ioctl "
			    "passthru data mem%s %d\n", curproc->p_comm, ioctl_data_size);
			ret = ENOMEM;
			goto out;
		}

		while (passcmd->complete == 0) {
			pause("mrsas_passthru", hz);
		}

		cmd->frame->dcmd.sge_count = passcmd->sge_count;
	}

	/*
	 * 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;

	if (ioctl_data_size != 0) {
		bus_dmamap_sync(ioctl_data_tag, ioctl_data_dmamap,
		    BUS_DMASYNC_POSTREAD);
		/*
		 * copy out the kernel buffers to user buffers
		 */
		ret = copyout(ioctl_temp_data_mem, ioc->buf, ioc->buf_size);
		if (ret) {
			device_printf(sc->mrsas_dev,
			    "IOCTL copyout failed!\n");
			goto out;
		}
	}

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

out:
	/*
	 * Release temporary passthrough ioctl
	 */
	if (ioctl_temp_data_mem)
		free(ioctl_temp_data_mem, M_MRSAS);
	if (passcmd)
		free(passcmd, M_MRSAS);

	/*
	 * Release data buffers
	 */
	if (ioctl_data_phys_addr) {
		bus_dmamap_unload(ioctl_data_tag, ioctl_data_dmamap);
		bus_dmamap_destroy(ioctl_data_tag, ioctl_data_dmamap);
	}
	if (ioctl_data_tag != NULL)
		bus_dma_tag_destroy(ioctl_data_tag);
	/* 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 = malloc(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] = malloc(sizeof(struct mrsas_mfi_cmd),
		    M_MRSAS, M_NOWAIT);
		if (!sc->mfi_cmd_list[i]) {
			for (j = 0; j < i; j++)
				free(sc->mfi_cmd_list[j], M_MRSAS);
			free(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");
		/* Free the frames */
		for (i = 0; i < MRSAS_MAX_MFI_CMDS; i++) {
			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,
	    1, 0,
	    BUS_SPACE_MAXADDR_32BIT,
	    BUS_SPACE_MAXADDR,
	    NULL, NULL,
	    MRSAS_MFI_FRAME_SIZE,
	    1,
	    MRSAS_MFI_FRAME_SIZE,
	    BUS_DMA_ALLOCNOW,
	    NULL, NULL,
	    &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);
		}
		/*
		 * For MFI controllers.
		 * max_num_sge = 60
		 * max_sge_sz  = 16 byte (sizeof megasas_sge_skinny)
		 * Totl 960 byte (15 MFI frame of 64 byte)
		 *
		 * Fusion adapter require only 3 extra frame.
		 * max_num_sge = 16 (defined as MAX_IOCTL_SGE)
		 * max_sge_sz  = 12 byte (sizeof  megasas_sge64)
		 * Total 192 byte (3 MFI frame of 64 byte)
		 */
		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);
}