/* * 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 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: * 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 #include /* * 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 NULL, NULL, // filter, filterarg 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 NULL, NULL, // filter, filterarg 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_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 = 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 NULL, NULL, // filter, filterarg 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); }