| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * QLogic Fibre Channel HBA Driver |
| * Copyright (c) 2003-2014 QLogic Corporation |
| */ |
| #include "qla_def.h" |
| #include "qla_gbl.h" |
| |
| #include <linux/kthread.h> |
| #include <linux/vmalloc.h> |
| #include <linux/delay.h> |
| #include <linux/bsg-lib.h> |
| |
| static void qla2xxx_free_fcport_work(struct work_struct *work) |
| { |
| struct fc_port *fcport = container_of(work, typeof(*fcport), |
| free_work); |
| |
| qla2x00_free_fcport(fcport); |
| } |
| |
| /* BSG support for ELS/CT pass through */ |
| void qla2x00_bsg_job_done(srb_t *sp, int res) |
| { |
| struct bsg_job *bsg_job = sp->u.bsg_job; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| |
| ql_dbg(ql_dbg_user, sp->vha, 0x7009, |
| "%s: sp hdl %x, result=%x bsg ptr %p\n", |
| __func__, sp->handle, res, bsg_job); |
| |
| /* ref: INIT */ |
| kref_put(&sp->cmd_kref, qla2x00_sp_release); |
| |
| bsg_reply->result = res; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| } |
| |
| void qla2x00_bsg_sp_free(srb_t *sp) |
| { |
| struct qla_hw_data *ha = sp->vha->hw; |
| struct bsg_job *bsg_job = sp->u.bsg_job; |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct qla_mt_iocb_rqst_fx00 *piocb_rqst; |
| |
| if (sp->type == SRB_FXIOCB_BCMD) { |
| piocb_rqst = (struct qla_mt_iocb_rqst_fx00 *) |
| &bsg_request->rqst_data.h_vendor.vendor_cmd[1]; |
| |
| if (piocb_rqst->flags & SRB_FXDISC_REQ_DMA_VALID) |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| |
| if (piocb_rqst->flags & SRB_FXDISC_RESP_DMA_VALID) |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| } else { |
| |
| if (sp->remap.remapped) { |
| dma_pool_free(ha->purex_dma_pool, sp->remap.rsp.buf, |
| sp->remap.rsp.dma); |
| dma_pool_free(ha->purex_dma_pool, sp->remap.req.buf, |
| sp->remap.req.dma); |
| } else { |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| } |
| } |
| |
| if (sp->type == SRB_CT_CMD || |
| sp->type == SRB_FXIOCB_BCMD || |
| sp->type == SRB_ELS_CMD_HST) { |
| INIT_WORK(&sp->fcport->free_work, qla2xxx_free_fcport_work); |
| queue_work(ha->wq, &sp->fcport->free_work); |
| } |
| |
| qla2x00_rel_sp(sp); |
| } |
| |
| int |
| qla24xx_fcp_prio_cfg_valid(scsi_qla_host_t *vha, |
| struct qla_fcp_prio_cfg *pri_cfg, uint8_t flag) |
| { |
| int i, ret, num_valid; |
| uint8_t *bcode; |
| struct qla_fcp_prio_entry *pri_entry; |
| uint32_t *bcode_val_ptr, bcode_val; |
| |
| ret = 1; |
| num_valid = 0; |
| bcode = (uint8_t *)pri_cfg; |
| bcode_val_ptr = (uint32_t *)pri_cfg; |
| bcode_val = (uint32_t)(*bcode_val_ptr); |
| |
| if (bcode_val == 0xFFFFFFFF) { |
| /* No FCP Priority config data in flash */ |
| ql_dbg(ql_dbg_user, vha, 0x7051, |
| "No FCP Priority config data.\n"); |
| return 0; |
| } |
| |
| if (memcmp(bcode, "HQOS", 4)) { |
| /* Invalid FCP priority data header*/ |
| ql_dbg(ql_dbg_user, vha, 0x7052, |
| "Invalid FCP Priority data header. bcode=0x%x.\n", |
| bcode_val); |
| return 0; |
| } |
| if (flag != 1) |
| return ret; |
| |
| pri_entry = &pri_cfg->entry[0]; |
| for (i = 0; i < pri_cfg->num_entries; i++) { |
| if (pri_entry->flags & FCP_PRIO_ENTRY_TAG_VALID) |
| num_valid++; |
| pri_entry++; |
| } |
| |
| if (num_valid == 0) { |
| /* No valid FCP priority data entries */ |
| ql_dbg(ql_dbg_user, vha, 0x7053, |
| "No valid FCP Priority data entries.\n"); |
| ret = 0; |
| } else { |
| /* FCP priority data is valid */ |
| ql_dbg(ql_dbg_user, vha, 0x7054, |
| "Valid FCP priority data. num entries = %d.\n", |
| num_valid); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| qla24xx_proc_fcp_prio_cfg_cmd(struct bsg_job *bsg_job) |
| { |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int ret = 0; |
| uint32_t len; |
| uint32_t oper; |
| |
| if (!(IS_QLA24XX_TYPE(ha) || IS_QLA25XX(ha) || IS_P3P_TYPE(ha))) { |
| ret = -EINVAL; |
| goto exit_fcp_prio_cfg; |
| } |
| |
| /* Get the sub command */ |
| oper = bsg_request->rqst_data.h_vendor.vendor_cmd[1]; |
| |
| /* Only set config is allowed if config memory is not allocated */ |
| if (!ha->fcp_prio_cfg && (oper != QLFC_FCP_PRIO_SET_CONFIG)) { |
| ret = -EINVAL; |
| goto exit_fcp_prio_cfg; |
| } |
| switch (oper) { |
| case QLFC_FCP_PRIO_DISABLE: |
| if (ha->flags.fcp_prio_enabled) { |
| ha->flags.fcp_prio_enabled = 0; |
| ha->fcp_prio_cfg->attributes &= |
| ~FCP_PRIO_ATTR_ENABLE; |
| qla24xx_update_all_fcp_prio(vha); |
| bsg_reply->result = DID_OK; |
| } else { |
| ret = -EINVAL; |
| bsg_reply->result = (DID_ERROR << 16); |
| goto exit_fcp_prio_cfg; |
| } |
| break; |
| |
| case QLFC_FCP_PRIO_ENABLE: |
| if (!ha->flags.fcp_prio_enabled) { |
| if (ha->fcp_prio_cfg) { |
| ha->flags.fcp_prio_enabled = 1; |
| ha->fcp_prio_cfg->attributes |= |
| FCP_PRIO_ATTR_ENABLE; |
| qla24xx_update_all_fcp_prio(vha); |
| bsg_reply->result = DID_OK; |
| } else { |
| ret = -EINVAL; |
| bsg_reply->result = (DID_ERROR << 16); |
| goto exit_fcp_prio_cfg; |
| } |
| } |
| break; |
| |
| case QLFC_FCP_PRIO_GET_CONFIG: |
| len = bsg_job->reply_payload.payload_len; |
| if (!len || len > FCP_PRIO_CFG_SIZE) { |
| ret = -EINVAL; |
| bsg_reply->result = (DID_ERROR << 16); |
| goto exit_fcp_prio_cfg; |
| } |
| |
| bsg_reply->result = DID_OK; |
| bsg_reply->reply_payload_rcv_len = |
| sg_copy_from_buffer( |
| bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, ha->fcp_prio_cfg, |
| len); |
| |
| break; |
| |
| case QLFC_FCP_PRIO_SET_CONFIG: |
| len = bsg_job->request_payload.payload_len; |
| if (!len || len > FCP_PRIO_CFG_SIZE) { |
| bsg_reply->result = (DID_ERROR << 16); |
| ret = -EINVAL; |
| goto exit_fcp_prio_cfg; |
| } |
| |
| if (!ha->fcp_prio_cfg) { |
| ha->fcp_prio_cfg = vmalloc(FCP_PRIO_CFG_SIZE); |
| if (!ha->fcp_prio_cfg) { |
| ql_log(ql_log_warn, vha, 0x7050, |
| "Unable to allocate memory for fcp prio " |
| "config data (%x).\n", FCP_PRIO_CFG_SIZE); |
| bsg_reply->result = (DID_ERROR << 16); |
| ret = -ENOMEM; |
| goto exit_fcp_prio_cfg; |
| } |
| } |
| |
| memset(ha->fcp_prio_cfg, 0, FCP_PRIO_CFG_SIZE); |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, ha->fcp_prio_cfg, |
| FCP_PRIO_CFG_SIZE); |
| |
| /* validate fcp priority data */ |
| |
| if (!qla24xx_fcp_prio_cfg_valid(vha, ha->fcp_prio_cfg, 1)) { |
| bsg_reply->result = (DID_ERROR << 16); |
| ret = -EINVAL; |
| /* If buffer was invalidatic int |
| * fcp_prio_cfg is of no use |
| */ |
| vfree(ha->fcp_prio_cfg); |
| ha->fcp_prio_cfg = NULL; |
| goto exit_fcp_prio_cfg; |
| } |
| |
| ha->flags.fcp_prio_enabled = 0; |
| if (ha->fcp_prio_cfg->attributes & FCP_PRIO_ATTR_ENABLE) |
| ha->flags.fcp_prio_enabled = 1; |
| qla24xx_update_all_fcp_prio(vha); |
| bsg_reply->result = DID_OK; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| exit_fcp_prio_cfg: |
| if (!ret) |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return ret; |
| } |
| |
| static int |
| qla2x00_process_els(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_rport *rport; |
| fc_port_t *fcport = NULL; |
| struct Scsi_Host *host; |
| scsi_qla_host_t *vha; |
| struct qla_hw_data *ha; |
| srb_t *sp; |
| const char *type; |
| int req_sg_cnt, rsp_sg_cnt; |
| int rval = (DID_ERROR << 16); |
| uint32_t els_cmd = 0; |
| int qla_port_allocated = 0; |
| |
| if (bsg_request->msgcode == FC_BSG_RPT_ELS) { |
| rport = fc_bsg_to_rport(bsg_job); |
| if (!rport) { |
| rval = -ENOMEM; |
| goto done; |
| } |
| fcport = *(fc_port_t **) rport->dd_data; |
| host = rport_to_shost(rport); |
| vha = shost_priv(host); |
| ha = vha->hw; |
| type = "FC_BSG_RPT_ELS"; |
| } else { |
| host = fc_bsg_to_shost(bsg_job); |
| vha = shost_priv(host); |
| ha = vha->hw; |
| type = "FC_BSG_HST_ELS_NOLOGIN"; |
| els_cmd = bsg_request->rqst_data.h_els.command_code; |
| if (els_cmd == ELS_AUTH_ELS) |
| return qla_edif_process_els(vha, bsg_job); |
| } |
| |
| if (!vha->flags.online) { |
| ql_log(ql_log_warn, vha, 0x7005, "Host not online.\n"); |
| rval = -EIO; |
| goto done; |
| } |
| |
| /* pass through is supported only for ISP 4Gb or higher */ |
| if (!IS_FWI2_CAPABLE(ha)) { |
| ql_dbg(ql_dbg_user, vha, 0x7001, |
| "ELS passthru not supported for ISP23xx based adapters.\n"); |
| rval = -EPERM; |
| goto done; |
| } |
| |
| /* Multiple SG's are not supported for ELS requests */ |
| if (bsg_job->request_payload.sg_cnt > 1 || |
| bsg_job->reply_payload.sg_cnt > 1) { |
| ql_dbg(ql_dbg_user, vha, 0x7002, |
| "Multiple SG's are not supported for ELS requests, " |
| "request_sg_cnt=%x reply_sg_cnt=%x.\n", |
| bsg_job->request_payload.sg_cnt, |
| bsg_job->reply_payload.sg_cnt); |
| rval = -EPERM; |
| goto done; |
| } |
| |
| /* ELS request for rport */ |
| if (bsg_request->msgcode == FC_BSG_RPT_ELS) { |
| /* make sure the rport is logged in, |
| * if not perform fabric login |
| */ |
| if (atomic_read(&fcport->state) != FCS_ONLINE) { |
| ql_dbg(ql_dbg_user, vha, 0x7003, |
| "Port %06X is not online for ELS passthru.\n", |
| fcport->d_id.b24); |
| rval = -EIO; |
| goto done; |
| } |
| } else { |
| /* Allocate a dummy fcport structure, since functions |
| * preparing the IOCB and mailbox command retrieves port |
| * specific information from fcport structure. For Host based |
| * ELS commands there will be no fcport structure allocated |
| */ |
| fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); |
| if (!fcport) { |
| rval = -ENOMEM; |
| goto done; |
| } |
| |
| qla_port_allocated = 1; |
| /* Initialize all required fields of fcport */ |
| fcport->vha = vha; |
| fcport->d_id.b.al_pa = |
| bsg_request->rqst_data.h_els.port_id[0]; |
| fcport->d_id.b.area = |
| bsg_request->rqst_data.h_els.port_id[1]; |
| fcport->d_id.b.domain = |
| bsg_request->rqst_data.h_els.port_id[2]; |
| fcport->loop_id = |
| (fcport->d_id.b.al_pa == 0xFD) ? |
| NPH_FABRIC_CONTROLLER : NPH_F_PORT; |
| } |
| |
| req_sg_cnt = |
| dma_map_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| if (!req_sg_cnt) { |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| rval = -ENOMEM; |
| goto done_free_fcport; |
| } |
| |
| rsp_sg_cnt = dma_map_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| if (!rsp_sg_cnt) { |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| rval = -ENOMEM; |
| goto done_free_fcport; |
| } |
| |
| if ((req_sg_cnt != bsg_job->request_payload.sg_cnt) || |
| (rsp_sg_cnt != bsg_job->reply_payload.sg_cnt)) { |
| ql_log(ql_log_warn, vha, 0x7008, |
| "dma mapping resulted in different sg counts, " |
| "request_sg_cnt: %x dma_request_sg_cnt:%x reply_sg_cnt:%x " |
| "dma_reply_sg_cnt:%x.\n", bsg_job->request_payload.sg_cnt, |
| req_sg_cnt, bsg_job->reply_payload.sg_cnt, rsp_sg_cnt); |
| rval = -EAGAIN; |
| goto done_unmap_sg; |
| } |
| |
| /* Alloc SRB structure */ |
| sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); |
| if (!sp) { |
| rval = -ENOMEM; |
| goto done_unmap_sg; |
| } |
| |
| sp->type = |
| (bsg_request->msgcode == FC_BSG_RPT_ELS ? |
| SRB_ELS_CMD_RPT : SRB_ELS_CMD_HST); |
| sp->name = |
| (bsg_request->msgcode == FC_BSG_RPT_ELS ? |
| "bsg_els_rpt" : "bsg_els_hst"); |
| sp->u.bsg_job = bsg_job; |
| sp->free = qla2x00_bsg_sp_free; |
| sp->done = qla2x00_bsg_job_done; |
| |
| ql_dbg(ql_dbg_user, vha, 0x700a, |
| "bsg rqst type: %s els type: %x - loop-id=%x " |
| "portid=%-2x%02x%02x.\n", type, |
| bsg_request->rqst_data.h_els.command_code, fcport->loop_id, |
| fcport->d_id.b.domain, fcport->d_id.b.area, fcport->d_id.b.al_pa); |
| |
| rval = qla2x00_start_sp(sp); |
| if (rval != QLA_SUCCESS) { |
| ql_log(ql_log_warn, vha, 0x700e, |
| "qla2x00_start_sp failed = %d\n", rval); |
| qla2x00_rel_sp(sp); |
| rval = -EIO; |
| goto done_unmap_sg; |
| } |
| return rval; |
| |
| done_unmap_sg: |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| goto done_free_fcport; |
| |
| done_free_fcport: |
| if (qla_port_allocated) |
| qla2x00_free_fcport(fcport); |
| done: |
| return rval; |
| } |
| |
| static inline uint16_t |
| qla24xx_calc_ct_iocbs(uint16_t dsds) |
| { |
| uint16_t iocbs; |
| |
| iocbs = 1; |
| if (dsds > 2) { |
| iocbs += (dsds - 2) / 5; |
| if ((dsds - 2) % 5) |
| iocbs++; |
| } |
| return iocbs; |
| } |
| |
| static int |
| qla2x00_process_ct(struct bsg_job *bsg_job) |
| { |
| srb_t *sp; |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = (DID_ERROR << 16); |
| int req_sg_cnt, rsp_sg_cnt; |
| uint16_t loop_id; |
| struct fc_port *fcport; |
| char *type = "FC_BSG_HST_CT"; |
| |
| req_sg_cnt = |
| dma_map_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| if (!req_sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x700f, |
| "dma_map_sg return %d for request\n", req_sg_cnt); |
| rval = -ENOMEM; |
| goto done; |
| } |
| |
| rsp_sg_cnt = dma_map_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| if (!rsp_sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x7010, |
| "dma_map_sg return %d for reply\n", rsp_sg_cnt); |
| rval = -ENOMEM; |
| goto done; |
| } |
| |
| if ((req_sg_cnt != bsg_job->request_payload.sg_cnt) || |
| (rsp_sg_cnt != bsg_job->reply_payload.sg_cnt)) { |
| ql_log(ql_log_warn, vha, 0x7011, |
| "request_sg_cnt: %x dma_request_sg_cnt: %x reply_sg_cnt:%x " |
| "dma_reply_sg_cnt: %x\n", bsg_job->request_payload.sg_cnt, |
| req_sg_cnt, bsg_job->reply_payload.sg_cnt, rsp_sg_cnt); |
| rval = -EAGAIN; |
| goto done_unmap_sg; |
| } |
| |
| if (!vha->flags.online) { |
| ql_log(ql_log_warn, vha, 0x7012, |
| "Host is not online.\n"); |
| rval = -EIO; |
| goto done_unmap_sg; |
| } |
| |
| loop_id = |
| (bsg_request->rqst_data.h_ct.preamble_word1 & 0xFF000000) |
| >> 24; |
| switch (loop_id) { |
| case 0xFC: |
| loop_id = NPH_SNS; |
| break; |
| case 0xFA: |
| loop_id = vha->mgmt_svr_loop_id; |
| break; |
| default: |
| ql_dbg(ql_dbg_user, vha, 0x7013, |
| "Unknown loop id: %x.\n", loop_id); |
| rval = -EINVAL; |
| goto done_unmap_sg; |
| } |
| |
| /* Allocate a dummy fcport structure, since functions preparing the |
| * IOCB and mailbox command retrieves port specific information |
| * from fcport structure. For Host based ELS commands there will be |
| * no fcport structure allocated |
| */ |
| fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); |
| if (!fcport) { |
| ql_log(ql_log_warn, vha, 0x7014, |
| "Failed to allocate fcport.\n"); |
| rval = -ENOMEM; |
| goto done_unmap_sg; |
| } |
| |
| /* Initialize all required fields of fcport */ |
| fcport->vha = vha; |
| fcport->d_id.b.al_pa = bsg_request->rqst_data.h_ct.port_id[0]; |
| fcport->d_id.b.area = bsg_request->rqst_data.h_ct.port_id[1]; |
| fcport->d_id.b.domain = bsg_request->rqst_data.h_ct.port_id[2]; |
| fcport->loop_id = loop_id; |
| |
| /* Alloc SRB structure */ |
| sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); |
| if (!sp) { |
| ql_log(ql_log_warn, vha, 0x7015, |
| "qla2x00_get_sp failed.\n"); |
| rval = -ENOMEM; |
| goto done_free_fcport; |
| } |
| |
| sp->type = SRB_CT_CMD; |
| sp->name = "bsg_ct"; |
| sp->iocbs = qla24xx_calc_ct_iocbs(req_sg_cnt + rsp_sg_cnt); |
| sp->u.bsg_job = bsg_job; |
| sp->free = qla2x00_bsg_sp_free; |
| sp->done = qla2x00_bsg_job_done; |
| |
| ql_dbg(ql_dbg_user, vha, 0x7016, |
| "bsg rqst type: %s else type: %x - " |
| "loop-id=%x portid=%02x%02x%02x.\n", type, |
| (bsg_request->rqst_data.h_ct.preamble_word2 >> 16), |
| fcport->loop_id, fcport->d_id.b.domain, fcport->d_id.b.area, |
| fcport->d_id.b.al_pa); |
| |
| rval = qla2x00_start_sp(sp); |
| if (rval != QLA_SUCCESS) { |
| ql_log(ql_log_warn, vha, 0x7017, |
| "qla2x00_start_sp failed=%d.\n", rval); |
| qla2x00_rel_sp(sp); |
| rval = -EIO; |
| goto done_free_fcport; |
| } |
| return rval; |
| |
| done_free_fcport: |
| qla2x00_free_fcport(fcport); |
| done_unmap_sg: |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| done: |
| return rval; |
| } |
| |
| /* Disable loopback mode */ |
| static inline int |
| qla81xx_reset_loopback_mode(scsi_qla_host_t *vha, uint16_t *config, |
| int wait, int wait2) |
| { |
| int ret = 0; |
| int rval = 0; |
| uint16_t new_config[4]; |
| struct qla_hw_data *ha = vha->hw; |
| |
| if (!IS_QLA81XX(ha) && !IS_QLA8031(ha) && !IS_QLA8044(ha)) |
| goto done_reset_internal; |
| |
| memset(new_config, 0 , sizeof(new_config)); |
| if ((config[0] & INTERNAL_LOOPBACK_MASK) >> 1 == |
| ENABLE_INTERNAL_LOOPBACK || |
| (config[0] & INTERNAL_LOOPBACK_MASK) >> 1 == |
| ENABLE_EXTERNAL_LOOPBACK) { |
| new_config[0] = config[0] & ~INTERNAL_LOOPBACK_MASK; |
| ql_dbg(ql_dbg_user, vha, 0x70bf, "new_config[0]=%02x\n", |
| (new_config[0] & INTERNAL_LOOPBACK_MASK)); |
| memcpy(&new_config[1], &config[1], sizeof(uint16_t) * 3) ; |
| |
| ha->notify_dcbx_comp = wait; |
| ha->notify_lb_portup_comp = wait2; |
| |
| ret = qla81xx_set_port_config(vha, new_config); |
| if (ret != QLA_SUCCESS) { |
| ql_log(ql_log_warn, vha, 0x7025, |
| "Set port config failed.\n"); |
| ha->notify_dcbx_comp = 0; |
| ha->notify_lb_portup_comp = 0; |
| rval = -EINVAL; |
| goto done_reset_internal; |
| } |
| |
| /* Wait for DCBX complete event */ |
| if (wait && !wait_for_completion_timeout(&ha->dcbx_comp, |
| (DCBX_COMP_TIMEOUT * HZ))) { |
| ql_dbg(ql_dbg_user, vha, 0x7026, |
| "DCBX completion not received.\n"); |
| ha->notify_dcbx_comp = 0; |
| ha->notify_lb_portup_comp = 0; |
| rval = -EINVAL; |
| goto done_reset_internal; |
| } else |
| ql_dbg(ql_dbg_user, vha, 0x7027, |
| "DCBX completion received.\n"); |
| |
| if (wait2 && |
| !wait_for_completion_timeout(&ha->lb_portup_comp, |
| (LB_PORTUP_COMP_TIMEOUT * HZ))) { |
| ql_dbg(ql_dbg_user, vha, 0x70c5, |
| "Port up completion not received.\n"); |
| ha->notify_lb_portup_comp = 0; |
| rval = -EINVAL; |
| goto done_reset_internal; |
| } else |
| ql_dbg(ql_dbg_user, vha, 0x70c6, |
| "Port up completion received.\n"); |
| |
| ha->notify_dcbx_comp = 0; |
| ha->notify_lb_portup_comp = 0; |
| } |
| done_reset_internal: |
| return rval; |
| } |
| |
| /* |
| * Set the port configuration to enable the internal or external loopback |
| * depending on the loopback mode. |
| */ |
| static inline int |
| qla81xx_set_loopback_mode(scsi_qla_host_t *vha, uint16_t *config, |
| uint16_t *new_config, uint16_t mode) |
| { |
| int ret = 0; |
| int rval = 0; |
| unsigned long rem_tmo = 0, current_tmo = 0; |
| struct qla_hw_data *ha = vha->hw; |
| |
| if (!IS_QLA81XX(ha) && !IS_QLA8031(ha) && !IS_QLA8044(ha)) |
| goto done_set_internal; |
| |
| if (mode == INTERNAL_LOOPBACK) |
| new_config[0] = config[0] | (ENABLE_INTERNAL_LOOPBACK << 1); |
| else if (mode == EXTERNAL_LOOPBACK) |
| new_config[0] = config[0] | (ENABLE_EXTERNAL_LOOPBACK << 1); |
| ql_dbg(ql_dbg_user, vha, 0x70be, |
| "new_config[0]=%02x\n", (new_config[0] & INTERNAL_LOOPBACK_MASK)); |
| |
| memcpy(&new_config[1], &config[1], sizeof(uint16_t) * 3); |
| |
| ha->notify_dcbx_comp = 1; |
| ret = qla81xx_set_port_config(vha, new_config); |
| if (ret != QLA_SUCCESS) { |
| ql_log(ql_log_warn, vha, 0x7021, |
| "set port config failed.\n"); |
| ha->notify_dcbx_comp = 0; |
| rval = -EINVAL; |
| goto done_set_internal; |
| } |
| |
| /* Wait for DCBX complete event */ |
| current_tmo = DCBX_COMP_TIMEOUT * HZ; |
| while (1) { |
| rem_tmo = wait_for_completion_timeout(&ha->dcbx_comp, |
| current_tmo); |
| if (!ha->idc_extend_tmo || rem_tmo) { |
| ha->idc_extend_tmo = 0; |
| break; |
| } |
| current_tmo = ha->idc_extend_tmo * HZ; |
| ha->idc_extend_tmo = 0; |
| } |
| |
| if (!rem_tmo) { |
| ql_dbg(ql_dbg_user, vha, 0x7022, |
| "DCBX completion not received.\n"); |
| ret = qla81xx_reset_loopback_mode(vha, new_config, 0, 0); |
| /* |
| * If the reset of the loopback mode doesn't work take a FCoE |
| * dump and reset the chip. |
| */ |
| if (ret) { |
| qla2xxx_dump_fw(vha); |
| set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); |
| } |
| rval = -EINVAL; |
| } else { |
| if (ha->flags.idc_compl_status) { |
| ql_dbg(ql_dbg_user, vha, 0x70c3, |
| "Bad status in IDC Completion AEN\n"); |
| rval = -EINVAL; |
| ha->flags.idc_compl_status = 0; |
| } else |
| ql_dbg(ql_dbg_user, vha, 0x7023, |
| "DCBX completion received.\n"); |
| } |
| |
| ha->notify_dcbx_comp = 0; |
| ha->idc_extend_tmo = 0; |
| |
| done_set_internal: |
| return rval; |
| } |
| |
| static int |
| qla2x00_process_loopback(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval; |
| uint8_t command_sent; |
| char *type; |
| struct msg_echo_lb elreq; |
| uint16_t response[MAILBOX_REGISTER_COUNT]; |
| uint16_t config[4], new_config[4]; |
| uint8_t *fw_sts_ptr; |
| void *req_data = NULL; |
| dma_addr_t req_data_dma; |
| uint32_t req_data_len; |
| uint8_t *rsp_data = NULL; |
| dma_addr_t rsp_data_dma; |
| uint32_t rsp_data_len; |
| |
| if (!vha->flags.online) { |
| ql_log(ql_log_warn, vha, 0x7019, "Host is not online.\n"); |
| return -EIO; |
| } |
| |
| memset(&elreq, 0, sizeof(elreq)); |
| |
| elreq.req_sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, bsg_job->request_payload.sg_cnt, |
| DMA_TO_DEVICE); |
| |
| if (!elreq.req_sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x701a, |
| "dma_map_sg returned %d for request.\n", elreq.req_sg_cnt); |
| return -ENOMEM; |
| } |
| |
| elreq.rsp_sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, bsg_job->reply_payload.sg_cnt, |
| DMA_FROM_DEVICE); |
| |
| if (!elreq.rsp_sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x701b, |
| "dma_map_sg returned %d for reply.\n", elreq.rsp_sg_cnt); |
| rval = -ENOMEM; |
| goto done_unmap_req_sg; |
| } |
| |
| if ((elreq.req_sg_cnt != bsg_job->request_payload.sg_cnt) || |
| (elreq.rsp_sg_cnt != bsg_job->reply_payload.sg_cnt)) { |
| ql_log(ql_log_warn, vha, 0x701c, |
| "dma mapping resulted in different sg counts, " |
| "request_sg_cnt: %x dma_request_sg_cnt: %x " |
| "reply_sg_cnt: %x dma_reply_sg_cnt: %x.\n", |
| bsg_job->request_payload.sg_cnt, elreq.req_sg_cnt, |
| bsg_job->reply_payload.sg_cnt, elreq.rsp_sg_cnt); |
| rval = -EAGAIN; |
| goto done_unmap_sg; |
| } |
| req_data_len = rsp_data_len = bsg_job->request_payload.payload_len; |
| req_data = dma_alloc_coherent(&ha->pdev->dev, req_data_len, |
| &req_data_dma, GFP_KERNEL); |
| if (!req_data) { |
| ql_log(ql_log_warn, vha, 0x701d, |
| "dma alloc failed for req_data.\n"); |
| rval = -ENOMEM; |
| goto done_unmap_sg; |
| } |
| |
| rsp_data = dma_alloc_coherent(&ha->pdev->dev, rsp_data_len, |
| &rsp_data_dma, GFP_KERNEL); |
| if (!rsp_data) { |
| ql_log(ql_log_warn, vha, 0x7004, |
| "dma alloc failed for rsp_data.\n"); |
| rval = -ENOMEM; |
| goto done_free_dma_req; |
| } |
| |
| /* Copy the request buffer in req_data now */ |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, req_data, req_data_len); |
| |
| elreq.send_dma = req_data_dma; |
| elreq.rcv_dma = rsp_data_dma; |
| elreq.transfer_size = req_data_len; |
| |
| elreq.options = bsg_request->rqst_data.h_vendor.vendor_cmd[1]; |
| elreq.iteration_count = |
| bsg_request->rqst_data.h_vendor.vendor_cmd[2]; |
| |
| if (atomic_read(&vha->loop_state) == LOOP_READY && |
| ((ha->current_topology == ISP_CFG_F && (elreq.options & 7) >= 2) || |
| ((IS_QLA81XX(ha) || IS_QLA8031(ha) || IS_QLA8044(ha)) && |
| get_unaligned_le32(req_data) == ELS_OPCODE_BYTE && |
| req_data_len == MAX_ELS_FRAME_PAYLOAD && |
| elreq.options == EXTERNAL_LOOPBACK))) { |
| type = "FC_BSG_HST_VENDOR_ECHO_DIAG"; |
| ql_dbg(ql_dbg_user, vha, 0x701e, |
| "BSG request type: %s.\n", type); |
| command_sent = INT_DEF_LB_ECHO_CMD; |
| rval = qla2x00_echo_test(vha, &elreq, response); |
| } else { |
| if (IS_QLA81XX(ha) || IS_QLA8031(ha) || IS_QLA8044(ha)) { |
| memset(config, 0, sizeof(config)); |
| memset(new_config, 0, sizeof(new_config)); |
| |
| if (qla81xx_get_port_config(vha, config)) { |
| ql_log(ql_log_warn, vha, 0x701f, |
| "Get port config failed.\n"); |
| rval = -EPERM; |
| goto done_free_dma_rsp; |
| } |
| |
| if ((config[0] & INTERNAL_LOOPBACK_MASK) != 0) { |
| ql_dbg(ql_dbg_user, vha, 0x70c4, |
| "Loopback operation already in " |
| "progress.\n"); |
| rval = -EAGAIN; |
| goto done_free_dma_rsp; |
| } |
| |
| ql_dbg(ql_dbg_user, vha, 0x70c0, |
| "elreq.options=%04x\n", elreq.options); |
| |
| if (elreq.options == EXTERNAL_LOOPBACK) |
| if (IS_QLA8031(ha) || IS_QLA8044(ha)) |
| rval = qla81xx_set_loopback_mode(vha, |
| config, new_config, elreq.options); |
| else |
| rval = qla81xx_reset_loopback_mode(vha, |
| config, 1, 0); |
| else |
| rval = qla81xx_set_loopback_mode(vha, config, |
| new_config, elreq.options); |
| |
| if (rval) { |
| rval = -EPERM; |
| goto done_free_dma_rsp; |
| } |
| |
| type = "FC_BSG_HST_VENDOR_LOOPBACK"; |
| ql_dbg(ql_dbg_user, vha, 0x7028, |
| "BSG request type: %s.\n", type); |
| |
| command_sent = INT_DEF_LB_LOOPBACK_CMD; |
| rval = qla2x00_loopback_test(vha, &elreq, response); |
| |
| if (response[0] == MBS_COMMAND_ERROR && |
| response[1] == MBS_LB_RESET) { |
| ql_log(ql_log_warn, vha, 0x7029, |
| "MBX command error, Aborting ISP.\n"); |
| set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); |
| qla2xxx_wake_dpc(vha); |
| qla2x00_wait_for_chip_reset(vha); |
| /* Also reset the MPI */ |
| if (IS_QLA81XX(ha)) { |
| if (qla81xx_restart_mpi_firmware(vha) != |
| QLA_SUCCESS) { |
| ql_log(ql_log_warn, vha, 0x702a, |
| "MPI reset failed.\n"); |
| } |
| } |
| |
| rval = -EIO; |
| goto done_free_dma_rsp; |
| } |
| |
| if (new_config[0]) { |
| int ret; |
| |
| /* Revert back to original port config |
| * Also clear internal loopback |
| */ |
| ret = qla81xx_reset_loopback_mode(vha, |
| new_config, 0, 1); |
| if (ret) { |
| /* |
| * If the reset of the loopback mode |
| * doesn't work take FCoE dump and then |
| * reset the chip. |
| */ |
| qla2xxx_dump_fw(vha); |
| set_bit(ISP_ABORT_NEEDED, |
| &vha->dpc_flags); |
| } |
| |
| } |
| |
| } else { |
| type = "FC_BSG_HST_VENDOR_LOOPBACK"; |
| ql_dbg(ql_dbg_user, vha, 0x702b, |
| "BSG request type: %s.\n", type); |
| command_sent = INT_DEF_LB_LOOPBACK_CMD; |
| rval = qla2x00_loopback_test(vha, &elreq, response); |
| } |
| } |
| |
| if (rval) { |
| ql_log(ql_log_warn, vha, 0x702c, |
| "Vendor request %s failed.\n", type); |
| |
| rval = 0; |
| bsg_reply->result = (DID_ERROR << 16); |
| bsg_reply->reply_payload_rcv_len = 0; |
| } else { |
| ql_dbg(ql_dbg_user, vha, 0x702d, |
| "Vendor request %s completed.\n", type); |
| bsg_reply->result = (DID_OK << 16); |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, rsp_data, |
| rsp_data_len); |
| } |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply) + |
| sizeof(response) + sizeof(uint8_t); |
| fw_sts_ptr = bsg_job->reply + sizeof(struct fc_bsg_reply); |
| memcpy(bsg_job->reply + sizeof(struct fc_bsg_reply), response, |
| sizeof(response)); |
| fw_sts_ptr += sizeof(response); |
| *fw_sts_ptr = command_sent; |
| |
| done_free_dma_rsp: |
| dma_free_coherent(&ha->pdev->dev, rsp_data_len, |
| rsp_data, rsp_data_dma); |
| done_free_dma_req: |
| dma_free_coherent(&ha->pdev->dev, req_data_len, |
| req_data, req_data_dma); |
| done_unmap_sg: |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| done_unmap_req_sg: |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| if (!rval) |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return rval; |
| } |
| |
| static int |
| qla84xx_reset(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| uint32_t flag; |
| |
| if (!IS_QLA84XX(ha)) { |
| ql_dbg(ql_dbg_user, vha, 0x702f, "Not 84xx, exiting.\n"); |
| return -EINVAL; |
| } |
| |
| flag = bsg_request->rqst_data.h_vendor.vendor_cmd[1]; |
| |
| rval = qla84xx_reset_chip(vha, flag == A84_ISSUE_RESET_DIAG_FW); |
| |
| if (rval) { |
| ql_log(ql_log_warn, vha, 0x7030, |
| "Vendor request 84xx reset failed.\n"); |
| rval = (DID_ERROR << 16); |
| |
| } else { |
| ql_dbg(ql_dbg_user, vha, 0x7031, |
| "Vendor request 84xx reset completed.\n"); |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| } |
| |
| return rval; |
| } |
| |
| static int |
| qla84xx_updatefw(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| struct verify_chip_entry_84xx *mn = NULL; |
| dma_addr_t mn_dma, fw_dma; |
| void *fw_buf = NULL; |
| int rval = 0; |
| uint32_t sg_cnt; |
| uint32_t data_len; |
| uint16_t options; |
| uint32_t flag; |
| uint32_t fw_ver; |
| |
| if (!IS_QLA84XX(ha)) { |
| ql_dbg(ql_dbg_user, vha, 0x7032, |
| "Not 84xx, exiting.\n"); |
| return -EINVAL; |
| } |
| |
| sg_cnt = dma_map_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| if (!sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x7033, |
| "dma_map_sg returned %d for request.\n", sg_cnt); |
| return -ENOMEM; |
| } |
| |
| if (sg_cnt != bsg_job->request_payload.sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x7034, |
| "DMA mapping resulted in different sg counts, " |
| "request_sg_cnt: %x dma_request_sg_cnt: %x.\n", |
| bsg_job->request_payload.sg_cnt, sg_cnt); |
| rval = -EAGAIN; |
| goto done_unmap_sg; |
| } |
| |
| data_len = bsg_job->request_payload.payload_len; |
| fw_buf = dma_alloc_coherent(&ha->pdev->dev, data_len, |
| &fw_dma, GFP_KERNEL); |
| if (!fw_buf) { |
| ql_log(ql_log_warn, vha, 0x7035, |
| "DMA alloc failed for fw_buf.\n"); |
| rval = -ENOMEM; |
| goto done_unmap_sg; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, fw_buf, data_len); |
| |
| mn = dma_pool_zalloc(ha->s_dma_pool, GFP_KERNEL, &mn_dma); |
| if (!mn) { |
| ql_log(ql_log_warn, vha, 0x7036, |
| "DMA alloc failed for fw buffer.\n"); |
| rval = -ENOMEM; |
| goto done_free_fw_buf; |
| } |
| |
| flag = bsg_request->rqst_data.h_vendor.vendor_cmd[1]; |
| fw_ver = get_unaligned_le32((uint32_t *)fw_buf + 2); |
| |
| mn->entry_type = VERIFY_CHIP_IOCB_TYPE; |
| mn->entry_count = 1; |
| |
| options = VCO_FORCE_UPDATE | VCO_END_OF_DATA; |
| if (flag == A84_ISSUE_UPDATE_DIAGFW_CMD) |
| options |= VCO_DIAG_FW; |
| |
| mn->options = cpu_to_le16(options); |
| mn->fw_ver = cpu_to_le32(fw_ver); |
| mn->fw_size = cpu_to_le32(data_len); |
| mn->fw_seq_size = cpu_to_le32(data_len); |
| put_unaligned_le64(fw_dma, &mn->dsd.address); |
| mn->dsd.length = cpu_to_le32(data_len); |
| mn->data_seg_cnt = cpu_to_le16(1); |
| |
| rval = qla2x00_issue_iocb_timeout(vha, mn, mn_dma, 0, 120); |
| |
| if (rval) { |
| ql_log(ql_log_warn, vha, 0x7037, |
| "Vendor request 84xx updatefw failed.\n"); |
| |
| rval = (DID_ERROR << 16); |
| } else { |
| ql_dbg(ql_dbg_user, vha, 0x7038, |
| "Vendor request 84xx updatefw completed.\n"); |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK; |
| } |
| |
| dma_pool_free(ha->s_dma_pool, mn, mn_dma); |
| |
| done_free_fw_buf: |
| dma_free_coherent(&ha->pdev->dev, data_len, fw_buf, fw_dma); |
| |
| done_unmap_sg: |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| |
| if (!rval) |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return rval; |
| } |
| |
| static int |
| qla84xx_mgmt_cmd(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| struct access_chip_84xx *mn = NULL; |
| dma_addr_t mn_dma, mgmt_dma; |
| void *mgmt_b = NULL; |
| int rval = 0; |
| struct qla_bsg_a84_mgmt *ql84_mgmt; |
| uint32_t sg_cnt; |
| uint32_t data_len = 0; |
| uint32_t dma_direction = DMA_NONE; |
| |
| if (!IS_QLA84XX(ha)) { |
| ql_log(ql_log_warn, vha, 0x703a, |
| "Not 84xx, exiting.\n"); |
| return -EINVAL; |
| } |
| |
| mn = dma_pool_zalloc(ha->s_dma_pool, GFP_KERNEL, &mn_dma); |
| if (!mn) { |
| ql_log(ql_log_warn, vha, 0x703c, |
| "DMA alloc failed for fw buffer.\n"); |
| return -ENOMEM; |
| } |
| |
| mn->entry_type = ACCESS_CHIP_IOCB_TYPE; |
| mn->entry_count = 1; |
| ql84_mgmt = (void *)bsg_request + sizeof(struct fc_bsg_request); |
| switch (ql84_mgmt->mgmt.cmd) { |
| case QLA84_MGMT_READ_MEM: |
| case QLA84_MGMT_GET_INFO: |
| sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| if (!sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x703d, |
| "dma_map_sg returned %d for reply.\n", sg_cnt); |
| rval = -ENOMEM; |
| goto exit_mgmt; |
| } |
| |
| dma_direction = DMA_FROM_DEVICE; |
| |
| if (sg_cnt != bsg_job->reply_payload.sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x703e, |
| "DMA mapping resulted in different sg counts, " |
| "reply_sg_cnt: %x dma_reply_sg_cnt: %x.\n", |
| bsg_job->reply_payload.sg_cnt, sg_cnt); |
| rval = -EAGAIN; |
| goto done_unmap_sg; |
| } |
| |
| data_len = bsg_job->reply_payload.payload_len; |
| |
| mgmt_b = dma_alloc_coherent(&ha->pdev->dev, data_len, |
| &mgmt_dma, GFP_KERNEL); |
| if (!mgmt_b) { |
| ql_log(ql_log_warn, vha, 0x703f, |
| "DMA alloc failed for mgmt_b.\n"); |
| rval = -ENOMEM; |
| goto done_unmap_sg; |
| } |
| |
| if (ql84_mgmt->mgmt.cmd == QLA84_MGMT_READ_MEM) { |
| mn->options = cpu_to_le16(ACO_DUMP_MEMORY); |
| mn->parameter1 = |
| cpu_to_le32( |
| ql84_mgmt->mgmt.mgmtp.u.mem.start_addr); |
| |
| } else if (ql84_mgmt->mgmt.cmd == QLA84_MGMT_GET_INFO) { |
| mn->options = cpu_to_le16(ACO_REQUEST_INFO); |
| mn->parameter1 = |
| cpu_to_le32(ql84_mgmt->mgmt.mgmtp.u.info.type); |
| |
| mn->parameter2 = |
| cpu_to_le32( |
| ql84_mgmt->mgmt.mgmtp.u.info.context); |
| } |
| break; |
| |
| case QLA84_MGMT_WRITE_MEM: |
| sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| |
| if (!sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x7040, |
| "dma_map_sg returned %d.\n", sg_cnt); |
| rval = -ENOMEM; |
| goto exit_mgmt; |
| } |
| |
| dma_direction = DMA_TO_DEVICE; |
| |
| if (sg_cnt != bsg_job->request_payload.sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x7041, |
| "DMA mapping resulted in different sg counts, " |
| "request_sg_cnt: %x dma_request_sg_cnt: %x.\n", |
| bsg_job->request_payload.sg_cnt, sg_cnt); |
| rval = -EAGAIN; |
| goto done_unmap_sg; |
| } |
| |
| data_len = bsg_job->request_payload.payload_len; |
| mgmt_b = dma_alloc_coherent(&ha->pdev->dev, data_len, |
| &mgmt_dma, GFP_KERNEL); |
| if (!mgmt_b) { |
| ql_log(ql_log_warn, vha, 0x7042, |
| "DMA alloc failed for mgmt_b.\n"); |
| rval = -ENOMEM; |
| goto done_unmap_sg; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, mgmt_b, data_len); |
| |
| mn->options = cpu_to_le16(ACO_LOAD_MEMORY); |
| mn->parameter1 = |
| cpu_to_le32(ql84_mgmt->mgmt.mgmtp.u.mem.start_addr); |
| break; |
| |
| case QLA84_MGMT_CHNG_CONFIG: |
| mn->options = cpu_to_le16(ACO_CHANGE_CONFIG_PARAM); |
| mn->parameter1 = |
| cpu_to_le32(ql84_mgmt->mgmt.mgmtp.u.config.id); |
| |
| mn->parameter2 = |
| cpu_to_le32(ql84_mgmt->mgmt.mgmtp.u.config.param0); |
| |
| mn->parameter3 = |
| cpu_to_le32(ql84_mgmt->mgmt.mgmtp.u.config.param1); |
| break; |
| |
| default: |
| rval = -EIO; |
| goto exit_mgmt; |
| } |
| |
| if (ql84_mgmt->mgmt.cmd != QLA84_MGMT_CHNG_CONFIG) { |
| mn->total_byte_cnt = cpu_to_le32(ql84_mgmt->mgmt.len); |
| mn->dseg_count = cpu_to_le16(1); |
| put_unaligned_le64(mgmt_dma, &mn->dsd.address); |
| mn->dsd.length = cpu_to_le32(ql84_mgmt->mgmt.len); |
| } |
| |
| rval = qla2x00_issue_iocb(vha, mn, mn_dma, 0); |
| |
| if (rval) { |
| ql_log(ql_log_warn, vha, 0x7043, |
| "Vendor request 84xx mgmt failed.\n"); |
| |
| rval = (DID_ERROR << 16); |
| |
| } else { |
| ql_dbg(ql_dbg_user, vha, 0x7044, |
| "Vendor request 84xx mgmt completed.\n"); |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK; |
| |
| if ((ql84_mgmt->mgmt.cmd == QLA84_MGMT_READ_MEM) || |
| (ql84_mgmt->mgmt.cmd == QLA84_MGMT_GET_INFO)) { |
| bsg_reply->reply_payload_rcv_len = |
| bsg_job->reply_payload.payload_len; |
| |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, mgmt_b, |
| data_len); |
| } |
| } |
| |
| done_unmap_sg: |
| if (mgmt_b) |
| dma_free_coherent(&ha->pdev->dev, data_len, mgmt_b, mgmt_dma); |
| |
| if (dma_direction == DMA_TO_DEVICE) |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| else if (dma_direction == DMA_FROM_DEVICE) |
| dma_unmap_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| |
| exit_mgmt: |
| dma_pool_free(ha->s_dma_pool, mn, mn_dma); |
| |
| if (!rval) |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return rval; |
| } |
| |
| static int |
| qla24xx_iidma(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| int rval = 0; |
| struct qla_port_param *port_param = NULL; |
| fc_port_t *fcport = NULL; |
| int found = 0; |
| uint16_t mb[MAILBOX_REGISTER_COUNT]; |
| uint8_t *rsp_ptr = NULL; |
| |
| if (!IS_IIDMA_CAPABLE(vha->hw)) { |
| ql_log(ql_log_info, vha, 0x7046, "iiDMA not supported.\n"); |
| return -EINVAL; |
| } |
| |
| port_param = (void *)bsg_request + sizeof(struct fc_bsg_request); |
| if (port_param->fc_scsi_addr.dest_type != EXT_DEF_TYPE_WWPN) { |
| ql_log(ql_log_warn, vha, 0x7048, |
| "Invalid destination type.\n"); |
| return -EINVAL; |
| } |
| |
| list_for_each_entry(fcport, &vha->vp_fcports, list) { |
| if (fcport->port_type != FCT_TARGET) |
| continue; |
| |
| if (memcmp(port_param->fc_scsi_addr.dest_addr.wwpn, |
| fcport->port_name, sizeof(fcport->port_name))) |
| continue; |
| |
| found = 1; |
| break; |
| } |
| |
| if (!found) { |
| ql_log(ql_log_warn, vha, 0x7049, |
| "Failed to find port.\n"); |
| return -EINVAL; |
| } |
| |
| if (atomic_read(&fcport->state) != FCS_ONLINE) { |
| ql_log(ql_log_warn, vha, 0x704a, |
| "Port is not online.\n"); |
| return -EINVAL; |
| } |
| |
| if (fcport->flags & FCF_LOGIN_NEEDED) { |
| ql_log(ql_log_warn, vha, 0x704b, |
| "Remote port not logged in flags = 0x%x.\n", fcport->flags); |
| return -EINVAL; |
| } |
| |
| if (port_param->mode) |
| rval = qla2x00_set_idma_speed(vha, fcport->loop_id, |
| port_param->speed, mb); |
| else |
| rval = qla2x00_get_idma_speed(vha, fcport->loop_id, |
| &port_param->speed, mb); |
| |
| if (rval) { |
| ql_log(ql_log_warn, vha, 0x704c, |
| "iiDMA cmd failed for %8phN -- " |
| "%04x %x %04x %04x.\n", fcport->port_name, |
| rval, fcport->fp_speed, mb[0], mb[1]); |
| rval = (DID_ERROR << 16); |
| } else { |
| if (!port_param->mode) { |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply) + |
| sizeof(struct qla_port_param); |
| |
| rsp_ptr = ((uint8_t *)bsg_reply) + |
| sizeof(struct fc_bsg_reply); |
| |
| memcpy(rsp_ptr, port_param, |
| sizeof(struct qla_port_param)); |
| } |
| |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| } |
| |
| return rval; |
| } |
| |
| static int |
| qla2x00_optrom_setup(struct bsg_job *bsg_job, scsi_qla_host_t *vha, |
| uint8_t is_update) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| uint32_t start = 0; |
| int valid = 0; |
| struct qla_hw_data *ha = vha->hw; |
| |
| if (unlikely(pci_channel_offline(ha->pdev))) |
| return -EINVAL; |
| |
| start = bsg_request->rqst_data.h_vendor.vendor_cmd[1]; |
| if (start > ha->optrom_size) { |
| ql_log(ql_log_warn, vha, 0x7055, |
| "start %d > optrom_size %d.\n", start, ha->optrom_size); |
| return -EINVAL; |
| } |
| |
| if (ha->optrom_state != QLA_SWAITING) { |
| ql_log(ql_log_info, vha, 0x7056, |
| "optrom_state %d.\n", ha->optrom_state); |
| return -EBUSY; |
| } |
| |
| ha->optrom_region_start = start; |
| ql_dbg(ql_dbg_user, vha, 0x7057, "is_update=%d.\n", is_update); |
| if (is_update) { |
| if (ha->optrom_size == OPTROM_SIZE_2300 && start == 0) |
| valid = 1; |
| else if (start == (ha->flt_region_boot * 4) || |
| start == (ha->flt_region_fw * 4)) |
| valid = 1; |
| else if (IS_QLA24XX_TYPE(ha) || IS_QLA25XX(ha) || |
| IS_CNA_CAPABLE(ha) || IS_QLA2031(ha) || IS_QLA27XX(ha) || |
| IS_QLA28XX(ha)) |
| valid = 1; |
| if (!valid) { |
| ql_log(ql_log_warn, vha, 0x7058, |
| "Invalid start region 0x%x/0x%x.\n", start, |
| bsg_job->request_payload.payload_len); |
| return -EINVAL; |
| } |
| |
| ha->optrom_region_size = start + |
| bsg_job->request_payload.payload_len > ha->optrom_size ? |
| ha->optrom_size - start : |
| bsg_job->request_payload.payload_len; |
| ha->optrom_state = QLA_SWRITING; |
| } else { |
| ha->optrom_region_size = start + |
| bsg_job->reply_payload.payload_len > ha->optrom_size ? |
| ha->optrom_size - start : |
| bsg_job->reply_payload.payload_len; |
| ha->optrom_state = QLA_SREADING; |
| } |
| |
| ha->optrom_buffer = vzalloc(ha->optrom_region_size); |
| if (!ha->optrom_buffer) { |
| ql_log(ql_log_warn, vha, 0x7059, |
| "Read: Unable to allocate memory for optrom retrieval " |
| "(%x)\n", ha->optrom_region_size); |
| |
| ha->optrom_state = QLA_SWAITING; |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_read_optrom(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| |
| if (ha->flags.nic_core_reset_hdlr_active) |
| return -EBUSY; |
| |
| mutex_lock(&ha->optrom_mutex); |
| rval = qla2x00_optrom_setup(bsg_job, vha, 0); |
| if (rval) { |
| mutex_unlock(&ha->optrom_mutex); |
| return rval; |
| } |
| |
| ha->isp_ops->read_optrom(vha, ha->optrom_buffer, |
| ha->optrom_region_start, ha->optrom_region_size); |
| |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, ha->optrom_buffer, |
| ha->optrom_region_size); |
| |
| bsg_reply->reply_payload_rcv_len = ha->optrom_region_size; |
| bsg_reply->result = DID_OK; |
| vfree(ha->optrom_buffer); |
| ha->optrom_buffer = NULL; |
| ha->optrom_state = QLA_SWAITING; |
| mutex_unlock(&ha->optrom_mutex); |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return rval; |
| } |
| |
| static int |
| qla2x00_update_optrom(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| |
| mutex_lock(&ha->optrom_mutex); |
| rval = qla2x00_optrom_setup(bsg_job, vha, 1); |
| if (rval) { |
| mutex_unlock(&ha->optrom_mutex); |
| return rval; |
| } |
| |
| /* Set the isp82xx_no_md_cap not to capture minidump */ |
| ha->flags.isp82xx_no_md_cap = 1; |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, ha->optrom_buffer, |
| ha->optrom_region_size); |
| |
| rval = ha->isp_ops->write_optrom(vha, ha->optrom_buffer, |
| ha->optrom_region_start, ha->optrom_region_size); |
| |
| if (rval) { |
| bsg_reply->result = -EINVAL; |
| rval = -EINVAL; |
| } else { |
| bsg_reply->result = DID_OK; |
| } |
| vfree(ha->optrom_buffer); |
| ha->optrom_buffer = NULL; |
| ha->optrom_state = QLA_SWAITING; |
| mutex_unlock(&ha->optrom_mutex); |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return rval; |
| } |
| |
| static int |
| qla2x00_update_fru_versions(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| uint8_t bsg[DMA_POOL_SIZE]; |
| struct qla_image_version_list *list = (void *)bsg; |
| struct qla_image_version *image; |
| uint32_t count; |
| dma_addr_t sfp_dma; |
| void *sfp = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &sfp_dma); |
| |
| if (!sfp) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_NO_MEMORY; |
| goto done; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, list, sizeof(bsg)); |
| |
| image = list->version; |
| count = list->count; |
| while (count--) { |
| memcpy(sfp, &image->field_info, sizeof(image->field_info)); |
| rval = qla2x00_write_sfp(vha, sfp_dma, sfp, |
| image->field_address.device, image->field_address.offset, |
| sizeof(image->field_info), image->field_address.option); |
| if (rval) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_MAILBOX; |
| goto dealloc; |
| } |
| image++; |
| } |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = 0; |
| |
| dealloc: |
| dma_pool_free(ha->s_dma_pool, sfp, sfp_dma); |
| |
| done: |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_read_fru_status(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| uint8_t bsg[DMA_POOL_SIZE]; |
| struct qla_status_reg *sr = (void *)bsg; |
| dma_addr_t sfp_dma; |
| uint8_t *sfp = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &sfp_dma); |
| |
| if (!sfp) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_NO_MEMORY; |
| goto done; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, sr, sizeof(*sr)); |
| |
| rval = qla2x00_read_sfp(vha, sfp_dma, sfp, |
| sr->field_address.device, sr->field_address.offset, |
| sizeof(sr->status_reg), sr->field_address.option); |
| sr->status_reg = *sfp; |
| |
| if (rval) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_MAILBOX; |
| goto dealloc; |
| } |
| |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, sr, sizeof(*sr)); |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = 0; |
| |
| dealloc: |
| dma_pool_free(ha->s_dma_pool, sfp, sfp_dma); |
| |
| done: |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->reply_payload_rcv_len = sizeof(*sr); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_write_fru_status(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| uint8_t bsg[DMA_POOL_SIZE]; |
| struct qla_status_reg *sr = (void *)bsg; |
| dma_addr_t sfp_dma; |
| uint8_t *sfp = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &sfp_dma); |
| |
| if (!sfp) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_NO_MEMORY; |
| goto done; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, sr, sizeof(*sr)); |
| |
| *sfp = sr->status_reg; |
| rval = qla2x00_write_sfp(vha, sfp_dma, sfp, |
| sr->field_address.device, sr->field_address.offset, |
| sizeof(sr->status_reg), sr->field_address.option); |
| |
| if (rval) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_MAILBOX; |
| goto dealloc; |
| } |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = 0; |
| |
| dealloc: |
| dma_pool_free(ha->s_dma_pool, sfp, sfp_dma); |
| |
| done: |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_write_i2c(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| uint8_t bsg[DMA_POOL_SIZE]; |
| struct qla_i2c_access *i2c = (void *)bsg; |
| dma_addr_t sfp_dma; |
| uint8_t *sfp = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &sfp_dma); |
| |
| if (!sfp) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_NO_MEMORY; |
| goto done; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, i2c, sizeof(*i2c)); |
| |
| memcpy(sfp, i2c->buffer, i2c->length); |
| rval = qla2x00_write_sfp(vha, sfp_dma, sfp, |
| i2c->device, i2c->offset, i2c->length, i2c->option); |
| |
| if (rval) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_MAILBOX; |
| goto dealloc; |
| } |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = 0; |
| |
| dealloc: |
| dma_pool_free(ha->s_dma_pool, sfp, sfp_dma); |
| |
| done: |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_read_i2c(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = 0; |
| uint8_t bsg[DMA_POOL_SIZE]; |
| struct qla_i2c_access *i2c = (void *)bsg; |
| dma_addr_t sfp_dma; |
| uint8_t *sfp = dma_pool_alloc(ha->s_dma_pool, GFP_KERNEL, &sfp_dma); |
| |
| if (!sfp) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_NO_MEMORY; |
| goto done; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, i2c, sizeof(*i2c)); |
| |
| rval = qla2x00_read_sfp(vha, sfp_dma, sfp, |
| i2c->device, i2c->offset, i2c->length, i2c->option); |
| |
| if (rval) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_MAILBOX; |
| goto dealloc; |
| } |
| |
| memcpy(i2c->buffer, sfp, i2c->length); |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, i2c, sizeof(*i2c)); |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = 0; |
| |
| dealloc: |
| dma_pool_free(ha->s_dma_pool, sfp, sfp_dma); |
| |
| done: |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->reply_payload_rcv_len = sizeof(*i2c); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return 0; |
| } |
| |
| static int |
| qla24xx_process_bidir_cmd(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| uint32_t rval = EXT_STATUS_OK; |
| uint16_t req_sg_cnt = 0; |
| uint16_t rsp_sg_cnt = 0; |
| uint16_t nextlid = 0; |
| uint32_t tot_dsds; |
| srb_t *sp = NULL; |
| uint32_t req_data_len; |
| uint32_t rsp_data_len; |
| |
| /* Check the type of the adapter */ |
| if (!IS_BIDI_CAPABLE(ha)) { |
| ql_log(ql_log_warn, vha, 0x70a0, |
| "This adapter is not supported\n"); |
| rval = EXT_STATUS_NOT_SUPPORTED; |
| goto done; |
| } |
| |
| if (test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags) || |
| test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) || |
| test_bit(ISP_ABORT_RETRY, &vha->dpc_flags)) { |
| rval = EXT_STATUS_BUSY; |
| goto done; |
| } |
| |
| /* Check if host is online */ |
| if (!vha->flags.online) { |
| ql_log(ql_log_warn, vha, 0x70a1, |
| "Host is not online\n"); |
| rval = EXT_STATUS_DEVICE_OFFLINE; |
| goto done; |
| } |
| |
| /* Check if cable is plugged in or not */ |
| if (vha->device_flags & DFLG_NO_CABLE) { |
| ql_log(ql_log_warn, vha, 0x70a2, |
| "Cable is unplugged...\n"); |
| rval = EXT_STATUS_INVALID_CFG; |
| goto done; |
| } |
| |
| /* Check if the switch is connected or not */ |
| if (ha->current_topology != ISP_CFG_F) { |
| ql_log(ql_log_warn, vha, 0x70a3, |
| "Host is not connected to the switch\n"); |
| rval = EXT_STATUS_INVALID_CFG; |
| goto done; |
| } |
| |
| /* Check if operating mode is P2P */ |
| if (ha->operating_mode != P2P) { |
| ql_log(ql_log_warn, vha, 0x70a4, |
| "Host operating mode is not P2p\n"); |
| rval = EXT_STATUS_INVALID_CFG; |
| goto done; |
| } |
| |
| mutex_lock(&ha->selflogin_lock); |
| if (vha->self_login_loop_id == 0) { |
| /* Initialize all required fields of fcport */ |
| vha->bidir_fcport.vha = vha; |
| vha->bidir_fcport.d_id.b.al_pa = vha->d_id.b.al_pa; |
| vha->bidir_fcport.d_id.b.area = vha->d_id.b.area; |
| vha->bidir_fcport.d_id.b.domain = vha->d_id.b.domain; |
| vha->bidir_fcport.loop_id = vha->loop_id; |
| |
| if (qla2x00_fabric_login(vha, &(vha->bidir_fcport), &nextlid)) { |
| ql_log(ql_log_warn, vha, 0x70a7, |
| "Failed to login port %06X for bidirectional IOCB\n", |
| vha->bidir_fcport.d_id.b24); |
| mutex_unlock(&ha->selflogin_lock); |
| rval = EXT_STATUS_MAILBOX; |
| goto done; |
| } |
| vha->self_login_loop_id = nextlid - 1; |
| |
| } |
| /* Assign the self login loop id to fcport */ |
| mutex_unlock(&ha->selflogin_lock); |
| |
| vha->bidir_fcport.loop_id = vha->self_login_loop_id; |
| |
| req_sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, |
| DMA_TO_DEVICE); |
| |
| if (!req_sg_cnt) { |
| rval = EXT_STATUS_NO_MEMORY; |
| goto done; |
| } |
| |
| rsp_sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, bsg_job->reply_payload.sg_cnt, |
| DMA_FROM_DEVICE); |
| |
| if (!rsp_sg_cnt) { |
| rval = EXT_STATUS_NO_MEMORY; |
| goto done_unmap_req_sg; |
| } |
| |
| if ((req_sg_cnt != bsg_job->request_payload.sg_cnt) || |
| (rsp_sg_cnt != bsg_job->reply_payload.sg_cnt)) { |
| ql_dbg(ql_dbg_user, vha, 0x70a9, |
| "Dma mapping resulted in different sg counts " |
| "[request_sg_cnt: %x dma_request_sg_cnt: %x reply_sg_cnt: " |
| "%x dma_reply_sg_cnt: %x]\n", |
| bsg_job->request_payload.sg_cnt, req_sg_cnt, |
| bsg_job->reply_payload.sg_cnt, rsp_sg_cnt); |
| rval = EXT_STATUS_NO_MEMORY; |
| goto done_unmap_sg; |
| } |
| |
| req_data_len = bsg_job->request_payload.payload_len; |
| rsp_data_len = bsg_job->reply_payload.payload_len; |
| |
| if (req_data_len != rsp_data_len) { |
| rval = EXT_STATUS_BUSY; |
| ql_log(ql_log_warn, vha, 0x70aa, |
| "req_data_len != rsp_data_len\n"); |
| goto done_unmap_sg; |
| } |
| |
| /* Alloc SRB structure */ |
| sp = qla2x00_get_sp(vha, &(vha->bidir_fcport), GFP_KERNEL); |
| if (!sp) { |
| ql_dbg(ql_dbg_user, vha, 0x70ac, |
| "Alloc SRB structure failed\n"); |
| rval = EXT_STATUS_NO_MEMORY; |
| goto done_unmap_sg; |
| } |
| |
| /*Populate srb->ctx with bidir ctx*/ |
| sp->u.bsg_job = bsg_job; |
| sp->free = qla2x00_bsg_sp_free; |
| sp->type = SRB_BIDI_CMD; |
| sp->done = qla2x00_bsg_job_done; |
| |
| /* Add the read and write sg count */ |
| tot_dsds = rsp_sg_cnt + req_sg_cnt; |
| |
| rval = qla2x00_start_bidir(sp, vha, tot_dsds); |
| if (rval != EXT_STATUS_OK) |
| goto done_free_srb; |
| /* the bsg request will be completed in the interrupt handler */ |
| return rval; |
| |
| done_free_srb: |
| mempool_free(sp, ha->srb_mempool); |
| done_unmap_sg: |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| done_unmap_req_sg: |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| done: |
| |
| /* Return an error vendor specific response |
| * and complete the bsg request |
| */ |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = rval; |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->reply_payload_rcv_len = 0; |
| bsg_reply->result = (DID_OK) << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| /* Always return success, vendor rsp carries correct status */ |
| return 0; |
| } |
| |
| static int |
| qlafx00_mgmt_cmd(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| int rval = (DID_ERROR << 16); |
| struct qla_mt_iocb_rqst_fx00 *piocb_rqst; |
| srb_t *sp; |
| int req_sg_cnt = 0, rsp_sg_cnt = 0; |
| struct fc_port *fcport; |
| char *type = "FC_BSG_HST_FX_MGMT"; |
| |
| /* Copy the IOCB specific information */ |
| piocb_rqst = (struct qla_mt_iocb_rqst_fx00 *) |
| &bsg_request->rqst_data.h_vendor.vendor_cmd[1]; |
| |
| /* Dump the vendor information */ |
| ql_dump_buffer(ql_dbg_user + ql_dbg_verbose , vha, 0x70cf, |
| piocb_rqst, sizeof(*piocb_rqst)); |
| |
| if (!vha->flags.online) { |
| ql_log(ql_log_warn, vha, 0x70d0, |
| "Host is not online.\n"); |
| rval = -EIO; |
| goto done; |
| } |
| |
| if (piocb_rqst->flags & SRB_FXDISC_REQ_DMA_VALID) { |
| req_sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| if (!req_sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x70c7, |
| "dma_map_sg return %d for request\n", req_sg_cnt); |
| rval = -ENOMEM; |
| goto done; |
| } |
| } |
| |
| if (piocb_rqst->flags & SRB_FXDISC_RESP_DMA_VALID) { |
| rsp_sg_cnt = dma_map_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| if (!rsp_sg_cnt) { |
| ql_log(ql_log_warn, vha, 0x70c8, |
| "dma_map_sg return %d for reply\n", rsp_sg_cnt); |
| rval = -ENOMEM; |
| goto done_unmap_req_sg; |
| } |
| } |
| |
| ql_dbg(ql_dbg_user, vha, 0x70c9, |
| "request_sg_cnt: %x dma_request_sg_cnt: %x reply_sg_cnt:%x " |
| "dma_reply_sg_cnt: %x\n", bsg_job->request_payload.sg_cnt, |
| req_sg_cnt, bsg_job->reply_payload.sg_cnt, rsp_sg_cnt); |
| |
| /* Allocate a dummy fcport structure, since functions preparing the |
| * IOCB and mailbox command retrieves port specific information |
| * from fcport structure. For Host based ELS commands there will be |
| * no fcport structure allocated |
| */ |
| fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); |
| if (!fcport) { |
| ql_log(ql_log_warn, vha, 0x70ca, |
| "Failed to allocate fcport.\n"); |
| rval = -ENOMEM; |
| goto done_unmap_rsp_sg; |
| } |
| |
| /* Alloc SRB structure */ |
| sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); |
| if (!sp) { |
| ql_log(ql_log_warn, vha, 0x70cb, |
| "qla2x00_get_sp failed.\n"); |
| rval = -ENOMEM; |
| goto done_free_fcport; |
| } |
| |
| /* Initialize all required fields of fcport */ |
| fcport->vha = vha; |
| fcport->loop_id = le32_to_cpu(piocb_rqst->dataword); |
| |
| sp->type = SRB_FXIOCB_BCMD; |
| sp->name = "bsg_fx_mgmt"; |
| sp->iocbs = qla24xx_calc_ct_iocbs(req_sg_cnt + rsp_sg_cnt); |
| sp->u.bsg_job = bsg_job; |
| sp->free = qla2x00_bsg_sp_free; |
| sp->done = qla2x00_bsg_job_done; |
| |
| ql_dbg(ql_dbg_user, vha, 0x70cc, |
| "bsg rqst type: %s fx_mgmt_type: %x id=%x\n", |
| type, piocb_rqst->func_type, fcport->loop_id); |
| |
| rval = qla2x00_start_sp(sp); |
| if (rval != QLA_SUCCESS) { |
| ql_log(ql_log_warn, vha, 0x70cd, |
| "qla2x00_start_sp failed=%d.\n", rval); |
| mempool_free(sp, ha->srb_mempool); |
| rval = -EIO; |
| goto done_free_fcport; |
| } |
| return rval; |
| |
| done_free_fcport: |
| qla2x00_free_fcport(fcport); |
| |
| done_unmap_rsp_sg: |
| if (piocb_rqst->flags & SRB_FXDISC_RESP_DMA_VALID) |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); |
| done_unmap_req_sg: |
| if (piocb_rqst->flags & SRB_FXDISC_REQ_DMA_VALID) |
| dma_unmap_sg(&ha->pdev->dev, |
| bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); |
| |
| done: |
| return rval; |
| } |
| |
| static int |
| qla26xx_serdes_op(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| int rval = 0; |
| struct qla_serdes_reg sr; |
| |
| memset(&sr, 0, sizeof(sr)); |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, &sr, sizeof(sr)); |
| |
| switch (sr.cmd) { |
| case INT_SC_SERDES_WRITE_REG: |
| rval = qla2x00_write_serdes_word(vha, sr.addr, sr.val); |
| bsg_reply->reply_payload_rcv_len = 0; |
| break; |
| case INT_SC_SERDES_READ_REG: |
| rval = qla2x00_read_serdes_word(vha, sr.addr, &sr.val); |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, &sr, sizeof(sr)); |
| bsg_reply->reply_payload_rcv_len = sizeof(sr); |
| break; |
| default: |
| ql_dbg(ql_dbg_user, vha, 0x708c, |
| "Unknown serdes cmd %x.\n", sr.cmd); |
| rval = -EINVAL; |
| break; |
| } |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| rval ? EXT_STATUS_MAILBOX : 0; |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return 0; |
| } |
| |
| static int |
| qla8044_serdes_op(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| int rval = 0; |
| struct qla_serdes_reg_ex sr; |
| |
| memset(&sr, 0, sizeof(sr)); |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, &sr, sizeof(sr)); |
| |
| switch (sr.cmd) { |
| case INT_SC_SERDES_WRITE_REG: |
| rval = qla8044_write_serdes_word(vha, sr.addr, sr.val); |
| bsg_reply->reply_payload_rcv_len = 0; |
| break; |
| case INT_SC_SERDES_READ_REG: |
| rval = qla8044_read_serdes_word(vha, sr.addr, &sr.val); |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, &sr, sizeof(sr)); |
| bsg_reply->reply_payload_rcv_len = sizeof(sr); |
| break; |
| default: |
| ql_dbg(ql_dbg_user, vha, 0x7020, |
| "Unknown serdes cmd %x.\n", sr.cmd); |
| rval = -EINVAL; |
| break; |
| } |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| rval ? EXT_STATUS_MAILBOX : 0; |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return 0; |
| } |
| |
| static int |
| qla27xx_get_flash_upd_cap(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| struct qla_flash_update_caps cap; |
| |
| if (!(IS_QLA27XX(ha)) && !IS_QLA28XX(ha)) |
| return -EPERM; |
| |
| memset(&cap, 0, sizeof(cap)); |
| cap.capabilities = (uint64_t)ha->fw_attributes_ext[1] << 48 | |
| (uint64_t)ha->fw_attributes_ext[0] << 32 | |
| (uint64_t)ha->fw_attributes_h << 16 | |
| (uint64_t)ha->fw_attributes; |
| |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, &cap, sizeof(cap)); |
| bsg_reply->reply_payload_rcv_len = sizeof(cap); |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_OK; |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return 0; |
| } |
| |
| static int |
| qla27xx_set_flash_upd_cap(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| uint64_t online_fw_attr = 0; |
| struct qla_flash_update_caps cap; |
| |
| if (!IS_QLA27XX(ha) && !IS_QLA28XX(ha)) |
| return -EPERM; |
| |
| memset(&cap, 0, sizeof(cap)); |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, &cap, sizeof(cap)); |
| |
| online_fw_attr = (uint64_t)ha->fw_attributes_ext[1] << 48 | |
| (uint64_t)ha->fw_attributes_ext[0] << 32 | |
| (uint64_t)ha->fw_attributes_h << 16 | |
| (uint64_t)ha->fw_attributes; |
| |
| if (online_fw_attr != cap.capabilities) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_INVALID_PARAM; |
| return -EINVAL; |
| } |
| |
| if (cap.outage_duration < MAX_LOOP_TIMEOUT) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_INVALID_PARAM; |
| return -EINVAL; |
| } |
| |
| bsg_reply->reply_payload_rcv_len = 0; |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_OK; |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return 0; |
| } |
| |
| static int |
| qla27xx_get_bbcr_data(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| struct qla_bbcr_data bbcr; |
| uint16_t loop_id, topo, sw_cap; |
| uint8_t domain, area, al_pa, state; |
| int rval; |
| |
| if (!IS_QLA27XX(ha) && !IS_QLA28XX(ha)) |
| return -EPERM; |
| |
| memset(&bbcr, 0, sizeof(bbcr)); |
| |
| if (vha->flags.bbcr_enable) |
| bbcr.status = QLA_BBCR_STATUS_ENABLED; |
| else |
| bbcr.status = QLA_BBCR_STATUS_DISABLED; |
| |
| if (bbcr.status == QLA_BBCR_STATUS_ENABLED) { |
| rval = qla2x00_get_adapter_id(vha, &loop_id, &al_pa, |
| &area, &domain, &topo, &sw_cap); |
| if (rval != QLA_SUCCESS) { |
| bbcr.status = QLA_BBCR_STATUS_UNKNOWN; |
| bbcr.state = QLA_BBCR_STATE_OFFLINE; |
| bbcr.mbx1 = loop_id; |
| goto done; |
| } |
| |
| state = (vha->bbcr >> 12) & 0x1; |
| |
| if (state) { |
| bbcr.state = QLA_BBCR_STATE_OFFLINE; |
| bbcr.offline_reason_code = QLA_BBCR_REASON_LOGIN_REJECT; |
| } else { |
| bbcr.state = QLA_BBCR_STATE_ONLINE; |
| bbcr.negotiated_bbscn = (vha->bbcr >> 8) & 0xf; |
| } |
| |
| bbcr.configured_bbscn = vha->bbcr & 0xf; |
| } |
| |
| done: |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, &bbcr, sizeof(bbcr)); |
| bsg_reply->reply_payload_rcv_len = sizeof(bbcr); |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_OK; |
| |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| return 0; |
| } |
| |
| static int |
| qla2x00_get_priv_stats(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| struct qla_hw_data *ha = vha->hw; |
| struct scsi_qla_host *base_vha = pci_get_drvdata(ha->pdev); |
| struct link_statistics *stats = NULL; |
| dma_addr_t stats_dma; |
| int rval; |
| uint32_t *cmd = bsg_request->rqst_data.h_vendor.vendor_cmd; |
| uint options = cmd[0] == QL_VND_GET_PRIV_STATS_EX ? cmd[1] : 0; |
| |
| if (test_bit(UNLOADING, &vha->dpc_flags)) |
| return -ENODEV; |
| |
| if (unlikely(pci_channel_offline(ha->pdev))) |
| return -ENODEV; |
| |
| if (qla2x00_reset_active(vha)) |
| return -EBUSY; |
| |
| if (!IS_FWI2_CAPABLE(ha)) |
| return -EPERM; |
| |
| stats = dma_alloc_coherent(&ha->pdev->dev, sizeof(*stats), &stats_dma, |
| GFP_KERNEL); |
| if (!stats) { |
| ql_log(ql_log_warn, vha, 0x70e2, |
| "Failed to allocate memory for stats.\n"); |
| return -ENOMEM; |
| } |
| |
| rval = qla24xx_get_isp_stats(base_vha, stats, stats_dma, options); |
| |
| if (rval == QLA_SUCCESS) { |
| ql_dump_buffer(ql_dbg_user + ql_dbg_verbose, vha, 0x70e5, |
| stats, sizeof(*stats)); |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, stats, sizeof(*stats)); |
| } |
| |
| bsg_reply->reply_payload_rcv_len = sizeof(*stats); |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| rval ? EXT_STATUS_MAILBOX : EXT_STATUS_OK; |
| |
| bsg_job->reply_len = sizeof(*bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| dma_free_coherent(&ha->pdev->dev, sizeof(*stats), |
| stats, stats_dma); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_do_dport_diagnostics(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| int rval; |
| struct qla_dport_diag *dd; |
| |
| if (!IS_QLA83XX(vha->hw) && !IS_QLA27XX(vha->hw) && |
| !IS_QLA28XX(vha->hw)) |
| return -EPERM; |
| |
| dd = kmalloc(sizeof(*dd), GFP_KERNEL); |
| if (!dd) { |
| ql_log(ql_log_warn, vha, 0x70db, |
| "Failed to allocate memory for dport.\n"); |
| return -ENOMEM; |
| } |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, dd, sizeof(*dd)); |
| |
| rval = qla26xx_dport_diagnostics( |
| vha, dd->buf, sizeof(dd->buf), dd->options); |
| if (rval == QLA_SUCCESS) { |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, dd, sizeof(*dd)); |
| } |
| |
| bsg_reply->reply_payload_rcv_len = sizeof(*dd); |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| rval ? EXT_STATUS_MAILBOX : EXT_STATUS_OK; |
| |
| bsg_job->reply_len = sizeof(*bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| kfree(dd); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_do_dport_diagnostics_v2(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct Scsi_Host *host = fc_bsg_to_shost(bsg_job); |
| scsi_qla_host_t *vha = shost_priv(host); |
| int rval; |
| struct qla_dport_diag_v2 *dd; |
| mbx_cmd_t mc; |
| mbx_cmd_t *mcp = &mc; |
| uint16_t options; |
| |
| if (!IS_DPORT_CAPABLE(vha->hw)) |
| return -EPERM; |
| |
| dd = kzalloc(sizeof(*dd), GFP_KERNEL); |
| if (!dd) |
| return -ENOMEM; |
| |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, dd, sizeof(*dd)); |
| |
| options = dd->options; |
| |
| /* Check dport Test in progress */ |
| if (options == QLA_GET_DPORT_RESULT_V2 && |
| vha->dport_status & DPORT_DIAG_IN_PROGRESS) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_DPORT_DIAG_IN_PROCESS; |
| goto dportcomplete; |
| } |
| |
| /* Check chip reset in progress and start/restart requests arrive */ |
| if (vha->dport_status & DPORT_DIAG_CHIP_RESET_IN_PROGRESS && |
| (options == QLA_START_DPORT_TEST_V2 || |
| options == QLA_RESTART_DPORT_TEST_V2)) { |
| vha->dport_status &= ~DPORT_DIAG_CHIP_RESET_IN_PROGRESS; |
| } |
| |
| /* Check chip reset in progress and get result request arrive */ |
| if (vha->dport_status & DPORT_DIAG_CHIP_RESET_IN_PROGRESS && |
| options == QLA_GET_DPORT_RESULT_V2) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_DPORT_DIAG_NOT_RUNNING; |
| goto dportcomplete; |
| } |
| |
| rval = qla26xx_dport_diagnostics_v2(vha, dd, mcp); |
| |
| if (rval == QLA_SUCCESS) { |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_OK; |
| if (options == QLA_START_DPORT_TEST_V2 || |
| options == QLA_RESTART_DPORT_TEST_V2) { |
| dd->mbx1 = mcp->mb[0]; |
| dd->mbx2 = mcp->mb[1]; |
| vha->dport_status |= DPORT_DIAG_IN_PROGRESS; |
| } else if (options == QLA_GET_DPORT_RESULT_V2) { |
| dd->mbx1 = le16_to_cpu(vha->dport_data[1]); |
| dd->mbx2 = le16_to_cpu(vha->dport_data[2]); |
| } |
| } else { |
| dd->mbx1 = mcp->mb[0]; |
| dd->mbx2 = mcp->mb[1]; |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = |
| EXT_STATUS_DPORT_DIAG_ERR; |
| } |
| |
| dportcomplete: |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, dd, sizeof(*dd)); |
| |
| bsg_reply->reply_payload_rcv_len = sizeof(*dd); |
| bsg_job->reply_len = sizeof(*bsg_reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| kfree(dd); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_get_flash_image_status(struct bsg_job *bsg_job) |
| { |
| scsi_qla_host_t *vha = shost_priv(fc_bsg_to_shost(bsg_job)); |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct qla_hw_data *ha = vha->hw; |
| struct qla_active_regions regions = { }; |
| struct active_regions active_regions = { }; |
| |
| qla27xx_get_active_image(vha, &active_regions); |
| regions.global_image = active_regions.global; |
| |
| if (IS_QLA27XX(ha)) |
| regions.nvme_params = QLA27XX_PRIMARY_IMAGE; |
| |
| if (IS_QLA28XX(ha)) { |
| qla28xx_get_aux_images(vha, &active_regions); |
| regions.board_config = active_regions.aux.board_config; |
| regions.vpd_nvram = active_regions.aux.vpd_nvram; |
| regions.npiv_config_0_1 = active_regions.aux.npiv_config_0_1; |
| regions.npiv_config_2_3 = active_regions.aux.npiv_config_2_3; |
| regions.nvme_params = active_regions.aux.nvme_params; |
| } |
| |
| ql_dbg(ql_dbg_user, vha, 0x70e1, |
| "%s(%lu): FW=%u BCFG=%u VPDNVR=%u NPIV01=%u NPIV02=%u NVME_PARAMS=%u\n", |
| __func__, vha->host_no, regions.global_image, |
| regions.board_config, regions.vpd_nvram, |
| regions.npiv_config_0_1, regions.npiv_config_2_3, regions.nvme_params); |
| |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, ®ions, sizeof(regions)); |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_OK; |
| bsg_reply->reply_payload_rcv_len = sizeof(regions); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job->reply_len = sizeof(struct fc_bsg_reply); |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return 0; |
| } |
| |
| static int |
| qla2x00_manage_host_stats(struct bsg_job *bsg_job) |
| { |
| scsi_qla_host_t *vha = shost_priv(fc_bsg_to_shost(bsg_job)); |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct ql_vnd_mng_host_stats_param *req_data; |
| struct ql_vnd_mng_host_stats_resp rsp_data; |
| u32 req_data_len; |
| int ret = 0; |
| |
| if (!vha->flags.online) { |
| ql_log(ql_log_warn, vha, 0x0000, "Host is not online.\n"); |
| return -EIO; |
| } |
| |
| req_data_len = bsg_job->request_payload.payload_len; |
| |
| if (req_data_len != sizeof(struct ql_vnd_mng_host_stats_param)) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data_len invalid.\n"); |
| return -EIO; |
| } |
| |
| req_data = kzalloc(sizeof(*req_data), GFP_KERNEL); |
| if (!req_data) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data memory allocation failure.\n"); |
| return -ENOMEM; |
| } |
| |
| /* Copy the request buffer in req_data */ |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, req_data, |
| req_data_len); |
| |
| switch (req_data->action) { |
| case QLA_STOP: |
| ret = qla2xxx_stop_stats(vha->host, req_data->stat_type); |
| break; |
| case QLA_START: |
| ret = qla2xxx_start_stats(vha->host, req_data->stat_type); |
| break; |
| case QLA_CLEAR: |
| ret = qla2xxx_reset_stats(vha->host, req_data->stat_type); |
| break; |
| default: |
| ql_log(ql_log_warn, vha, 0x0000, "Invalid action.\n"); |
| ret = -EIO; |
| break; |
| } |
| |
| kfree(req_data); |
| |
| /* Prepare response */ |
| rsp_data.status = ret; |
| bsg_job->reply_payload.payload_len = sizeof(struct ql_vnd_mng_host_stats_resp); |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_OK; |
| bsg_reply->reply_payload_rcv_len = |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, |
| &rsp_data, |
| sizeof(struct ql_vnd_mng_host_stats_resp)); |
| |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return ret; |
| } |
| |
| static int |
| qla2x00_get_host_stats(struct bsg_job *bsg_job) |
| { |
| scsi_qla_host_t *vha = shost_priv(fc_bsg_to_shost(bsg_job)); |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct ql_vnd_stats_param *req_data; |
| struct ql_vnd_host_stats_resp rsp_data; |
| u32 req_data_len; |
| int ret = 0; |
| u64 ini_entry_count = 0; |
| u64 entry_count = 0; |
| u64 tgt_num = 0; |
| u64 tmp_stat_type = 0; |
| u64 response_len = 0; |
| void *data; |
| |
| req_data_len = bsg_job->request_payload.payload_len; |
| |
| if (req_data_len != sizeof(struct ql_vnd_stats_param)) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data_len invalid.\n"); |
| return -EIO; |
| } |
| |
| req_data = kzalloc(sizeof(*req_data), GFP_KERNEL); |
| if (!req_data) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data memory allocation failure.\n"); |
| return -ENOMEM; |
| } |
| |
| /* Copy the request buffer in req_data */ |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, req_data, req_data_len); |
| |
| /* Copy stat type to work on it */ |
| tmp_stat_type = req_data->stat_type; |
| |
| if (tmp_stat_type & QLA2XX_TGT_SHT_LNK_DOWN) { |
| /* Num of tgts connected to this host */ |
| tgt_num = qla2x00_get_num_tgts(vha); |
| /* unset BIT_17 */ |
| tmp_stat_type &= ~(1 << 17); |
| } |
| |
| /* Total ini stats */ |
| ini_entry_count = qla2x00_count_set_bits(tmp_stat_type); |
| |
| /* Total number of entries */ |
| entry_count = ini_entry_count + tgt_num; |
| |
| response_len = sizeof(struct ql_vnd_host_stats_resp) + |
| (sizeof(struct ql_vnd_stat_entry) * entry_count); |
| |
| if (response_len > bsg_job->reply_payload.payload_len) { |
| rsp_data.status = EXT_STATUS_BUFFER_TOO_SMALL; |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_BUFFER_TOO_SMALL; |
| bsg_job->reply_payload.payload_len = sizeof(struct ql_vnd_mng_host_stats_resp); |
| |
| bsg_reply->reply_payload_rcv_len = |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, &rsp_data, |
| sizeof(struct ql_vnd_mng_host_stats_resp)); |
| |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| goto host_stat_out; |
| } |
| |
| data = kzalloc(response_len, GFP_KERNEL); |
| if (!data) { |
| ret = -ENOMEM; |
| goto host_stat_out; |
| } |
| |
| ret = qla2xxx_get_ini_stats(fc_bsg_to_shost(bsg_job), req_data->stat_type, |
| data, response_len); |
| |
| rsp_data.status = EXT_STATUS_OK; |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_OK; |
| |
| bsg_reply->reply_payload_rcv_len = sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, |
| data, response_len); |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| kfree(data); |
| host_stat_out: |
| kfree(req_data); |
| return ret; |
| } |
| |
| static struct fc_rport * |
| qla2xxx_find_rport(scsi_qla_host_t *vha, uint32_t tgt_num) |
| { |
| fc_port_t *fcport = NULL; |
| |
| list_for_each_entry(fcport, &vha->vp_fcports, list) { |
| if (fcport->rport->number == tgt_num) |
| return fcport->rport; |
| } |
| return NULL; |
| } |
| |
| static int |
| qla2x00_get_tgt_stats(struct bsg_job *bsg_job) |
| { |
| scsi_qla_host_t *vha = shost_priv(fc_bsg_to_shost(bsg_job)); |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct ql_vnd_tgt_stats_param *req_data; |
| u32 req_data_len; |
| int ret = 0; |
| u64 response_len = 0; |
| struct ql_vnd_tgt_stats_resp *data = NULL; |
| struct fc_rport *rport = NULL; |
| |
| if (!vha->flags.online) { |
| ql_log(ql_log_warn, vha, 0x0000, "Host is not online.\n"); |
| return -EIO; |
| } |
| |
| req_data_len = bsg_job->request_payload.payload_len; |
| |
| if (req_data_len != sizeof(struct ql_vnd_stat_entry)) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data_len invalid.\n"); |
| return -EIO; |
| } |
| |
| req_data = kzalloc(sizeof(*req_data), GFP_KERNEL); |
| if (!req_data) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data memory allocation failure.\n"); |
| return -ENOMEM; |
| } |
| |
| /* Copy the request buffer in req_data */ |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, |
| req_data, req_data_len); |
| |
| response_len = sizeof(struct ql_vnd_tgt_stats_resp) + |
| sizeof(struct ql_vnd_stat_entry); |
| |
| /* structure + size for one entry */ |
| data = kzalloc(response_len, GFP_KERNEL); |
| if (!data) { |
| kfree(req_data); |
| return -ENOMEM; |
| } |
| |
| if (response_len > bsg_job->reply_payload.payload_len) { |
| data->status = EXT_STATUS_BUFFER_TOO_SMALL; |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_BUFFER_TOO_SMALL; |
| bsg_job->reply_payload.payload_len = sizeof(struct ql_vnd_mng_host_stats_resp); |
| |
| bsg_reply->reply_payload_rcv_len = |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, data, |
| sizeof(struct ql_vnd_tgt_stats_resp)); |
| |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| goto tgt_stat_out; |
| } |
| |
| rport = qla2xxx_find_rport(vha, req_data->tgt_id); |
| if (!rport) { |
| ql_log(ql_log_warn, vha, 0x0000, "target %d not found.\n", req_data->tgt_id); |
| ret = EXT_STATUS_INVALID_PARAM; |
| data->status = EXT_STATUS_INVALID_PARAM; |
| goto reply; |
| } |
| |
| ret = qla2xxx_get_tgt_stats(fc_bsg_to_shost(bsg_job), req_data->stat_type, |
| rport, (void *)data, response_len); |
| |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_OK; |
| reply: |
| bsg_reply->reply_payload_rcv_len = |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, data, |
| response_len); |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| tgt_stat_out: |
| kfree(data); |
| kfree(req_data); |
| |
| return ret; |
| } |
| |
| static int |
| qla2x00_manage_host_port(struct bsg_job *bsg_job) |
| { |
| scsi_qla_host_t *vha = shost_priv(fc_bsg_to_shost(bsg_job)); |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| struct ql_vnd_mng_host_port_param *req_data; |
| struct ql_vnd_mng_host_port_resp rsp_data; |
| u32 req_data_len; |
| int ret = 0; |
| |
| req_data_len = bsg_job->request_payload.payload_len; |
| |
| if (req_data_len != sizeof(struct ql_vnd_mng_host_port_param)) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data_len invalid.\n"); |
| return -EIO; |
| } |
| |
| req_data = kzalloc(sizeof(*req_data), GFP_KERNEL); |
| if (!req_data) { |
| ql_log(ql_log_warn, vha, 0x0000, "req_data memory allocation failure.\n"); |
| return -ENOMEM; |
| } |
| |
| /* Copy the request buffer in req_data */ |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, req_data, req_data_len); |
| |
| switch (req_data->action) { |
| case QLA_ENABLE: |
| ret = qla2xxx_enable_port(vha->host); |
| break; |
| case QLA_DISABLE: |
| ret = qla2xxx_disable_port(vha->host); |
| break; |
| default: |
| ql_log(ql_log_warn, vha, 0x0000, "Invalid action.\n"); |
| ret = -EIO; |
| break; |
| } |
| |
| kfree(req_data); |
| |
| /* Prepare response */ |
| rsp_data.status = ret; |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_OK; |
| bsg_job->reply_payload.payload_len = sizeof(struct ql_vnd_mng_host_port_resp); |
| |
| bsg_reply->reply_payload_rcv_len = |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, &rsp_data, |
| sizeof(struct ql_vnd_mng_host_port_resp)); |
| bsg_reply->result = DID_OK; |
| bsg_job_done(bsg_job, bsg_reply->result, |
| bsg_reply->reply_payload_rcv_len); |
| |
| return ret; |
| } |
| |
| static int |
| qla2x00_process_vendor_specific(struct scsi_qla_host *vha, struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| |
| ql_dbg(ql_dbg_edif, vha, 0x911b, "%s FC_BSG_HST_VENDOR cmd[0]=0x%x\n", |
| __func__, bsg_request->rqst_data.h_vendor.vendor_cmd[0]); |
| |
| switch (bsg_request->rqst_data.h_vendor.vendor_cmd[0]) { |
| case QL_VND_LOOPBACK: |
| return qla2x00_process_loopback(bsg_job); |
| |
| case QL_VND_A84_RESET: |
| return qla84xx_reset(bsg_job); |
| |
| case QL_VND_A84_UPDATE_FW: |
| return qla84xx_updatefw(bsg_job); |
| |
| case QL_VND_A84_MGMT_CMD: |
| return qla84xx_mgmt_cmd(bsg_job); |
| |
| case QL_VND_IIDMA: |
| return qla24xx_iidma(bsg_job); |
| |
| case QL_VND_FCP_PRIO_CFG_CMD: |
| return qla24xx_proc_fcp_prio_cfg_cmd(bsg_job); |
| |
| case QL_VND_READ_FLASH: |
| return qla2x00_read_optrom(bsg_job); |
| |
| case QL_VND_UPDATE_FLASH: |
| return qla2x00_update_optrom(bsg_job); |
| |
| case QL_VND_SET_FRU_VERSION: |
| return qla2x00_update_fru_versions(bsg_job); |
| |
| case QL_VND_READ_FRU_STATUS: |
| return qla2x00_read_fru_status(bsg_job); |
| |
| case QL_VND_WRITE_FRU_STATUS: |
| return qla2x00_write_fru_status(bsg_job); |
| |
| case QL_VND_WRITE_I2C: |
| return qla2x00_write_i2c(bsg_job); |
| |
| case QL_VND_READ_I2C: |
| return qla2x00_read_i2c(bsg_job); |
| |
| case QL_VND_DIAG_IO_CMD: |
| return qla24xx_process_bidir_cmd(bsg_job); |
| |
| case QL_VND_FX00_MGMT_CMD: |
| return qlafx00_mgmt_cmd(bsg_job); |
| |
| case QL_VND_SERDES_OP: |
| return qla26xx_serdes_op(bsg_job); |
| |
| case QL_VND_SERDES_OP_EX: |
| return qla8044_serdes_op(bsg_job); |
| |
| case QL_VND_GET_FLASH_UPDATE_CAPS: |
| return qla27xx_get_flash_upd_cap(bsg_job); |
| |
| case QL_VND_SET_FLASH_UPDATE_CAPS: |
| return qla27xx_set_flash_upd_cap(bsg_job); |
| |
| case QL_VND_GET_BBCR_DATA: |
| return qla27xx_get_bbcr_data(bsg_job); |
| |
| case QL_VND_GET_PRIV_STATS: |
| case QL_VND_GET_PRIV_STATS_EX: |
| return qla2x00_get_priv_stats(bsg_job); |
| |
| case QL_VND_DPORT_DIAGNOSTICS: |
| return qla2x00_do_dport_diagnostics(bsg_job); |
| |
| case QL_VND_DPORT_DIAGNOSTICS_V2: |
| return qla2x00_do_dport_diagnostics_v2(bsg_job); |
| |
| case QL_VND_EDIF_MGMT: |
| return qla_edif_app_mgmt(bsg_job); |
| |
| case QL_VND_SS_GET_FLASH_IMAGE_STATUS: |
| return qla2x00_get_flash_image_status(bsg_job); |
| |
| case QL_VND_MANAGE_HOST_STATS: |
| return qla2x00_manage_host_stats(bsg_job); |
| |
| case QL_VND_GET_HOST_STATS: |
| return qla2x00_get_host_stats(bsg_job); |
| |
| case QL_VND_GET_TGT_STATS: |
| return qla2x00_get_tgt_stats(bsg_job); |
| |
| case QL_VND_MANAGE_HOST_PORT: |
| return qla2x00_manage_host_port(bsg_job); |
| |
| case QL_VND_MBX_PASSTHRU: |
| return qla2x00_mailbox_passthru(bsg_job); |
| |
| default: |
| return -ENOSYS; |
| } |
| } |
| |
| int |
| qla24xx_bsg_request(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_request *bsg_request = bsg_job->request; |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| int ret = -EINVAL; |
| struct fc_rport *rport; |
| struct Scsi_Host *host; |
| scsi_qla_host_t *vha; |
| |
| /* In case no data transferred. */ |
| bsg_reply->reply_payload_rcv_len = 0; |
| |
| if (bsg_request->msgcode == FC_BSG_RPT_ELS) { |
| rport = fc_bsg_to_rport(bsg_job); |
| if (!rport) |
| return ret; |
| host = rport_to_shost(rport); |
| vha = shost_priv(host); |
| } else { |
| host = fc_bsg_to_shost(bsg_job); |
| vha = shost_priv(host); |
| } |
| |
| /* Disable port will bring down the chip, allow enable command */ |
| if (bsg_request->rqst_data.h_vendor.vendor_cmd[0] == QL_VND_MANAGE_HOST_PORT || |
| bsg_request->rqst_data.h_vendor.vendor_cmd[0] == QL_VND_GET_HOST_STATS) |
| goto skip_chip_chk; |
| |
| if (vha->hw->flags.port_isolated) { |
| bsg_reply->result = DID_ERROR; |
| /* operation not permitted */ |
| return -EPERM; |
| } |
| |
| if (qla2x00_chip_is_down(vha)) { |
| ql_dbg(ql_dbg_user, vha, 0x709f, |
| "BSG: ISP abort active/needed -- cmd=%d.\n", |
| bsg_request->msgcode); |
| SET_DID_STATUS(bsg_reply->result, DID_ERROR); |
| return -EBUSY; |
| } |
| |
| if (test_bit(PFLG_DRIVER_REMOVING, &vha->pci_flags)) { |
| SET_DID_STATUS(bsg_reply->result, DID_ERROR); |
| return -EIO; |
| } |
| |
| skip_chip_chk: |
| ql_dbg(ql_dbg_user + ql_dbg_verbose, vha, 0x7000, |
| "Entered %s msgcode=0x%x. bsg ptr %px\n", |
| __func__, bsg_request->msgcode, bsg_job); |
| |
| switch (bsg_request->msgcode) { |
| case FC_BSG_RPT_ELS: |
| case FC_BSG_HST_ELS_NOLOGIN: |
| ret = qla2x00_process_els(bsg_job); |
| break; |
| case FC_BSG_HST_CT: |
| ret = qla2x00_process_ct(bsg_job); |
| break; |
| case FC_BSG_HST_VENDOR: |
| ret = qla2x00_process_vendor_specific(vha, bsg_job); |
| break; |
| case FC_BSG_HST_ADD_RPORT: |
| case FC_BSG_HST_DEL_RPORT: |
| case FC_BSG_RPT_CT: |
| default: |
| ql_log(ql_log_warn, vha, 0x705a, "Unsupported BSG request.\n"); |
| break; |
| } |
| |
| ql_dbg(ql_dbg_user + ql_dbg_verbose, vha, 0x7000, |
| "%s done with return %x\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| int |
| qla24xx_bsg_timeout(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| scsi_qla_host_t *vha = shost_priv(fc_bsg_to_shost(bsg_job)); |
| struct qla_hw_data *ha = vha->hw; |
| srb_t *sp; |
| int cnt, que; |
| unsigned long flags; |
| struct req_que *req; |
| |
| ql_log(ql_log_info, vha, 0x708b, "%s CMD timeout. bsg ptr %p.\n", |
| __func__, bsg_job); |
| |
| if (qla2x00_isp_reg_stat(ha)) { |
| ql_log(ql_log_info, vha, 0x9007, |
| "PCI/Register disconnect.\n"); |
| qla_pci_set_eeh_busy(vha); |
| } |
| |
| /* find the bsg job from the active list of commands */ |
| spin_lock_irqsave(&ha->hardware_lock, flags); |
| for (que = 0; que < ha->max_req_queues; que++) { |
| req = ha->req_q_map[que]; |
| if (!req) |
| continue; |
| |
| for (cnt = 1; cnt < req->num_outstanding_cmds; cnt++) { |
| sp = req->outstanding_cmds[cnt]; |
| if (sp && |
| (sp->type == SRB_CT_CMD || |
| sp->type == SRB_ELS_CMD_HST || |
| sp->type == SRB_ELS_CMD_HST_NOLOGIN || |
| sp->type == SRB_FXIOCB_BCMD) && |
| sp->u.bsg_job == bsg_job) { |
| req->outstanding_cmds[cnt] = NULL; |
| spin_unlock_irqrestore(&ha->hardware_lock, flags); |
| |
| if (!ha->flags.eeh_busy && ha->isp_ops->abort_command(sp)) { |
| ql_log(ql_log_warn, vha, 0x7089, |
| "mbx abort_command failed.\n"); |
| bsg_reply->result = -EIO; |
| } else { |
| ql_dbg(ql_dbg_user, vha, 0x708a, |
| "mbx abort_command success.\n"); |
| bsg_reply->result = 0; |
| } |
| spin_lock_irqsave(&ha->hardware_lock, flags); |
| goto done; |
| |
| } |
| } |
| } |
| spin_unlock_irqrestore(&ha->hardware_lock, flags); |
| ql_log(ql_log_info, vha, 0x708b, "SRB not found to abort.\n"); |
| bsg_reply->result = -ENXIO; |
| return 0; |
| |
| done: |
| spin_unlock_irqrestore(&ha->hardware_lock, flags); |
| /* ref: INIT */ |
| kref_put(&sp->cmd_kref, qla2x00_sp_release); |
| return 0; |
| } |
| |
| int qla2x00_mailbox_passthru(struct bsg_job *bsg_job) |
| { |
| struct fc_bsg_reply *bsg_reply = bsg_job->reply; |
| scsi_qla_host_t *vha = shost_priv(fc_bsg_to_shost(bsg_job)); |
| int ret = -EINVAL; |
| int ptsize = sizeof(struct qla_mbx_passthru); |
| struct qla_mbx_passthru *req_data = NULL; |
| uint32_t req_data_len; |
| |
| req_data_len = bsg_job->request_payload.payload_len; |
| if (req_data_len != ptsize) { |
| ql_log(ql_log_warn, vha, 0xf0a3, "req_data_len invalid.\n"); |
| return -EIO; |
| } |
| req_data = kzalloc(ptsize, GFP_KERNEL); |
| if (!req_data) { |
| ql_log(ql_log_warn, vha, 0xf0a4, |
| "req_data memory allocation failure.\n"); |
| return -ENOMEM; |
| } |
| |
| /* Copy the request buffer in req_data */ |
| sg_copy_to_buffer(bsg_job->request_payload.sg_list, |
| bsg_job->request_payload.sg_cnt, req_data, ptsize); |
| ret = qla_mailbox_passthru(vha, req_data->mbx_in, req_data->mbx_out); |
| |
| /* Copy the req_data in request buffer */ |
| sg_copy_from_buffer(bsg_job->reply_payload.sg_list, |
| bsg_job->reply_payload.sg_cnt, req_data, ptsize); |
| |
| bsg_reply->reply_payload_rcv_len = ptsize; |
| if (ret == QLA_SUCCESS) |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_OK; |
| else |
| bsg_reply->reply_data.vendor_reply.vendor_rsp[0] = EXT_STATUS_ERR; |
| |
| bsg_job->reply_len = sizeof(*bsg_job->reply); |
| bsg_reply->result = DID_OK << 16; |
| bsg_job_done(bsg_job, bsg_reply->result, bsg_reply->reply_payload_rcv_len); |
| |
| kfree(req_data); |
| |
| return ret; |
| } |