diff options
Diffstat (limited to 'sys/dev/mrsas/mrsas_ioctl.c')
-rw-r--r-- | sys/dev/mrsas/mrsas_ioctl.c | 546 |
1 files changed, 546 insertions, 0 deletions
diff --git a/sys/dev/mrsas/mrsas_ioctl.c b/sys/dev/mrsas/mrsas_ioctl.c new file mode 100644 index 0000000..6343aa5 --- /dev/null +++ b/sys/dev/mrsas/mrsas_ioctl.c @@ -0,0 +1,546 @@ +/* + * 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 + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <dev/mrsas/mrsas.h> +#include <dev/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, (long long unsigned int) 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 = 0; + 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 + NULL, NULL, // filter, filterarg + ioctl_data_size, // maxsize + 1, // msegments + ioctl_data_size, // maxsegsize + BUS_DMA_ALLOCNOW, // flags + NULL, NULL, // lockfunc, lockarg + &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 + NULL, NULL, // filter, filterarg + ioctl_sense_size, // maxsize + 1, // msegments + ioctl_sense_size, // maxsegsize + BUS_DMA_ALLOCNOW, // flags + NULL, NULL, // lockfunc, lockarg + &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_mem; + } + + /* + * 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 = 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"); + 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 + NULL, NULL, // filter, filterarg + MRSAS_MFI_FRAME_SIZE, // maxsize + 1, // msegments + MRSAS_MFI_FRAME_SIZE, // maxsegsize + BUS_DMA_ALLOCNOW, // flags + NULL, NULL, // lockfunc, lockarg + &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); +} |