| /* |
| * linux/drivers/scsi/esas2r/esas2r_ioctl.c |
| * For use with ATTO ExpressSAS R6xx SAS/SATA RAID controllers |
| * |
| * Copyright (c) 2001-2013 ATTO Technology, Inc. |
| * (mailto:linuxdrivers@attotech.com) |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * NO WARRANTY |
| * THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR |
| * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT |
| * LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, |
| * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is |
| * solely responsible for determining the appropriateness of using and |
| * distributing the Program and assumes all risks associated with its |
| * exercise of rights under this Agreement, including but not limited to |
| * the risks and costs of program errors, damage to or loss of data, |
| * programs or equipment, and unavailability or interruption of operations. |
| * |
| * DISCLAIMER OF LIABILITY |
| * NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED |
| * HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, |
| * USA. |
| */ |
| |
| #include "esas2r.h" |
| |
| /* |
| * Buffered ioctl handlers. A buffered ioctl is one which requires that we |
| * allocate a DMA-able memory area to communicate with the firmware. In |
| * order to prevent continually allocating and freeing consistent memory, |
| * we will allocate a global buffer the first time we need it and re-use |
| * it for subsequent ioctl calls that require it. |
| */ |
| |
| u8 *esas2r_buffered_ioctl; |
| dma_addr_t esas2r_buffered_ioctl_addr; |
| u32 esas2r_buffered_ioctl_size; |
| struct pci_dev *esas2r_buffered_ioctl_pcid; |
| |
| static DEFINE_SEMAPHORE(buffered_ioctl_semaphore); |
| typedef int (*BUFFERED_IOCTL_CALLBACK)(struct esas2r_adapter *, |
| struct esas2r_request *, |
| struct esas2r_sg_context *, |
| void *); |
| typedef void (*BUFFERED_IOCTL_DONE_CALLBACK)(struct esas2r_adapter *, |
| struct esas2r_request *, void *); |
| |
| struct esas2r_buffered_ioctl { |
| struct esas2r_adapter *a; |
| void *ioctl; |
| u32 length; |
| u32 control_code; |
| u32 offset; |
| BUFFERED_IOCTL_CALLBACK |
| callback; |
| void *context; |
| BUFFERED_IOCTL_DONE_CALLBACK |
| done_callback; |
| void *done_context; |
| |
| }; |
| |
| static void complete_fm_api_req(struct esas2r_adapter *a, |
| struct esas2r_request *rq) |
| { |
| a->fm_api_command_done = 1; |
| wake_up_interruptible(&a->fm_api_waiter); |
| } |
| |
| /* Callbacks for building scatter/gather lists for FM API requests */ |
| static u32 get_physaddr_fm_api(struct esas2r_sg_context *sgc, u64 *addr) |
| { |
| struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; |
| int offset = sgc->cur_offset - a->save_offset; |
| |
| (*addr) = a->firmware.phys + offset; |
| return a->firmware.orig_len - offset; |
| } |
| |
| static u32 get_physaddr_fm_api_header(struct esas2r_sg_context *sgc, u64 *addr) |
| { |
| struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; |
| int offset = sgc->cur_offset - a->save_offset; |
| |
| (*addr) = a->firmware.header_buff_phys + offset; |
| return sizeof(struct esas2r_flash_img) - offset; |
| } |
| |
| /* Handle EXPRESS_IOCTL_RW_FIRMWARE ioctl with img_type = FW_IMG_FM_API. */ |
| static void do_fm_api(struct esas2r_adapter *a, struct esas2r_flash_img *fi) |
| { |
| struct esas2r_request *rq; |
| |
| if (mutex_lock_interruptible(&a->fm_api_mutex)) { |
| fi->status = FI_STAT_BUSY; |
| return; |
| } |
| |
| rq = esas2r_alloc_request(a); |
| if (rq == NULL) { |
| fi->status = FI_STAT_BUSY; |
| goto free_sem; |
| } |
| |
| if (fi == &a->firmware.header) { |
| a->firmware.header_buff = dma_alloc_coherent(&a->pcid->dev, |
| (size_t)sizeof( |
| struct |
| esas2r_flash_img), |
| (dma_addr_t *)&a-> |
| firmware. |
| header_buff_phys, |
| GFP_KERNEL); |
| |
| if (a->firmware.header_buff == NULL) { |
| esas2r_debug("failed to allocate header buffer!"); |
| fi->status = FI_STAT_BUSY; |
| goto free_req; |
| } |
| |
| memcpy(a->firmware.header_buff, fi, |
| sizeof(struct esas2r_flash_img)); |
| a->save_offset = a->firmware.header_buff; |
| a->fm_api_sgc.get_phys_addr = |
| (PGETPHYSADDR)get_physaddr_fm_api_header; |
| } else { |
| a->save_offset = (u8 *)fi; |
| a->fm_api_sgc.get_phys_addr = |
| (PGETPHYSADDR)get_physaddr_fm_api; |
| } |
| |
| rq->comp_cb = complete_fm_api_req; |
| a->fm_api_command_done = 0; |
| a->fm_api_sgc.cur_offset = a->save_offset; |
| |
| if (!esas2r_fm_api(a, (struct esas2r_flash_img *)a->save_offset, rq, |
| &a->fm_api_sgc)) |
| goto all_done; |
| |
| /* Now wait around for it to complete. */ |
| while (!a->fm_api_command_done) |
| wait_event_interruptible(a->fm_api_waiter, |
| a->fm_api_command_done); |
| all_done: |
| if (fi == &a->firmware.header) { |
| memcpy(fi, a->firmware.header_buff, |
| sizeof(struct esas2r_flash_img)); |
| |
| dma_free_coherent(&a->pcid->dev, |
| (size_t)sizeof(struct esas2r_flash_img), |
| a->firmware.header_buff, |
| (dma_addr_t)a->firmware.header_buff_phys); |
| } |
| free_req: |
| esas2r_free_request(a, (struct esas2r_request *)rq); |
| free_sem: |
| mutex_unlock(&a->fm_api_mutex); |
| return; |
| |
| } |
| |
| static void complete_nvr_req(struct esas2r_adapter *a, |
| struct esas2r_request *rq) |
| { |
| a->nvram_command_done = 1; |
| wake_up_interruptible(&a->nvram_waiter); |
| } |
| |
| /* Callback for building scatter/gather lists for buffered ioctls */ |
| static u32 get_physaddr_buffered_ioctl(struct esas2r_sg_context *sgc, |
| u64 *addr) |
| { |
| int offset = (u8 *)sgc->cur_offset - esas2r_buffered_ioctl; |
| |
| (*addr) = esas2r_buffered_ioctl_addr + offset; |
| return esas2r_buffered_ioctl_size - offset; |
| } |
| |
| static void complete_buffered_ioctl_req(struct esas2r_adapter *a, |
| struct esas2r_request *rq) |
| { |
| a->buffered_ioctl_done = 1; |
| wake_up_interruptible(&a->buffered_ioctl_waiter); |
| } |
| |
| static u8 handle_buffered_ioctl(struct esas2r_buffered_ioctl *bi) |
| { |
| struct esas2r_adapter *a = bi->a; |
| struct esas2r_request *rq; |
| struct esas2r_sg_context sgc; |
| u8 result = IOCTL_SUCCESS; |
| |
| if (down_interruptible(&buffered_ioctl_semaphore)) |
| return IOCTL_OUT_OF_RESOURCES; |
| |
| /* allocate a buffer or use the existing buffer. */ |
| if (esas2r_buffered_ioctl) { |
| if (esas2r_buffered_ioctl_size < bi->length) { |
| /* free the too-small buffer and get a new one */ |
| dma_free_coherent(&a->pcid->dev, |
| (size_t)esas2r_buffered_ioctl_size, |
| esas2r_buffered_ioctl, |
| esas2r_buffered_ioctl_addr); |
| |
| goto allocate_buffer; |
| } |
| } else { |
| allocate_buffer: |
| esas2r_buffered_ioctl_size = bi->length; |
| esas2r_buffered_ioctl_pcid = a->pcid; |
| esas2r_buffered_ioctl = dma_alloc_coherent(&a->pcid->dev, |
| (size_t) |
| esas2r_buffered_ioctl_size, |
| & |
| esas2r_buffered_ioctl_addr, |
| GFP_KERNEL); |
| } |
| |
| if (!esas2r_buffered_ioctl) { |
| esas2r_log(ESAS2R_LOG_CRIT, |
| "could not allocate %d bytes of consistent memory " |
| "for a buffered ioctl!", |
| bi->length); |
| |
| esas2r_debug("buffered ioctl alloc failure"); |
| result = IOCTL_OUT_OF_RESOURCES; |
| goto exit_cleanly; |
| } |
| |
| memcpy(esas2r_buffered_ioctl, bi->ioctl, bi->length); |
| |
| rq = esas2r_alloc_request(a); |
| if (rq == NULL) { |
| esas2r_log(ESAS2R_LOG_CRIT, |
| "could not allocate an internal request"); |
| |
| result = IOCTL_OUT_OF_RESOURCES; |
| esas2r_debug("buffered ioctl - no requests"); |
| goto exit_cleanly; |
| } |
| |
| a->buffered_ioctl_done = 0; |
| rq->comp_cb = complete_buffered_ioctl_req; |
| sgc.cur_offset = esas2r_buffered_ioctl + bi->offset; |
| sgc.get_phys_addr = (PGETPHYSADDR)get_physaddr_buffered_ioctl; |
| sgc.length = esas2r_buffered_ioctl_size; |
| |
| if (!(*bi->callback)(a, rq, &sgc, bi->context)) { |
| /* completed immediately, no need to wait */ |
| a->buffered_ioctl_done = 0; |
| goto free_andexit_cleanly; |
| } |
| |
| /* now wait around for it to complete. */ |
| while (!a->buffered_ioctl_done) |
| wait_event_interruptible(a->buffered_ioctl_waiter, |
| a->buffered_ioctl_done); |
| |
| free_andexit_cleanly: |
| if (result == IOCTL_SUCCESS && bi->done_callback) |
| (*bi->done_callback)(a, rq, bi->done_context); |
| |
| esas2r_free_request(a, rq); |
| |
| exit_cleanly: |
| if (result == IOCTL_SUCCESS) |
| memcpy(bi->ioctl, esas2r_buffered_ioctl, bi->length); |
| |
| up(&buffered_ioctl_semaphore); |
| return result; |
| } |
| |
| /* SMP ioctl support */ |
| static int smp_ioctl_callback(struct esas2r_adapter *a, |
| struct esas2r_request *rq, |
| struct esas2r_sg_context *sgc, void *context) |
| { |
| struct atto_ioctl_smp *si = |
| (struct atto_ioctl_smp *)esas2r_buffered_ioctl; |
| |
| esas2r_sgc_init(sgc, a, rq, rq->vrq->ioctl.sge); |
| esas2r_build_ioctl_req(a, rq, sgc->length, VDA_IOCTL_SMP); |
| |
| if (!esas2r_build_sg_list(a, rq, sgc)) { |
| si->status = ATTO_STS_OUT_OF_RSRC; |
| return false; |
| } |
| |
| esas2r_start_request(a, rq); |
| return true; |
| } |
| |
| static u8 handle_smp_ioctl(struct esas2r_adapter *a, struct atto_ioctl_smp *si) |
| { |
| struct esas2r_buffered_ioctl bi; |
| |
| memset(&bi, 0, sizeof(bi)); |
| |
| bi.a = a; |
| bi.ioctl = si; |
| bi.length = sizeof(struct atto_ioctl_smp) |
| + le32_to_cpu(si->req_length) |
| + le32_to_cpu(si->rsp_length); |
| bi.offset = 0; |
| bi.callback = smp_ioctl_callback; |
| return handle_buffered_ioctl(&bi); |
| } |
| |
| |
| /* CSMI ioctl support */ |
| static void esas2r_csmi_ioctl_tunnel_comp_cb(struct esas2r_adapter *a, |
| struct esas2r_request *rq) |
| { |
| rq->target_id = le16_to_cpu(rq->func_rsp.ioctl_rsp.csmi.target_id); |
| rq->vrq->scsi.flags |= cpu_to_le32(rq->func_rsp.ioctl_rsp.csmi.lun); |
| |
| /* Now call the original completion callback. */ |
| (*rq->aux_req_cb)(a, rq); |
| } |
| |
| /* Tunnel a CSMI IOCTL to the back end driver for processing. */ |
| static bool csmi_ioctl_tunnel(struct esas2r_adapter *a, |
| union atto_ioctl_csmi *ci, |
| struct esas2r_request *rq, |
| struct esas2r_sg_context *sgc, |
| u32 ctrl_code, |
| u16 target_id) |
| { |
| struct atto_vda_ioctl_req *ioctl = &rq->vrq->ioctl; |
| |
| if (test_bit(AF_DEGRADED_MODE, &a->flags)) |
| return false; |
| |
| esas2r_sgc_init(sgc, a, rq, rq->vrq->ioctl.sge); |
| esas2r_build_ioctl_req(a, rq, sgc->length, VDA_IOCTL_CSMI); |
| ioctl->csmi.ctrl_code = cpu_to_le32(ctrl_code); |
| ioctl->csmi.target_id = cpu_to_le16(target_id); |
| ioctl->csmi.lun = (u8)le32_to_cpu(rq->vrq->scsi.flags); |
| |
| /* |
| * Always usurp the completion callback since the interrupt callback |
| * mechanism may be used. |
| */ |
| rq->aux_req_cx = ci; |
| rq->aux_req_cb = rq->comp_cb; |
| rq->comp_cb = esas2r_csmi_ioctl_tunnel_comp_cb; |
| |
| if (!esas2r_build_sg_list(a, rq, sgc)) |
| return false; |
| |
| esas2r_start_request(a, rq); |
| return true; |
| } |
| |
| static bool check_lun(struct scsi_lun lun) |
| { |
| bool result; |
| |
| result = ((lun.scsi_lun[7] == 0) && |
| (lun.scsi_lun[6] == 0) && |
| (lun.scsi_lun[5] == 0) && |
| (lun.scsi_lun[4] == 0) && |
| (lun.scsi_lun[3] == 0) && |
| (lun.scsi_lun[2] == 0) && |
| /* Byte 1 is intentionally skipped */ |
| (lun.scsi_lun[0] == 0)); |
| |
| return result; |
| } |
| |
| static int csmi_ioctl_callback(struct esas2r_adapter *a, |
| struct esas2r_request *rq, |
| struct esas2r_sg_context *sgc, void *context) |
| { |
| struct atto_csmi *ci = (struct atto_csmi *)context; |
| union atto_ioctl_csmi *ioctl_csmi = |
| (union atto_ioctl_csmi *)esas2r_buffered_ioctl; |
| u8 path = 0; |
| u8 tid = 0; |
| u8 lun = 0; |
| u32 sts = CSMI_STS_SUCCESS; |
| struct esas2r_target *t; |
| unsigned long flags; |
| |
| if (ci->control_code == CSMI_CC_GET_DEV_ADDR) { |
| struct atto_csmi_get_dev_addr *gda = &ci->data.dev_addr; |
| |
| path = gda->path_id; |
| tid = gda->target_id; |
| lun = gda->lun; |
| } else if (ci->control_code == CSMI_CC_TASK_MGT) { |
| struct atto_csmi_task_mgmt *tm = &ci->data.tsk_mgt; |
| |
| path = tm->path_id; |
| tid = tm->target_id; |
| lun = tm->lun; |
| } |
| |
| if (path > 0) { |
| rq->func_rsp.ioctl_rsp.csmi.csmi_status = cpu_to_le32( |
| CSMI_STS_INV_PARAM); |
| return false; |
| } |
| |
| rq->target_id = tid; |
| rq->vrq->scsi.flags |= cpu_to_le32(lun); |
| |
| switch (ci->control_code) { |
| case CSMI_CC_GET_DRVR_INFO: |
| { |
| struct atto_csmi_get_driver_info *gdi = &ioctl_csmi->drvr_info; |
| |
| strcpy(gdi->description, esas2r_get_model_name(a)); |
| gdi->csmi_major_rev = CSMI_MAJOR_REV; |
| gdi->csmi_minor_rev = CSMI_MINOR_REV; |
| break; |
| } |
| |
| case CSMI_CC_GET_CNTLR_CFG: |
| { |
| struct atto_csmi_get_cntlr_cfg *gcc = &ioctl_csmi->cntlr_cfg; |
| |
| gcc->base_io_addr = 0; |
| pci_read_config_dword(a->pcid, PCI_BASE_ADDRESS_2, |
| &gcc->base_memaddr_lo); |
| pci_read_config_dword(a->pcid, PCI_BASE_ADDRESS_3, |
| &gcc->base_memaddr_hi); |
| gcc->board_id = MAKEDWORD(a->pcid->subsystem_device, |
| a->pcid->subsystem_vendor); |
| gcc->slot_num = CSMI_SLOT_NUM_UNKNOWN; |
| gcc->cntlr_class = CSMI_CNTLR_CLASS_HBA; |
| gcc->io_bus_type = CSMI_BUS_TYPE_PCI; |
| gcc->pci_addr.bus_num = a->pcid->bus->number; |
| gcc->pci_addr.device_num = PCI_SLOT(a->pcid->devfn); |
| gcc->pci_addr.function_num = PCI_FUNC(a->pcid->devfn); |
| |
| memset(gcc->serial_num, 0, sizeof(gcc->serial_num)); |
| |
| gcc->major_rev = LOBYTE(LOWORD(a->fw_version)); |
| gcc->minor_rev = HIBYTE(LOWORD(a->fw_version)); |
| gcc->build_rev = LOBYTE(HIWORD(a->fw_version)); |
| gcc->release_rev = HIBYTE(HIWORD(a->fw_version)); |
| gcc->bios_major_rev = HIBYTE(HIWORD(a->flash_ver)); |
| gcc->bios_minor_rev = LOBYTE(HIWORD(a->flash_ver)); |
| gcc->bios_build_rev = LOWORD(a->flash_ver); |
| |
| if (test_bit(AF2_THUNDERLINK, &a->flags2)) |
| gcc->cntlr_flags = CSMI_CNTLRF_SAS_HBA |
| | CSMI_CNTLRF_SATA_HBA; |
| else |
| gcc->cntlr_flags = CSMI_CNTLRF_SAS_RAID |
| | CSMI_CNTLRF_SATA_RAID; |
| |
| gcc->rrom_major_rev = 0; |
| gcc->rrom_minor_rev = 0; |
| gcc->rrom_build_rev = 0; |
| gcc->rrom_release_rev = 0; |
| gcc->rrom_biosmajor_rev = 0; |
| gcc->rrom_biosminor_rev = 0; |
| gcc->rrom_biosbuild_rev = 0; |
| gcc->rrom_biosrelease_rev = 0; |
| break; |
| } |
| |
| case CSMI_CC_GET_CNTLR_STS: |
| { |
| struct atto_csmi_get_cntlr_sts *gcs = &ioctl_csmi->cntlr_sts; |
| |
| if (test_bit(AF_DEGRADED_MODE, &a->flags)) |
| gcs->status = CSMI_CNTLR_STS_FAILED; |
| else |
| gcs->status = CSMI_CNTLR_STS_GOOD; |
| |
| gcs->offline_reason = CSMI_OFFLINE_NO_REASON; |
| break; |
| } |
| |
| case CSMI_CC_FW_DOWNLOAD: |
| case CSMI_CC_GET_RAID_INFO: |
| case CSMI_CC_GET_RAID_CFG: |
| |
| sts = CSMI_STS_BAD_CTRL_CODE; |
| break; |
| |
| case CSMI_CC_SMP_PASSTHRU: |
| case CSMI_CC_SSP_PASSTHRU: |
| case CSMI_CC_STP_PASSTHRU: |
| case CSMI_CC_GET_PHY_INFO: |
| case CSMI_CC_SET_PHY_INFO: |
| case CSMI_CC_GET_LINK_ERRORS: |
| case CSMI_CC_GET_SATA_SIG: |
| case CSMI_CC_GET_CONN_INFO: |
| case CSMI_CC_PHY_CTRL: |
| |
| if (!csmi_ioctl_tunnel(a, ioctl_csmi, rq, sgc, |
| ci->control_code, |
| ESAS2R_TARG_ID_INV)) { |
| sts = CSMI_STS_FAILED; |
| break; |
| } |
| |
| return true; |
| |
| case CSMI_CC_GET_SCSI_ADDR: |
| { |
| struct atto_csmi_get_scsi_addr *gsa = &ioctl_csmi->scsi_addr; |
| |
| struct scsi_lun lun; |
| |
| memcpy(&lun, gsa->sas_lun, sizeof(struct scsi_lun)); |
| |
| if (!check_lun(lun)) { |
| sts = CSMI_STS_NO_SCSI_ADDR; |
| break; |
| } |
| |
| /* make sure the device is present */ |
| spin_lock_irqsave(&a->mem_lock, flags); |
| t = esas2r_targ_db_find_by_sas_addr(a, (u64 *)gsa->sas_addr); |
| spin_unlock_irqrestore(&a->mem_lock, flags); |
| |
| if (t == NULL) { |
| sts = CSMI_STS_NO_SCSI_ADDR; |
| break; |
| } |
| |
| gsa->host_index = 0xFF; |
| gsa->lun = gsa->sas_lun[1]; |
| rq->target_id = esas2r_targ_get_id(t, a); |
| break; |
| } |
| |
| case CSMI_CC_GET_DEV_ADDR: |
| { |
| struct atto_csmi_get_dev_addr *gda = &ioctl_csmi->dev_addr; |
| |
| /* make sure the target is present */ |
| t = a->targetdb + rq->target_id; |
| |
| if (t >= a->targetdb_end |
| || t->target_state != TS_PRESENT |
| || t->sas_addr == 0) { |
| sts = CSMI_STS_NO_DEV_ADDR; |
| break; |
| } |
| |
| /* fill in the result */ |
| *(u64 *)gda->sas_addr = t->sas_addr; |
| memset(gda->sas_lun, 0, sizeof(gda->sas_lun)); |
| gda->sas_lun[1] = (u8)le32_to_cpu(rq->vrq->scsi.flags); |
| break; |
| } |
| |
| case CSMI_CC_TASK_MGT: |
| |
| /* make sure the target is present */ |
| t = a->targetdb + rq->target_id; |
| |
| if (t >= a->targetdb_end |
| || t->target_state != TS_PRESENT |
| || !(t->flags & TF_PASS_THRU)) { |
| sts = CSMI_STS_NO_DEV_ADDR; |
| break; |
| } |
| |
| if (!csmi_ioctl_tunnel(a, ioctl_csmi, rq, sgc, |
| ci->control_code, |
| t->phys_targ_id)) { |
| sts = CSMI_STS_FAILED; |
| break; |
| } |
| |
| return true; |
| |
| default: |
| |
| sts = CSMI_STS_BAD_CTRL_CODE; |
| break; |
| } |
| |
| rq->func_rsp.ioctl_rsp.csmi.csmi_status = cpu_to_le32(sts); |
| |
| return false; |
| } |
| |
| |
| static void csmi_ioctl_done_callback(struct esas2r_adapter *a, |
| struct esas2r_request *rq, void *context) |
| { |
| struct atto_csmi *ci = (struct atto_csmi *)context; |
| union atto_ioctl_csmi *ioctl_csmi = |
| (union atto_ioctl_csmi *)esas2r_buffered_ioctl; |
| |
| switch (ci->control_code) { |
| case CSMI_CC_GET_DRVR_INFO: |
| { |
| struct atto_csmi_get_driver_info *gdi = |
| &ioctl_csmi->drvr_info; |
| |
| strcpy(gdi->name, ESAS2R_VERSION_STR); |
| |
| gdi->major_rev = ESAS2R_MAJOR_REV; |
| gdi->minor_rev = ESAS2R_MINOR_REV; |
| gdi->build_rev = 0; |
| gdi->release_rev = 0; |
| break; |
| } |
| |
| case CSMI_CC_GET_SCSI_ADDR: |
| { |
| struct atto_csmi_get_scsi_addr *gsa = &ioctl_csmi->scsi_addr; |
| |
| if (le32_to_cpu(rq->func_rsp.ioctl_rsp.csmi.csmi_status) == |
| CSMI_STS_SUCCESS) { |
| gsa->target_id = rq->target_id; |
| gsa->path_id = 0; |
| } |
| |
| break; |
| } |
| } |
| |
| ci->status = le32_to_cpu(rq->func_rsp.ioctl_rsp.csmi.csmi_status); |
| } |
| |
| |
| static u8 handle_csmi_ioctl(struct esas2r_adapter *a, struct atto_csmi *ci) |
| { |
| struct esas2r_buffered_ioctl bi; |
| |
| memset(&bi, 0, sizeof(bi)); |
| |
| bi.a = a; |
| bi.ioctl = &ci->data; |
| bi.length = sizeof(union atto_ioctl_csmi); |
| bi.offset = 0; |
| bi.callback = csmi_ioctl_callback; |
| bi.context = ci; |
| bi.done_callback = csmi_ioctl_done_callback; |
| bi.done_context = ci; |
| |
| return handle_buffered_ioctl(&bi); |
| } |
| |
| /* ATTO HBA ioctl support */ |
| |
| /* Tunnel an ATTO HBA IOCTL to the back end driver for processing. */ |
| static bool hba_ioctl_tunnel(struct esas2r_adapter *a, |
| struct atto_ioctl *hi, |
| struct esas2r_request *rq, |
| struct esas2r_sg_context *sgc) |
| { |
| esas2r_sgc_init(sgc, a, rq, rq->vrq->ioctl.sge); |
| |
| esas2r_build_ioctl_req(a, rq, sgc->length, VDA_IOCTL_HBA); |
| |
| if (!esas2r_build_sg_list(a, rq, sgc)) { |
| hi->status = ATTO_STS_OUT_OF_RSRC; |
| |
| return false; |
| } |
| |
| esas2r_start_request(a, rq); |
| |
| return true; |
| } |
| |
| static void scsi_passthru_comp_cb(struct esas2r_adapter *a, |
| struct esas2r_request *rq) |
| { |
| struct atto_ioctl *hi = (struct atto_ioctl *)rq->aux_req_cx; |
| struct atto_hba_scsi_pass_thru *spt = &hi->data.scsi_pass_thru; |
| u8 sts = ATTO_SPT_RS_FAILED; |
| |
| spt->scsi_status = rq->func_rsp.scsi_rsp.scsi_stat; |
| spt->sense_length = rq->sense_len; |
| spt->residual_length = |
| le32_to_cpu(rq->func_rsp.scsi_rsp.residual_length); |
| |
| switch (rq->req_stat) { |
| case RS_SUCCESS: |
| case RS_SCSI_ERROR: |
| sts = ATTO_SPT_RS_SUCCESS; |
| break; |
| case RS_UNDERRUN: |
| sts = ATTO_SPT_RS_UNDERRUN; |
| break; |
| case RS_OVERRUN: |
| sts = ATTO_SPT_RS_OVERRUN; |
| break; |
| case RS_SEL: |
| case RS_SEL2: |
| sts = ATTO_SPT_RS_NO_DEVICE; |
| break; |
| case RS_NO_LUN: |
| sts = ATTO_SPT_RS_NO_LUN; |
| break; |
| case RS_TIMEOUT: |
| sts = ATTO_SPT_RS_TIMEOUT; |
| break; |
| case RS_DEGRADED: |
| sts = ATTO_SPT_RS_DEGRADED; |
| break; |
| case RS_BUSY: |
| sts = ATTO_SPT_RS_BUSY; |
| break; |
| case RS_ABORTED: |
| sts = ATTO_SPT_RS_ABORTED; |
| break; |
| case RS_RESET: |
| sts = ATTO_SPT_RS_BUS_RESET; |
| break; |
| } |
| |
| spt->req_status = sts; |
| |
| /* Update the target ID to the next one present. */ |
| spt->target_id = |
| esas2r_targ_db_find_next_present(a, (u16)spt->target_id); |
| |
| /* Done, call the completion callback. */ |
| (*rq->aux_req_cb)(a, rq); |
| } |
| |
| static int hba_ioctl_callback(struct esas2r_adapter *a, |
| struct esas2r_request *rq, |
| struct esas2r_sg_context *sgc, |
| void *context) |
| { |
| struct atto_ioctl *hi = (struct atto_ioctl *)esas2r_buffered_ioctl; |
| |
| hi->status = ATTO_STS_SUCCESS; |
| |
| switch (hi->function) { |
| case ATTO_FUNC_GET_ADAP_INFO: |
| { |
| u8 *class_code = (u8 *)&a->pcid->class; |
| |
| struct atto_hba_get_adapter_info *gai = |
| &hi->data.get_adap_info; |
| |
| if (hi->flags & HBAF_TUNNEL) { |
| hi->status = ATTO_STS_UNSUPPORTED; |
| break; |
| } |
| |
| if (hi->version > ATTO_VER_GET_ADAP_INFO0) { |
| hi->status = ATTO_STS_INV_VERSION; |
| hi->version = ATTO_VER_GET_ADAP_INFO0; |
| break; |
| } |
| |
| memset(gai, 0, sizeof(*gai)); |
| |
| gai->pci.vendor_id = a->pcid->vendor; |
| gai->pci.device_id = a->pcid->device; |
| gai->pci.ss_vendor_id = a->pcid->subsystem_vendor; |
| gai->pci.ss_device_id = a->pcid->subsystem_device; |
| gai->pci.class_code[0] = class_code[0]; |
| gai->pci.class_code[1] = class_code[1]; |
| gai->pci.class_code[2] = class_code[2]; |
| gai->pci.rev_id = a->pcid->revision; |
| gai->pci.bus_num = a->pcid->bus->number; |
| gai->pci.dev_num = PCI_SLOT(a->pcid->devfn); |
| gai->pci.func_num = PCI_FUNC(a->pcid->devfn); |
| |
| if (pci_is_pcie(a->pcid)) { |
| u16 stat; |
| u32 caps; |
| |
| pcie_capability_read_word(a->pcid, PCI_EXP_LNKSTA, |
| &stat); |
| pcie_capability_read_dword(a->pcid, PCI_EXP_LNKCAP, |
| &caps); |
| |
| gai->pci.link_speed_curr = |
| (u8)(stat & PCI_EXP_LNKSTA_CLS); |
| gai->pci.link_speed_max = |
| (u8)(caps & PCI_EXP_LNKCAP_SLS); |
| gai->pci.link_width_curr = |
| (u8)((stat & PCI_EXP_LNKSTA_NLW) |
| >> PCI_EXP_LNKSTA_NLW_SHIFT); |
| gai->pci.link_width_max = |
| (u8)((caps & PCI_EXP_LNKCAP_MLW) |
| >> 4); |
| } |
| |
| gai->pci.msi_vector_cnt = 1; |
| |
| if (a->pcid->msix_enabled) |
| gai->pci.interrupt_mode = ATTO_GAI_PCIIM_MSIX; |
| else if (a->pcid->msi_enabled) |
| gai->pci.interrupt_mode = ATTO_GAI_PCIIM_MSI; |
| else |
| gai->pci.interrupt_mode = ATTO_GAI_PCIIM_LEGACY; |
| |
| gai->adap_type = ATTO_GAI_AT_ESASRAID2; |
| |
| if (test_bit(AF2_THUNDERLINK, &a->flags2)) |
| gai->adap_type = ATTO_GAI_AT_TLSASHBA; |
| |
| if (test_bit(AF_DEGRADED_MODE, &a->flags)) |
| gai->adap_flags |= ATTO_GAI_AF_DEGRADED; |
| |
| gai->adap_flags |= ATTO_GAI_AF_SPT_SUPP | |
| ATTO_GAI_AF_DEVADDR_SUPP; |
| |
| if (a->pcid->subsystem_device == ATTO_ESAS_R60F |
| || a->pcid->subsystem_device == ATTO_ESAS_R608 |
| || a->pcid->subsystem_device == ATTO_ESAS_R644 |
| || a->pcid->subsystem_device == ATTO_TSSC_3808E) |
| gai->adap_flags |= ATTO_GAI_AF_VIRT_SES; |
| |
| gai->num_ports = ESAS2R_NUM_PHYS; |
| gai->num_phys = ESAS2R_NUM_PHYS; |
| |
| strcpy(gai->firmware_rev, a->fw_rev); |
| strcpy(gai->flash_rev, a->flash_rev); |
| strcpy(gai->model_name_short, esas2r_get_model_name_short(a)); |
| strcpy(gai->model_name, esas2r_get_model_name(a)); |
| |
| gai->num_targets = ESAS2R_MAX_TARGETS; |
| |
| gai->num_busses = 1; |
| gai->num_targsper_bus = gai->num_targets; |
| gai->num_lunsper_targ = 256; |
| |
| if (a->pcid->subsystem_device == ATTO_ESAS_R6F0 |
| || a->pcid->subsystem_device == ATTO_ESAS_R60F) |
| gai->num_connectors = 4; |
| else |
| gai->num_connectors = 2; |
| |
| gai->adap_flags2 |= ATTO_GAI_AF2_ADAP_CTRL_SUPP; |
| |
| gai->num_targets_backend = a->num_targets_backend; |
| |
| gai->tunnel_flags = a->ioctl_tunnel |
| & (ATTO_GAI_TF_MEM_RW |
| | ATTO_GAI_TF_TRACE |
| | ATTO_GAI_TF_SCSI_PASS_THRU |
| | ATTO_GAI_TF_GET_DEV_ADDR |
| | ATTO_GAI_TF_PHY_CTRL |
| | ATTO_GAI_TF_CONN_CTRL |
| | ATTO_GAI_TF_GET_DEV_INFO); |
| break; |
| } |
| |
| case ATTO_FUNC_GET_ADAP_ADDR: |
| { |
| struct atto_hba_get_adapter_address *gaa = |
| &hi->data.get_adap_addr; |
| |
| if (hi->flags & HBAF_TUNNEL) { |
| hi->status = ATTO_STS_UNSUPPORTED; |
| break; |
| } |
| |
| if (hi->version > ATTO_VER_GET_ADAP_ADDR0) { |
| hi->status = ATTO_STS_INV_VERSION; |
| hi->version = ATTO_VER_GET_ADAP_ADDR0; |
| } else if (gaa->addr_type == ATTO_GAA_AT_PORT |
| || gaa->addr_type == ATTO_GAA_AT_NODE) { |
| if (gaa->addr_type == ATTO_GAA_AT_PORT |
| && gaa->port_id >= ESAS2R_NUM_PHYS) { |
| hi->status = ATTO_STS_NOT_APPL; |
| } else { |
| memcpy((u64 *)gaa->address, |
| &a->nvram->sas_addr[0], sizeof(u64)); |
| gaa->addr_len = sizeof(u64); |
| } |
| } else { |
| hi->status = ATTO_STS_INV_PARAM; |
| } |
| |
| break; |
| } |
| |
| case ATTO_FUNC_MEM_RW: |
| { |
| if (hi->flags & HBAF_TUNNEL) { |
| if (hba_ioctl_tunnel(a, hi, rq, sgc)) |
| return true; |
| |
| break; |
| } |
| |
| hi->status = ATTO_STS_UNSUPPORTED; |
| |
| break; |
| } |
| |
| case ATTO_FUNC_TRACE: |
| { |
| struct atto_hba_trace *trc = &hi->data.trace; |
| |
| if (hi->flags & HBAF_TUNNEL) { |
| if (hba_ioctl_tunnel(a, hi, rq, sgc)) |
| return true; |
| |
| break; |
| } |
| |
| if (hi->version > ATTO_VER_TRACE1) { |
| hi->status = ATTO_STS_INV_VERSION; |
| hi->version = ATTO_VER_TRACE1; |
| break; |
| } |
| |
| if (trc->trace_type == ATTO_TRC_TT_FWCOREDUMP |
| && hi->version >= ATTO_VER_TRACE1) { |
| if (trc->trace_func == ATTO_TRC_TF_UPLOAD) { |
| u32 len = hi->data_length; |
| u32 offset = trc->current_offset; |
| u32 total_len = ESAS2R_FWCOREDUMP_SZ; |
| |
| /* Size is zero if a core dump isn't present */ |
| if (!test_bit(AF2_COREDUMP_SAVED, &a->flags2)) |
| total_len = 0; |
| |
| if (len > total_len) |
| len = total_len; |
| |
| if (offset >= total_len |
| || offset + len > total_len |
| || len == 0) { |
| hi->status = ATTO_STS_INV_PARAM; |
| break; |
| } |
| |
| memcpy(trc + 1, |
| a->fw_coredump_buff + offset, |
| len); |
| |
| hi->data_length = len; |
| } else if (trc->trace_func == ATTO_TRC_TF_RESET) { |
| memset(a->fw_coredump_buff, 0, |
| ESAS2R_FWCOREDUMP_SZ); |
| |
| clear_bit(AF2_COREDUMP_SAVED, &a->flags2); |
| } else if (trc->trace_func != ATTO_TRC_TF_GET_INFO) { |
| hi->status = ATTO_STS_UNSUPPORTED; |
| break; |
| } |
| |
| /* Always return all the info we can. */ |
| trc->trace_mask = 0; |
| trc->current_offset = 0; |
| trc->total_length = ESAS2R_FWCOREDUMP_SZ; |
| |
| /* Return zero length buffer if core dump not present */ |
| if (!test_bit(AF2_COREDUMP_SAVED, &a->flags2)) |
| trc->total_length = 0; |
| } else { |
| hi->status = ATTO_STS_UNSUPPORTED; |
| } |
| |
| break; |
| } |
| |
| case ATTO_FUNC_SCSI_PASS_THRU: |
| { |
| struct atto_hba_scsi_pass_thru *spt = &hi->data.scsi_pass_thru; |
| struct scsi_lun lun; |
| |
| memcpy(&lun, spt->lun, sizeof(struct scsi_lun)); |
| |
| if (hi->flags & HBAF_TUNNEL) { |
| if (hba_ioctl_tunnel(a, hi, rq, sgc)) |
| return true; |
| |
| break; |
| } |
| |
| if (hi->version > ATTO_VER_SCSI_PASS_THRU0) { |
| hi->status = ATTO_STS_INV_VERSION; |
| hi->version = ATTO_VER_SCSI_PASS_THRU0; |
| break; |
| } |
| |
| if (spt->target_id >= ESAS2R_MAX_TARGETS || !check_lun(lun)) { |
| hi->status = ATTO_STS_INV_PARAM; |
| break; |
| } |
| |
| esas2r_sgc_init(sgc, a, rq, NULL); |
| |
| sgc->length = hi->data_length; |
| sgc->cur_offset += offsetof(struct atto_ioctl, data.byte) |
| + sizeof(struct atto_hba_scsi_pass_thru); |
| |
| /* Finish request initialization */ |
| rq->target_id = (u16)spt->target_id; |
| rq->vrq->scsi.flags |= cpu_to_le32(spt->lun[1]); |
| memcpy(rq->vrq->scsi.cdb, spt->cdb, 16); |
| rq->vrq->scsi.length = cpu_to_le32(hi->data_length); |
| rq->sense_len = spt->sense_length; |
| rq->sense_buf = (u8 *)spt->sense_data; |
| /* NOTE: we ignore spt->timeout */ |
| |
| /* |
| * always usurp the completion callback since the interrupt |
| * callback mechanism may be used. |
| */ |
| |
| rq->aux_req_cx = hi; |
| rq->aux_req_cb = rq->comp_cb; |
| rq->comp_cb = scsi_passthru_comp_cb; |
| |
| if (spt->flags & ATTO_SPTF_DATA_IN) { |
| rq->vrq->scsi.flags |= cpu_to_le32(FCP_CMND_RDD); |
| } else if (spt->flags & ATTO_SPTF_DATA_OUT) { |
| rq->vrq->scsi.flags |= cpu_to_le32(FCP_CMND_WRD); |
| } else { |
| if (sgc->length) { |
| hi->status = ATTO_STS_INV_PARAM; |
| break; |
| } |
| } |
| |
| if (spt->flags & ATTO_SPTF_ORDERED_Q) |
| rq->vrq->scsi.flags |= |
| cpu_to_le32(FCP_CMND_TA_ORDRD_Q); |
| else if (spt->flags & ATTO_SPTF_HEAD_OF_Q) |
| rq->vrq->scsi.flags |= cpu_to_le32(FCP_CMND_TA_HEAD_Q); |
| |
| |
| if (!esas2r_build_sg_list(a, rq, sgc)) { |
| hi->status = ATTO_STS_OUT_OF_RSRC; |
| break; |
| } |
| |
| esas2r_start_request(a, rq); |
| |
| return true; |
| } |
| |
| case ATTO_FUNC_GET_DEV_ADDR: |
| { |
| struct atto_hba_get_device_address *gda = |
| &hi->data.get_dev_addr; |
| struct esas2r_target *t; |
| |
| if (hi->flags & HBAF_TUNNEL) { |
| if (hba_ioctl_tunnel(a, hi, rq, sgc)) |
| return true; |
| |
| break; |
| } |
| |
| if (hi->version > ATTO_VER_GET_DEV_ADDR0) { |
| hi->status = ATTO_STS_INV_VERSION; |
| hi->version = ATTO_VER_GET_DEV_ADDR0; |
| break; |
| } |
| |
| if (gda->target_id >= ESAS2R_MAX_TARGETS) { |
| hi->status = ATTO_STS_INV_PARAM; |
| break; |
| } |
| |
| t = a->targetdb + (u16)gda->target_id; |
| |
| if (t->target_state != TS_PRESENT) { |
| hi->status = ATTO_STS_FAILED; |
| } else if (gda->addr_type == ATTO_GDA_AT_PORT) { |
| if (t->sas_addr == 0) { |
| hi->status = ATTO_STS_UNSUPPORTED; |
| } else { |
| *(u64 *)gda->address = t->sas_addr; |
| |
| gda->addr_len = sizeof(u64); |
| } |
| } else if (gda->addr_type == ATTO_GDA_AT_NODE) { |
| hi->status = ATTO_STS_NOT_APPL; |
| } else { |
| hi->status = ATTO_STS_INV_PARAM; |
| } |
| |
| /* update the target ID to the next one present. */ |
| |
| gda->target_id = |
| esas2r_targ_db_find_next_present(a, |
| (u16)gda->target_id); |
| break; |
| } |
| |
| case ATTO_FUNC_PHY_CTRL: |
| case ATTO_FUNC_CONN_CTRL: |
| { |
| if (hba_ioctl_tunnel(a, hi, rq, sgc)) |
| return true; |
| |
| break; |
| } |
| |
| case ATTO_FUNC_ADAP_CTRL: |
| { |
| struct atto_hba_adap_ctrl *ac = &hi->data.adap_ctrl; |
| |
| if (hi->flags & HBAF_TUNNEL) { |
| hi->status = ATTO_STS_UNSUPPORTED; |
| break; |
| } |
| |
| if (hi->version > ATTO_VER_ADAP_CTRL0) { |
| hi->status = ATTO_STS_INV_VERSION; |
| hi->version = ATTO_VER_ADAP_CTRL0; |
| break; |
| } |
| |
| if (ac->adap_func == ATTO_AC_AF_HARD_RST) { |
| esas2r_reset_adapter(a); |
| } else if (ac->adap_func != ATTO_AC_AF_GET_STATE) { |
| hi->status = ATTO_STS_UNSUPPORTED; |
| break; |
| } |
| |
| if (test_bit(AF_CHPRST_NEEDED, &a->flags)) |
| ac->adap_state = ATTO_AC_AS_RST_SCHED; |
| else if (test_bit(AF_CHPRST_PENDING, &a->flags)) |
| ac->adap_state = ATTO_AC_AS_RST_IN_PROG; |
| else if (test_bit(AF_DISC_PENDING, &a->flags)) |
| ac->adap_state = ATTO_AC_AS_RST_DISC; |
| else if (test_bit(AF_DISABLED, &a->flags)) |
| ac->adap_state = ATTO_AC_AS_DISABLED; |
| else if (test_bit(AF_DEGRADED_MODE, &a->flags)) |
| ac->adap_state = ATTO_AC_AS_DEGRADED; |
| else |
| ac->adap_state = ATTO_AC_AS_OK; |
| |
| break; |
| } |
| |
| case ATTO_FUNC_GET_DEV_INFO: |
| { |
| struct atto_hba_get_device_info *gdi = &hi->data.get_dev_info; |
| struct esas2r_target *t; |
| |
| if (hi->flags & HBAF_TUNNEL) { |
| if (hba_ioctl_tunnel(a, hi, rq, sgc)) |
| return true; |
| |
| break; |
| } |
| |
| if (hi->version > ATTO_VER_GET_DEV_INFO0) { |
| hi->status = ATTO_STS_INV_VERSION; |
| hi->version = ATTO_VER_GET_DEV_INFO0; |
| break; |
| } |
| |
| if (gdi->target_id >= ESAS2R_MAX_TARGETS) { |
| hi->status = ATTO_STS_INV_PARAM; |
| break; |
| } |
| |
| t = a->targetdb + (u16)gdi->target_id; |
| |
| /* update the target ID to the next one present. */ |
| |
| gdi->target_id = |
| esas2r_targ_db_find_next_present(a, |
| (u16)gdi->target_id); |
| |
| if (t->target_state != TS_PRESENT) { |
| hi->status = ATTO_STS_FAILED; |
| break; |
| } |
| |
| hi->status = ATTO_STS_UNSUPPORTED; |
| break; |
| } |
| |
| default: |
| |
| hi->status = ATTO_STS_INV_FUNC; |
| break; |
| } |
| |
| return false; |
| } |
| |
| static void hba_ioctl_done_callback(struct esas2r_adapter *a, |
| struct esas2r_request *rq, void *context) |
| { |
| struct atto_ioctl *ioctl_hba = |
| (struct atto_ioctl *)esas2r_buffered_ioctl; |
| |
| esas2r_debug("hba_ioctl_done_callback %d", a->index); |
| |
| if (ioctl_hba->function == ATTO_FUNC_GET_ADAP_INFO) { |
| struct atto_hba_get_adapter_info *gai = |
| &ioctl_hba->data.get_adap_info; |
| |
| esas2r_debug("ATTO_FUNC_GET_ADAP_INFO"); |
| |
| gai->drvr_rev_major = ESAS2R_MAJOR_REV; |
| gai->drvr_rev_minor = ESAS2R_MINOR_REV; |
| |
| strcpy(gai->drvr_rev_ascii, ESAS2R_VERSION_STR); |
| strcpy(gai->drvr_name, ESAS2R_DRVR_NAME); |
| |
| gai->num_busses = 1; |
| gai->num_targsper_bus = ESAS2R_MAX_ID + 1; |
| gai->num_lunsper_targ = 1; |
| } |
| } |
| |
| u8 handle_hba_ioctl(struct esas2r_adapter *a, |
| struct atto_ioctl *ioctl_hba) |
| { |
| struct esas2r_buffered_ioctl bi; |
| |
| memset(&bi, 0, sizeof(bi)); |
| |
| bi.a = a; |
| bi.ioctl = ioctl_hba; |
| bi.length = sizeof(struct atto_ioctl) + ioctl_hba->data_length; |
| bi.callback = hba_ioctl_callback; |
| bi.context = NULL; |
| bi.done_callback = hba_ioctl_done_callback; |
| bi.done_context = NULL; |
| bi.offset = 0; |
| |
| return handle_buffered_ioctl(&bi); |
| } |
| |
| |
| int esas2r_write_params(struct esas2r_adapter *a, struct esas2r_request *rq, |
| struct esas2r_sas_nvram *data) |
| { |
| int result = 0; |
| |
| a->nvram_command_done = 0; |
| rq->comp_cb = complete_nvr_req; |
| |
| if (esas2r_nvram_write(a, rq, data)) { |
| /* now wait around for it to complete. */ |
| while (!a->nvram_command_done) |
| wait_event_interruptible(a->nvram_waiter, |
| a->nvram_command_done); |
| ; |
| |
| /* done, check the status. */ |
| if (rq->req_stat == RS_SUCCESS) |
| result = 1; |
| } |
| return result; |
| } |
| |
| |
| /* This function only cares about ATTO-specific ioctls (atto_express_ioctl) */ |
| int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg) |
| { |
| struct atto_express_ioctl *ioctl = NULL; |
| struct esas2r_adapter *a; |
| struct esas2r_request *rq; |
| u16 code; |
| int err; |
| |
| esas2r_log(ESAS2R_LOG_DEBG, "ioctl (%p, %x, %p)", hostdata, cmd, arg); |
| |
| if ((arg == NULL) |
| || (cmd < EXPRESS_IOCTL_MIN) |
| || (cmd > EXPRESS_IOCTL_MAX)) |
| return -ENOTSUPP; |
| |
| ioctl = memdup_user(arg, sizeof(struct atto_express_ioctl)); |
| if (IS_ERR(ioctl)) { |
| esas2r_log(ESAS2R_LOG_WARN, |
| "ioctl_handler access_ok failed for cmd %u, address %p", |
| cmd, arg); |
| return PTR_ERR(ioctl); |
| } |
| |
| /* verify the signature */ |
| |
| if (memcmp(ioctl->header.signature, |
| EXPRESS_IOCTL_SIGNATURE, |
| EXPRESS_IOCTL_SIGNATURE_SIZE) != 0) { |
| esas2r_log(ESAS2R_LOG_WARN, "invalid signature"); |
| kfree(ioctl); |
| |
| return -ENOTSUPP; |
| } |
| |
| /* assume success */ |
| |
| ioctl->header.return_code = IOCTL_SUCCESS; |
| err = 0; |
| |
| /* |
| * handle EXPRESS_IOCTL_GET_CHANNELS |
| * without paying attention to channel |
| */ |
| |
| if (cmd == EXPRESS_IOCTL_GET_CHANNELS) { |
| int i = 0, k = 0; |
| |
| ioctl->data.chanlist.num_channels = 0; |
| |
| while (i < MAX_ADAPTERS) { |
| if (esas2r_adapters[i]) { |
| ioctl->data.chanlist.num_channels++; |
| ioctl->data.chanlist.channel[k] = i; |
| k++; |
| } |
| i++; |
| } |
| |
| goto ioctl_done; |
| } |
| |
| /* get the channel */ |
| |
| if (ioctl->header.channel == 0xFF) { |
| a = (struct esas2r_adapter *)hostdata; |
| } else { |
| if (ioctl->header.channel >= MAX_ADAPTERS || |
| esas2r_adapters[ioctl->header.channel] == NULL) { |
| ioctl->header.return_code = IOCTL_BAD_CHANNEL; |
| esas2r_log(ESAS2R_LOG_WARN, "bad channel value"); |
| kfree(ioctl); |
| |
| return -ENOTSUPP; |
| } |
| a = esas2r_adapters[ioctl->header.channel]; |
| } |
| |
| switch (cmd) { |
| case EXPRESS_IOCTL_RW_FIRMWARE: |
| |
| if (ioctl->data.fwrw.img_type == FW_IMG_FM_API) { |
| err = esas2r_write_fw(a, |
| (char *)ioctl->data.fwrw.image, |
| 0, |
| sizeof(struct |
| atto_express_ioctl)); |
| |
| if (err >= 0) { |
| err = esas2r_read_fw(a, |
| (char *)ioctl->data.fwrw. |
| image, |
| 0, |
| sizeof(struct |
| atto_express_ioctl)); |
| } |
| } else if (ioctl->data.fwrw.img_type == FW_IMG_FS_API) { |
| err = esas2r_write_fs(a, |
| (char *)ioctl->data.fwrw.image, |
| 0, |
| sizeof(struct |
| atto_express_ioctl)); |
| |
| if (err >= 0) { |
| err = esas2r_read_fs(a, |
| (char *)ioctl->data.fwrw. |
| image, |
| 0, |
| sizeof(struct |
| atto_express_ioctl)); |
| } |
| } else { |
| ioctl->header.return_code = IOCTL_BAD_FLASH_IMGTYPE; |
| } |
| |
| break; |
| |
| case EXPRESS_IOCTL_READ_PARAMS: |
| |
| memcpy(ioctl->data.prw.data_buffer, a->nvram, |
| sizeof(struct esas2r_sas_nvram)); |
| ioctl->data.prw.code = 1; |
| break; |
| |
| case EXPRESS_IOCTL_WRITE_PARAMS: |
| |
| rq = esas2r_alloc_request(a); |
| if (rq == NULL) { |
| kfree(ioctl); |
| esas2r_log(ESAS2R_LOG_WARN, |
| "could not allocate an internal request"); |
| return -ENOMEM; |
| } |
| |
| code = esas2r_write_params(a, rq, |
| (struct esas2r_sas_nvram *)ioctl->data.prw.data_buffer); |
| ioctl->data.prw.code = code; |
| |
| esas2r_free_request(a, rq); |
| |
| break; |
| |
| case EXPRESS_IOCTL_DEFAULT_PARAMS: |
| |
| esas2r_nvram_get_defaults(a, |
| (struct esas2r_sas_nvram *)ioctl->data.prw.data_buffer); |
| ioctl->data.prw.code = 1; |
| break; |
| |
| case EXPRESS_IOCTL_CHAN_INFO: |
| |
| ioctl->data.chaninfo.major_rev = ESAS2R_MAJOR_REV; |
| ioctl->data.chaninfo.minor_rev = ESAS2R_MINOR_REV; |
| ioctl->data.chaninfo.IRQ = a->pcid->irq; |
| ioctl->data.chaninfo.device_id = a->pcid->device; |
| ioctl->data.chaninfo.vendor_id = a->pcid->vendor; |
| ioctl->data.chaninfo.ven_dev_id = a->pcid->subsystem_device; |
| ioctl->data.chaninfo.revision_id = a->pcid->revision; |
| ioctl->data.chaninfo.pci_bus = a->pcid->bus->number; |
| ioctl->data.chaninfo.pci_dev_func = a->pcid->devfn; |
| ioctl->data.chaninfo.core_rev = 0; |
| ioctl->data.chaninfo.host_no = a->host->host_no; |
| ioctl->data.chaninfo.hbaapi_rev = 0; |
| break; |
| |
| case EXPRESS_IOCTL_SMP: |
| ioctl->header.return_code = handle_smp_ioctl(a, |
| &ioctl->data. |
| ioctl_smp); |
| break; |
| |
| case EXPRESS_CSMI: |
| ioctl->header.return_code = |
| handle_csmi_ioctl(a, &ioctl->data.csmi); |
| break; |
| |
| case EXPRESS_IOCTL_HBA: |
| ioctl->header.return_code = handle_hba_ioctl(a, |
| &ioctl->data. |
| ioctl_hba); |
| break; |
| |
| case EXPRESS_IOCTL_VDA: |
| err = esas2r_write_vda(a, |
| (char *)&ioctl->data.ioctl_vda, |
| 0, |
| sizeof(struct atto_ioctl_vda) + |
| ioctl->data.ioctl_vda.data_length); |
| |
| if (err >= 0) { |
| err = esas2r_read_vda(a, |
| (char *)&ioctl->data.ioctl_vda, |
| 0, |
| sizeof(struct atto_ioctl_vda) + |
| ioctl->data.ioctl_vda.data_length); |
| } |
| |
| |
| |
| |
| break; |
| |
| case EXPRESS_IOCTL_GET_MOD_INFO: |
| |
| ioctl->data.modinfo.adapter = a; |
| ioctl->data.modinfo.pci_dev = a->pcid; |
| ioctl->data.modinfo.scsi_host = a->host; |
| ioctl->data.modinfo.host_no = a->host->host_no; |
| |
| break; |
| |
| default: |
| esas2r_debug("esas2r_ioctl invalid cmd %p!", cmd); |
| ioctl->header.return_code = IOCTL_ERR_INVCMD; |
| } |
| |
| ioctl_done: |
| |
| if (err < 0) { |
| esas2r_log(ESAS2R_LOG_WARN, "err %d on ioctl cmd %u", err, |
| cmd); |
| |
| switch (err) { |
| case -ENOMEM: |
| case -EBUSY: |
| ioctl->header.return_code = IOCTL_OUT_OF_RESOURCES; |
| break; |
| |
| case -ENOSYS: |
| case -EINVAL: |
| ioctl->header.return_code = IOCTL_INVALID_PARAM; |
| break; |
| |
| default: |
| ioctl->header.return_code = IOCTL_GENERAL_ERROR; |
| break; |
| } |
| |
| } |
| |
| /* Always copy the buffer back, if only to pick up the status */ |
| err = __copy_to_user(arg, ioctl, sizeof(struct atto_express_ioctl)); |
| if (err != 0) { |
| esas2r_log(ESAS2R_LOG_WARN, |
| "ioctl_handler copy_to_user didn't copy everything (err %d, cmd %u)", |
| err, cmd); |
| kfree(ioctl); |
| |
| return -EFAULT; |
| } |
| |
| kfree(ioctl); |
| |
| return 0; |
| } |
| |
| int esas2r_ioctl(struct scsi_device *sd, unsigned int cmd, void __user *arg) |
| { |
| return esas2r_ioctl_handler(sd->host->hostdata, cmd, arg); |
| } |
| |
| static void free_fw_buffers(struct esas2r_adapter *a) |
| { |
| if (a->firmware.data) { |
| dma_free_coherent(&a->pcid->dev, |
| (size_t)a->firmware.orig_len, |
| a->firmware.data, |
| (dma_addr_t)a->firmware.phys); |
| |
| a->firmware.data = NULL; |
| } |
| } |
| |
| static int allocate_fw_buffers(struct esas2r_adapter *a, u32 length) |
| { |
| free_fw_buffers(a); |
| |
| a->firmware.orig_len = length; |
| |
| a->firmware.data = (u8 *)dma_alloc_coherent(&a->pcid->dev, |
| (size_t)length, |
| (dma_addr_t *)&a->firmware. |
| phys, |
| GFP_KERNEL); |
| |
| if (!a->firmware.data) { |
| esas2r_debug("buffer alloc failed!"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Handle a call to read firmware. */ |
| int esas2r_read_fw(struct esas2r_adapter *a, char *buf, long off, int count) |
| { |
| esas2r_trace_enter(); |
| /* if the cached header is a status, simply copy it over and return. */ |
| if (a->firmware.state == FW_STATUS_ST) { |
| int size = min_t(int, count, sizeof(a->firmware.header)); |
| esas2r_trace_exit(); |
| memcpy(buf, &a->firmware.header, size); |
| esas2r_debug("esas2r_read_fw: STATUS size %d", size); |
| return size; |
| } |
| |
| /* |
| * if the cached header is a command, do it if at |
| * offset 0, otherwise copy the pieces. |
| */ |
| |
| if (a->firmware.state == FW_COMMAND_ST) { |
| u32 length = a->firmware.header.length; |
| esas2r_trace_exit(); |
| |
| esas2r_debug("esas2r_read_fw: COMMAND length %d off %d", |
| length, |
| off); |
| |
| if (off == 0) { |
| if (a->firmware.header.action == FI_ACT_UP) { |
| if (!allocate_fw_buffers(a, length)) |
| return -ENOMEM; |
| |
| |
| /* copy header over */ |
| |
| memcpy(a->firmware.data, |
| &a->firmware.header, |
| sizeof(a->firmware.header)); |
| |
| do_fm_api(a, |
| (struct esas2r_flash_img *)a->firmware.data); |
| } else if (a->firmware.header.action == FI_ACT_UPSZ) { |
| int size = |
| min((int)count, |
| (int)sizeof(a->firmware.header)); |
| do_fm_api(a, &a->firmware.header); |
| memcpy(buf, &a->firmware.header, size); |
| esas2r_debug("FI_ACT_UPSZ size %d", size); |
| return size; |
| } else { |
| esas2r_debug("invalid action %d", |
| a->firmware.header.action); |
| return -ENOSYS; |
| } |
| } |
| |
| if (count + off > length) |
| count = length - off; |
| |
| if (count < 0) |
| return 0; |
| |
| if (!a->firmware.data) { |
| esas2r_debug( |
| "read: nonzero offset but no buffer available!"); |
| return -ENOMEM; |
| } |
| |
| esas2r_debug("esas2r_read_fw: off %d count %d length %d ", off, |
| count, |
| length); |
| |
| memcpy(buf, &a->firmware.data[off], count); |
| |
| /* when done, release the buffer */ |
| |
| if (length <= off + count) { |
| esas2r_debug("esas2r_read_fw: freeing buffer!"); |
| |
| free_fw_buffers(a); |
| } |
| |
| return count; |
| } |
| |
| esas2r_trace_exit(); |
| esas2r_debug("esas2r_read_fw: invalid firmware state %d", |
| a->firmware.state); |
| |
| return -EINVAL; |
| } |
| |
| /* Handle a call to write firmware. */ |
| int esas2r_write_fw(struct esas2r_adapter *a, const char *buf, long off, |
| int count) |
| { |
| u32 length; |
| |
| if (off == 0) { |
| struct esas2r_flash_img *header = |
| (struct esas2r_flash_img *)buf; |
| |
| /* assume version 0 flash image */ |
| |
| int min_size = sizeof(struct esas2r_flash_img_v0); |
| |
| a->firmware.state = FW_INVALID_ST; |
| |
| /* validate the version field first */ |
| |
| if (count < 4 |
| || header->fi_version > FI_VERSION_1) { |
| esas2r_debug( |
| "esas2r_write_fw: short header or invalid version"); |
| return -EINVAL; |
| } |
| |
| /* See if its a version 1 flash image */ |
| |
| if (header->fi_version == FI_VERSION_1) |
| min_size = sizeof(struct esas2r_flash_img); |
| |
| /* If this is the start, the header must be full and valid. */ |
| if (count < min_size) { |
| esas2r_debug("esas2r_write_fw: short header, aborting"); |
| return -EINVAL; |
| } |
| |
| /* Make sure the size is reasonable. */ |
| length = header->length; |
| |
| if (length > 1024 * 1024) { |
| esas2r_debug( |
| "esas2r_write_fw: hosed, length %d fi_version %d", |
| length, header->fi_version); |
| return -EINVAL; |
| } |
| |
| /* |
| * If this is a write command, allocate memory because |
| * we have to cache everything. otherwise, just cache |
| * the header, because the read op will do the command. |
| */ |
| |
| if (header->action == FI_ACT_DOWN) { |
| if (!allocate_fw_buffers(a, length)) |
| return -ENOMEM; |
| |
| /* |
| * Store the command, so there is context on subsequent |
| * calls. |
| */ |
| memcpy(&a->firmware.header, |
| buf, |
| sizeof(*header)); |
| } else if (header->action == FI_ACT_UP |
| || header->action == FI_ACT_UPSZ) { |
| /* Save the command, result will be picked up on read */ |
| memcpy(&a->firmware.header, |
| buf, |
| sizeof(*header)); |
| |
| a->firmware.state = FW_COMMAND_ST; |
| |
| esas2r_debug( |
| "esas2r_write_fw: COMMAND, count %d, action %d ", |
| count, header->action); |
| |
| /* |
| * Pretend we took the whole buffer, |
| * so we don't get bothered again. |
| */ |
| |
| return count; |
| } else { |
| esas2r_debug("esas2r_write_fw: invalid action %d ", |
| a->firmware.header.action); |
| return -ENOSYS; |
| } |
| } else { |
| length = a->firmware.header.length; |
| } |
| |
| /* |
| * We only get here on a download command, regardless of offset. |
| * the chunks written by the system need to be cached, and when |
| * the final one arrives, issue the fmapi command. |
| */ |
| |
| if (off + count > length) |
| count = length - off; |
| |
| if (count > 0) { |
| esas2r_debug("esas2r_write_fw: off %d count %d length %d", off, |
| count, |
| length); |
| |
| /* |
| * On a full upload, the system tries sending the whole buffer. |
| * there's nothing to do with it, so just drop it here, before |
| * trying to copy over into unallocated memory! |
| */ |
| if (a->firmware.header.action == FI_ACT_UP) |
| return count; |
| |
| if (!a->firmware.data) { |
| esas2r_debug( |
| "write: nonzero offset but no buffer available!"); |
| return -ENOMEM; |
| } |
| |
| memcpy(&a->firmware.data[off], buf, count); |
| |
| if (length == off + count) { |
| do_fm_api(a, |
| (struct esas2r_flash_img *)a->firmware.data); |
| |
| /* |
| * Now copy the header result to be picked up by the |
| * next read |
| */ |
| memcpy(&a->firmware.header, |
| a->firmware.data, |
| sizeof(a->firmware.header)); |
| |
| a->firmware.state = FW_STATUS_ST; |
| |
| esas2r_debug("write completed"); |
| |
| /* |
| * Since the system has the data buffered, the only way |
| * this can leak is if a root user writes a program |
| * that writes a shorter buffer than it claims, and the |
| * copyin fails. |
| */ |
| free_fw_buffers(a); |
| } |
| } |
| |
| return count; |
| } |
| |
| /* Callback for the completion of a VDA request. */ |
| static void vda_complete_req(struct esas2r_adapter *a, |
| struct esas2r_request *rq) |
| { |
| a->vda_command_done = 1; |
| wake_up_interruptible(&a->vda_waiter); |
| } |
| |
| /* Scatter/gather callback for VDA requests */ |
| static u32 get_physaddr_vda(struct esas2r_sg_context *sgc, u64 *addr) |
| { |
| struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; |
| int offset = (u8 *)sgc->cur_offset - (u8 *)a->vda_buffer; |
| |
| (*addr) = a->ppvda_buffer + offset; |
| return VDA_MAX_BUFFER_SIZE - offset; |
| } |
| |
| /* Handle a call to read a VDA command. */ |
| int esas2r_read_vda(struct esas2r_adapter *a, char *buf, long off, int count) |
| { |
| if (!a->vda_buffer) |
| return -ENOMEM; |
| |
| if (off == 0) { |
| struct esas2r_request *rq; |
| struct atto_ioctl_vda *vi = |
| (struct atto_ioctl_vda *)a->vda_buffer; |
| struct esas2r_sg_context sgc; |
| bool wait_for_completion; |
| |
| /* |
| * Presumeably, someone has already written to the vda_buffer, |
| * and now they are reading the node the response, so now we |
| * will actually issue the request to the chip and reply. |
| */ |
| |
| /* allocate a request */ |
| rq = esas2r_alloc_request(a); |
| if (rq == NULL) { |
| esas2r_debug("esas2r_read_vda: out of requests"); |
| return -EBUSY; |
| } |
| |
| rq->comp_cb = vda_complete_req; |
| |
| sgc.first_req = rq; |
| sgc.adapter = a; |
| sgc.cur_offset = a->vda_buffer + VDA_BUFFER_HEADER_SZ; |
| sgc.get_phys_addr = (PGETPHYSADDR)get_physaddr_vda; |
| |
| a->vda_command_done = 0; |
| |
| wait_for_completion = |
| esas2r_process_vda_ioctl(a, vi, rq, &sgc); |
| |
| if (wait_for_completion) { |
| /* now wait around for it to complete. */ |
| |
| while (!a->vda_command_done) |
| wait_event_interruptible(a->vda_waiter, |
| a->vda_command_done); |
| } |
| |
| esas2r_free_request(a, (struct esas2r_request *)rq); |
| } |
| |
| if (off > VDA_MAX_BUFFER_SIZE) |
| return 0; |
| |
| if (count + off > VDA_MAX_BUFFER_SIZE) |
| count = VDA_MAX_BUFFER_SIZE - off; |
| |
| if (count < 0) |
| return 0; |
| |
| memcpy(buf, a->vda_buffer + off, count); |
| |
| return count; |
| } |
| |
| /* Handle a call to write a VDA command. */ |
| int esas2r_write_vda(struct esas2r_adapter *a, const char *buf, long off, |
| int count) |
| { |
| /* |
| * allocate memory for it, if not already done. once allocated, |
| * we will keep it around until the driver is unloaded. |
| */ |
| |
| if (!a->vda_buffer) { |
| dma_addr_t dma_addr; |
| a->vda_buffer = (u8 *)dma_alloc_coherent(&a->pcid->dev, |
| (size_t) |
| VDA_MAX_BUFFER_SIZE, |
| &dma_addr, |
| GFP_KERNEL); |
| |
| a->ppvda_buffer = dma_addr; |
| } |
| |
| if (!a->vda_buffer) |
| return -ENOMEM; |
| |
| if (off > VDA_MAX_BUFFER_SIZE) |
| return 0; |
| |
| if (count + off > VDA_MAX_BUFFER_SIZE) |
| count = VDA_MAX_BUFFER_SIZE - off; |
| |
| if (count < 1) |
| return 0; |
| |
| memcpy(a->vda_buffer + off, buf, count); |
| |
| return count; |
| } |
| |
| /* Callback for the completion of an FS_API request.*/ |
| static void fs_api_complete_req(struct esas2r_adapter *a, |
| struct esas2r_request *rq) |
| { |
| a->fs_api_command_done = 1; |
| |
| wake_up_interruptible(&a->fs_api_waiter); |
| } |
| |
| /* Scatter/gather callback for VDA requests */ |
| static u32 get_physaddr_fs_api(struct esas2r_sg_context *sgc, u64 *addr) |
| { |
| struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; |
| struct esas2r_ioctl_fs *fs = |
| (struct esas2r_ioctl_fs *)a->fs_api_buffer; |
| u32 offset = (u8 *)sgc->cur_offset - (u8 *)fs; |
| |
| (*addr) = a->ppfs_api_buffer + offset; |
| |
| return a->fs_api_buffer_size - offset; |
| } |
| |
| /* Handle a call to read firmware via FS_API. */ |
| int esas2r_read_fs(struct esas2r_adapter *a, char *buf, long off, int count) |
| { |
| if (!a->fs_api_buffer) |
| return -ENOMEM; |
| |
| if (off == 0) { |
| struct esas2r_request *rq; |
| struct esas2r_sg_context sgc; |
| struct esas2r_ioctl_fs *fs = |
| (struct esas2r_ioctl_fs *)a->fs_api_buffer; |
| |
| /* If another flash request is already in progress, return. */ |
| if (mutex_lock_interruptible(&a->fs_api_mutex)) { |
| busy: |
| fs->status = ATTO_STS_OUT_OF_RSRC; |
| return -EBUSY; |
| } |
| |
| /* |
| * Presumeably, someone has already written to the |
| * fs_api_buffer, and now they are reading the node the |
| * response, so now we will actually issue the request to the |
| * chip and reply. Allocate a request |
| */ |
| |
| rq = esas2r_alloc_request(a); |
| if (rq == NULL) { |
| esas2r_debug("esas2r_read_fs: out of requests"); |
| mutex_unlock(&a->fs_api_mutex); |
| goto busy; |
| } |
| |
| rq->comp_cb = fs_api_complete_req; |
| |
| /* Set up the SGCONTEXT for to build the s/g table */ |
| |
| sgc.cur_offset = fs->data; |
| sgc.get_phys_addr = (PGETPHYSADDR)get_physaddr_fs_api; |
| |
| a->fs_api_command_done = 0; |
| |
| if (!esas2r_process_fs_ioctl(a, fs, rq, &sgc)) { |
| if (fs->status == ATTO_STS_OUT_OF_RSRC) |
| count = -EBUSY; |
| |
| goto dont_wait; |
| } |
| |
| /* Now wait around for it to complete. */ |
| |
| while (!a->fs_api_command_done) |
| wait_event_interruptible(a->fs_api_waiter, |
| a->fs_api_command_done); |
| ; |
| dont_wait: |
| /* Free the request and keep going */ |
| mutex_unlock(&a->fs_api_mutex); |
| esas2r_free_request(a, (struct esas2r_request *)rq); |
| |
| /* Pick up possible error code from above */ |
| if (count < 0) |
| return count; |
| } |
| |
| if (off > a->fs_api_buffer_size) |
| return 0; |
| |
| if (count + off > a->fs_api_buffer_size) |
| count = a->fs_api_buffer_size - off; |
| |
| if (count < 0) |
| return 0; |
| |
| memcpy(buf, a->fs_api_buffer + off, count); |
| |
| return count; |
| } |
| |
| /* Handle a call to write firmware via FS_API. */ |
| int esas2r_write_fs(struct esas2r_adapter *a, const char *buf, long off, |
| int count) |
| { |
| if (off == 0) { |
| struct esas2r_ioctl_fs *fs = (struct esas2r_ioctl_fs *)buf; |
| u32 length = fs->command.length + offsetof( |
| struct esas2r_ioctl_fs, |
| data); |
| |
| /* |
| * Special case, for BEGIN commands, the length field |
| * is lying to us, so just get enough for the header. |
| */ |
| |
| if (fs->command.command == ESAS2R_FS_CMD_BEGINW) |
| length = offsetof(struct esas2r_ioctl_fs, data); |
| |
| /* |
| * Beginning a command. We assume we'll get at least |
| * enough in the first write so we can look at the |
| * header and see how much we need to alloc. |
| */ |
| |
| if (count < offsetof(struct esas2r_ioctl_fs, data)) |
| return -EINVAL; |
| |
| /* Allocate a buffer or use the existing buffer. */ |
| if (a->fs_api_buffer) { |
| if (a->fs_api_buffer_size < length) { |
| /* Free too-small buffer and get a new one */ |
| dma_free_coherent(&a->pcid->dev, |
| (size_t)a->fs_api_buffer_size, |
| a->fs_api_buffer, |
| (dma_addr_t)a->ppfs_api_buffer); |
| |
| goto re_allocate_buffer; |
| } |
| } else { |
| re_allocate_buffer: |
| a->fs_api_buffer_size = length; |
| |
| a->fs_api_buffer = (u8 *)dma_alloc_coherent( |
| &a->pcid->dev, |
| (size_t)a->fs_api_buffer_size, |
| (dma_addr_t *)&a->ppfs_api_buffer, |
| GFP_KERNEL); |
| } |
| } |
| |
| if (!a->fs_api_buffer) |
| return -ENOMEM; |
| |
| if (off > a->fs_api_buffer_size) |
| return 0; |
| |
| if (count + off > a->fs_api_buffer_size) |
| count = a->fs_api_buffer_size - off; |
| |
| if (count < 1) |
| return 0; |
| |
| memcpy(a->fs_api_buffer + off, buf, count); |
| |
| return count; |
| } |