diff options
Diffstat (limited to 'drivers/scsi/lpfc/lpfc_bsg.c')
-rw-r--r-- | drivers/scsi/lpfc/lpfc_bsg.c | 5690 |
1 files changed, 5690 insertions, 0 deletions
diff --git a/drivers/scsi/lpfc/lpfc_bsg.c b/drivers/scsi/lpfc/lpfc_bsg.c new file mode 100644 index 000000000..b54fafb48 --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_bsg.c @@ -0,0 +1,5690 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2017-2022 Broadcom. All Rights Reserved. The term * + * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. * + * Copyright (C) 2009-2015 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.broadcom.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#include <linux/interrupt.h> +#include <linux/mempool.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/bsg-lib.h> +#include <linux/vmalloc.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_transport_fc.h> +#include <scsi/scsi_bsg_fc.h> +#include <scsi/fc/fc_fs.h> + +#include "lpfc_hw4.h" +#include "lpfc_hw.h" +#include "lpfc_sli.h" +#include "lpfc_sli4.h" +#include "lpfc_nl.h" +#include "lpfc_bsg.h" +#include "lpfc_disc.h" +#include "lpfc_scsi.h" +#include "lpfc.h" +#include "lpfc_logmsg.h" +#include "lpfc_crtn.h" +#include "lpfc_debugfs.h" +#include "lpfc_vport.h" +#include "lpfc_version.h" + +struct lpfc_bsg_event { + struct list_head node; + struct kref kref; + wait_queue_head_t wq; + + /* Event type and waiter identifiers */ + uint32_t type_mask; + uint32_t req_id; + uint32_t reg_id; + + /* next two flags are here for the auto-delete logic */ + unsigned long wait_time_stamp; + int waiting; + + /* seen and not seen events */ + struct list_head events_to_get; + struct list_head events_to_see; + + /* driver data associated with the job */ + void *dd_data; +}; + +struct lpfc_bsg_iocb { + struct lpfc_iocbq *cmdiocbq; + struct lpfc_dmabuf *rmp; + struct lpfc_nodelist *ndlp; +}; + +struct lpfc_bsg_mbox { + LPFC_MBOXQ_t *pmboxq; + MAILBOX_t *mb; + struct lpfc_dmabuf *dmabuffers; /* for BIU diags */ + uint8_t *ext; /* extended mailbox data */ + uint32_t mbOffset; /* from app */ + uint32_t inExtWLen; /* from app */ + uint32_t outExtWLen; /* from app */ +}; + +#define TYPE_EVT 1 +#define TYPE_IOCB 2 +#define TYPE_MBOX 3 +struct bsg_job_data { + uint32_t type; + struct bsg_job *set_job; /* job waiting for this iocb to finish */ + union { + struct lpfc_bsg_event *evt; + struct lpfc_bsg_iocb iocb; + struct lpfc_bsg_mbox mbox; + } context_un; +}; + +struct event_data { + struct list_head node; + uint32_t type; + uint32_t immed_dat; + void *data; + uint32_t len; +}; + +#define BUF_SZ_4K 4096 +#define SLI_CT_ELX_LOOPBACK 0x10 + +enum ELX_LOOPBACK_CMD { + ELX_LOOPBACK_XRI_SETUP, + ELX_LOOPBACK_DATA, +}; + +#define ELX_LOOPBACK_HEADER_SZ \ + (size_t)(&((struct lpfc_sli_ct_request *)NULL)->un) + +struct lpfc_dmabufext { + struct lpfc_dmabuf dma; + uint32_t size; + uint32_t flag; +}; + +static void +lpfc_free_bsg_buffers(struct lpfc_hba *phba, struct lpfc_dmabuf *mlist) +{ + struct lpfc_dmabuf *mlast, *next_mlast; + + if (mlist) { + list_for_each_entry_safe(mlast, next_mlast, &mlist->list, + list) { + lpfc_mbuf_free(phba, mlast->virt, mlast->phys); + list_del(&mlast->list); + kfree(mlast); + } + lpfc_mbuf_free(phba, mlist->virt, mlist->phys); + kfree(mlist); + } + return; +} + +static struct lpfc_dmabuf * +lpfc_alloc_bsg_buffers(struct lpfc_hba *phba, unsigned int size, + int outbound_buffers, struct ulp_bde64 *bpl, + int *bpl_entries) +{ + struct lpfc_dmabuf *mlist = NULL; + struct lpfc_dmabuf *mp; + unsigned int bytes_left = size; + + /* Verify we can support the size specified */ + if (!size || (size > (*bpl_entries * LPFC_BPL_SIZE))) + return NULL; + + /* Determine the number of dma buffers to allocate */ + *bpl_entries = (size % LPFC_BPL_SIZE ? size/LPFC_BPL_SIZE + 1 : + size/LPFC_BPL_SIZE); + + /* Allocate dma buffer and place in BPL passed */ + while (bytes_left) { + /* Allocate dma buffer */ + mp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!mp) { + if (mlist) + lpfc_free_bsg_buffers(phba, mlist); + return NULL; + } + + INIT_LIST_HEAD(&mp->list); + mp->virt = lpfc_mbuf_alloc(phba, MEM_PRI, &(mp->phys)); + + if (!mp->virt) { + kfree(mp); + if (mlist) + lpfc_free_bsg_buffers(phba, mlist); + return NULL; + } + + /* Queue it to a linked list */ + if (!mlist) + mlist = mp; + else + list_add_tail(&mp->list, &mlist->list); + + /* Add buffer to buffer pointer list */ + if (outbound_buffers) + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; + else + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; + bpl->addrLow = le32_to_cpu(putPaddrLow(mp->phys)); + bpl->addrHigh = le32_to_cpu(putPaddrHigh(mp->phys)); + bpl->tus.f.bdeSize = (uint16_t) + (bytes_left >= LPFC_BPL_SIZE ? LPFC_BPL_SIZE : + bytes_left); + bytes_left -= bpl->tus.f.bdeSize; + bpl->tus.w = le32_to_cpu(bpl->tus.w); + bpl++; + } + return mlist; +} + +static unsigned int +lpfc_bsg_copy_data(struct lpfc_dmabuf *dma_buffers, + struct bsg_buffer *bsg_buffers, + unsigned int bytes_to_transfer, int to_buffers) +{ + + struct lpfc_dmabuf *mp; + unsigned int transfer_bytes, bytes_copied = 0; + unsigned int sg_offset, dma_offset; + unsigned char *dma_address, *sg_address; + LIST_HEAD(temp_list); + struct sg_mapping_iter miter; + unsigned long flags; + unsigned int sg_flags = SG_MITER_ATOMIC; + bool sg_valid; + + list_splice_init(&dma_buffers->list, &temp_list); + list_add(&dma_buffers->list, &temp_list); + sg_offset = 0; + if (to_buffers) + sg_flags |= SG_MITER_FROM_SG; + else + sg_flags |= SG_MITER_TO_SG; + sg_miter_start(&miter, bsg_buffers->sg_list, bsg_buffers->sg_cnt, + sg_flags); + local_irq_save(flags); + sg_valid = sg_miter_next(&miter); + list_for_each_entry(mp, &temp_list, list) { + dma_offset = 0; + while (bytes_to_transfer && sg_valid && + (dma_offset < LPFC_BPL_SIZE)) { + dma_address = mp->virt + dma_offset; + if (sg_offset) { + /* Continue previous partial transfer of sg */ + sg_address = miter.addr + sg_offset; + transfer_bytes = miter.length - sg_offset; + } else { + sg_address = miter.addr; + transfer_bytes = miter.length; + } + if (bytes_to_transfer < transfer_bytes) + transfer_bytes = bytes_to_transfer; + if (transfer_bytes > (LPFC_BPL_SIZE - dma_offset)) + transfer_bytes = LPFC_BPL_SIZE - dma_offset; + if (to_buffers) + memcpy(dma_address, sg_address, transfer_bytes); + else + memcpy(sg_address, dma_address, transfer_bytes); + dma_offset += transfer_bytes; + sg_offset += transfer_bytes; + bytes_to_transfer -= transfer_bytes; + bytes_copied += transfer_bytes; + if (sg_offset >= miter.length) { + sg_offset = 0; + sg_valid = sg_miter_next(&miter); + } + } + } + sg_miter_stop(&miter); + local_irq_restore(flags); + list_del_init(&dma_buffers->list); + list_splice(&temp_list, &dma_buffers->list); + return bytes_copied; +} + +/** + * lpfc_bsg_send_mgmt_cmd_cmp - lpfc_bsg_send_mgmt_cmd's completion handler + * @phba: Pointer to HBA context object. + * @cmdiocbq: Pointer to command iocb. + * @rspiocbq: Pointer to response iocb. + * + * This function is the completion handler for iocbs issued using + * lpfc_bsg_send_mgmt_cmd function. This function is called by the + * ring event handler function without any lock held. This function + * can be called from both worker thread context and interrupt + * context. This function also can be called from another thread which + * cleans up the SLI layer objects. + * This function copies the contents of the response iocb to the + * response iocb memory object provided by the caller of + * lpfc_sli_issue_iocb_wait and then wakes up the thread which + * sleeps for the iocb completion. + **/ +static void +lpfc_bsg_send_mgmt_cmd_cmp(struct lpfc_hba *phba, + struct lpfc_iocbq *cmdiocbq, + struct lpfc_iocbq *rspiocbq) +{ + struct bsg_job_data *dd_data; + struct bsg_job *job; + struct fc_bsg_reply *bsg_reply; + struct lpfc_dmabuf *bmp, *cmp, *rmp; + struct lpfc_nodelist *ndlp; + struct lpfc_bsg_iocb *iocb; + unsigned long flags; + int rc = 0; + u32 ulp_status, ulp_word4, total_data_placed; + + dd_data = cmdiocbq->context_un.dd_data; + + /* Determine if job has been aborted */ + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + bsg_reply = job->reply; + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Close the timeout handler abort window */ + spin_lock_irqsave(&phba->hbalock, flags); + cmdiocbq->cmd_flag &= ~LPFC_IO_CMD_OUTSTANDING; + spin_unlock_irqrestore(&phba->hbalock, flags); + + iocb = &dd_data->context_un.iocb; + ndlp = iocb->cmdiocbq->ndlp; + rmp = iocb->rmp; + cmp = cmdiocbq->cmd_dmabuf; + bmp = cmdiocbq->bpl_dmabuf; + ulp_status = get_job_ulpstatus(phba, rspiocbq); + ulp_word4 = get_job_word4(phba, rspiocbq); + total_data_placed = get_job_data_placed(phba, rspiocbq); + + /* Copy the completed data or set the error status */ + + if (job) { + if (ulp_status) { + if (ulp_status == IOSTAT_LOCAL_REJECT) { + switch (ulp_word4 & IOERR_PARAM_MASK) { + case IOERR_SEQUENCE_TIMEOUT: + rc = -ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = -EFAULT; + break; + default: + rc = -EACCES; + break; + } + } else { + rc = -EACCES; + } + } else { + bsg_reply->reply_payload_rcv_len = + lpfc_bsg_copy_data(rmp, &job->reply_payload, + total_data_placed, 0); + } + } + + lpfc_free_bsg_buffers(phba, cmp); + lpfc_free_bsg_buffers(phba, rmp); + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); + kfree(bmp); + lpfc_nlp_put(ndlp); + lpfc_sli_release_iocbq(phba, cmdiocbq); + kfree(dd_data); + + /* Complete the job if the job is still active */ + + if (job) { + bsg_reply->result = rc; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + } + return; +} + +/** + * lpfc_bsg_send_mgmt_cmd - send a CT command from a bsg request + * @job: fc_bsg_job to handle + **/ +static int +lpfc_bsg_send_mgmt_cmd(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_rport_data *rdata = fc_bsg_to_rport(job)->dd_data; + struct lpfc_hba *phba = vport->phba; + struct lpfc_nodelist *ndlp = rdata->pnode; + struct fc_bsg_reply *bsg_reply = job->reply; + struct ulp_bde64 *bpl = NULL; + struct lpfc_iocbq *cmdiocbq = NULL; + struct lpfc_dmabuf *bmp = NULL, *cmp = NULL, *rmp = NULL; + int request_nseg, reply_nseg; + u32 num_entry; + struct bsg_job_data *dd_data; + unsigned long flags; + uint32_t creg_val; + int rc = 0; + int iocb_stat; + u16 ulp_context; + + /* in case no data is transferred */ + bsg_reply->reply_payload_rcv_len = 0; + + if (ndlp->nlp_flag & NLP_ELS_SND_MASK) + return -ENODEV; + + /* allocate our bsg tracking structure */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (!dd_data) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2733 Failed allocation of dd_data\n"); + rc = -ENOMEM; + goto no_dd_data; + } + + cmdiocbq = lpfc_sli_get_iocbq(phba); + if (!cmdiocbq) { + rc = -ENOMEM; + goto free_dd; + } + + bmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!bmp) { + rc = -ENOMEM; + goto free_cmdiocbq; + } + bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); + if (!bmp->virt) { + rc = -ENOMEM; + goto free_bmp; + } + + INIT_LIST_HEAD(&bmp->list); + + bpl = (struct ulp_bde64 *) bmp->virt; + request_nseg = LPFC_BPL_SIZE/sizeof(struct ulp_bde64); + cmp = lpfc_alloc_bsg_buffers(phba, job->request_payload.payload_len, + 1, bpl, &request_nseg); + if (!cmp) { + rc = -ENOMEM; + goto free_bmp; + } + lpfc_bsg_copy_data(cmp, &job->request_payload, + job->request_payload.payload_len, 1); + + bpl += request_nseg; + reply_nseg = LPFC_BPL_SIZE/sizeof(struct ulp_bde64) - request_nseg; + rmp = lpfc_alloc_bsg_buffers(phba, job->reply_payload.payload_len, 0, + bpl, &reply_nseg); + if (!rmp) { + rc = -ENOMEM; + goto free_cmp; + } + + num_entry = request_nseg + reply_nseg; + + if (phba->sli_rev == LPFC_SLI_REV4) + ulp_context = phba->sli4_hba.rpi_ids[ndlp->nlp_rpi]; + else + ulp_context = ndlp->nlp_rpi; + + lpfc_sli_prep_gen_req(phba, cmdiocbq, bmp, ulp_context, num_entry, + phba->fc_ratov * 2); + + cmdiocbq->num_bdes = num_entry; + cmdiocbq->vport = phba->pport; + cmdiocbq->cmd_dmabuf = cmp; + cmdiocbq->bpl_dmabuf = bmp; + cmdiocbq->cmd_flag |= LPFC_IO_LIBDFC; + + cmdiocbq->cmd_cmpl = lpfc_bsg_send_mgmt_cmd_cmp; + cmdiocbq->context_un.dd_data = dd_data; + + dd_data->type = TYPE_IOCB; + dd_data->set_job = job; + dd_data->context_un.iocb.cmdiocbq = cmdiocbq; + dd_data->context_un.iocb.rmp = rmp; + job->dd_data = dd_data; + + if (phba->cfg_poll & DISABLE_FCP_RING_INT) { + if (lpfc_readl(phba->HCregaddr, &creg_val)) { + rc = -EIO ; + goto free_rmp; + } + creg_val |= (HC_R0INT_ENA << LPFC_FCP_RING); + writel(creg_val, phba->HCregaddr); + readl(phba->HCregaddr); /* flush */ + } + + cmdiocbq->ndlp = lpfc_nlp_get(ndlp); + if (!cmdiocbq->ndlp) { + rc = -ENODEV; + goto free_rmp; + } + + iocb_stat = lpfc_sli_issue_iocb(phba, LPFC_ELS_RING, cmdiocbq, 0); + if (iocb_stat == IOCB_SUCCESS) { + spin_lock_irqsave(&phba->hbalock, flags); + /* make sure the I/O had not been completed yet */ + if (cmdiocbq->cmd_flag & LPFC_IO_LIBDFC) { + /* open up abort window to timeout handler */ + cmdiocbq->cmd_flag |= LPFC_IO_CMD_OUTSTANDING; + } + spin_unlock_irqrestore(&phba->hbalock, flags); + return 0; /* done for now */ + } else if (iocb_stat == IOCB_BUSY) { + rc = -EAGAIN; + } else { + rc = -EIO; + } + + /* iocb failed so cleanup */ + lpfc_nlp_put(ndlp); + +free_rmp: + lpfc_free_bsg_buffers(phba, rmp); +free_cmp: + lpfc_free_bsg_buffers(phba, cmp); +free_bmp: + if (bmp->virt) + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); + kfree(bmp); +free_cmdiocbq: + lpfc_sli_release_iocbq(phba, cmdiocbq); +free_dd: + kfree(dd_data); +no_dd_data: + /* make error code available to userspace */ + bsg_reply->result = rc; + job->dd_data = NULL; + return rc; +} + +/** + * lpfc_bsg_rport_els_cmp - lpfc_bsg_rport_els's completion handler + * @phba: Pointer to HBA context object. + * @cmdiocbq: Pointer to command iocb. + * @rspiocbq: Pointer to response iocb. + * + * This function is the completion handler for iocbs issued using + * lpfc_bsg_rport_els_cmp function. This function is called by the + * ring event handler function without any lock held. This function + * can be called from both worker thread context and interrupt + * context. This function also can be called from other thread which + * cleans up the SLI layer objects. + * This function copies the contents of the response iocb to the + * response iocb memory object provided by the caller of + * lpfc_sli_issue_iocb_wait and then wakes up the thread which + * sleeps for the iocb completion. + **/ +static void +lpfc_bsg_rport_els_cmp(struct lpfc_hba *phba, + struct lpfc_iocbq *cmdiocbq, + struct lpfc_iocbq *rspiocbq) +{ + struct bsg_job_data *dd_data; + struct bsg_job *job; + struct fc_bsg_reply *bsg_reply; + struct lpfc_nodelist *ndlp; + struct lpfc_dmabuf *pcmd = NULL, *prsp = NULL; + struct fc_bsg_ctels_reply *els_reply; + uint8_t *rjt_data; + unsigned long flags; + unsigned int rsp_size; + int rc = 0; + u32 ulp_status, ulp_word4, total_data_placed; + + dd_data = cmdiocbq->context_un.dd_data; + ndlp = dd_data->context_un.iocb.ndlp; + cmdiocbq->ndlp = ndlp; + + /* Determine if job has been aborted */ + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + bsg_reply = job->reply; + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Close the timeout handler abort window */ + spin_lock_irqsave(&phba->hbalock, flags); + cmdiocbq->cmd_flag &= ~LPFC_IO_CMD_OUTSTANDING; + spin_unlock_irqrestore(&phba->hbalock, flags); + + ulp_status = get_job_ulpstatus(phba, rspiocbq); + ulp_word4 = get_job_word4(phba, rspiocbq); + total_data_placed = get_job_data_placed(phba, rspiocbq); + pcmd = cmdiocbq->cmd_dmabuf; + prsp = (struct lpfc_dmabuf *)pcmd->list.next; + + /* Copy the completed job data or determine the job status if job is + * still active + */ + + if (job) { + if (ulp_status == IOSTAT_SUCCESS) { + rsp_size = total_data_placed; + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + prsp->virt, + rsp_size); + } else if (ulp_status == IOSTAT_LS_RJT) { + bsg_reply->reply_payload_rcv_len = + sizeof(struct fc_bsg_ctels_reply); + /* LS_RJT data returned in word 4 */ + rjt_data = (uint8_t *)&ulp_word4; + els_reply = &bsg_reply->reply_data.ctels_reply; + els_reply->status = FC_CTELS_STATUS_REJECT; + els_reply->rjt_data.action = rjt_data[3]; + els_reply->rjt_data.reason_code = rjt_data[2]; + els_reply->rjt_data.reason_explanation = rjt_data[1]; + els_reply->rjt_data.vendor_unique = rjt_data[0]; + } else if (ulp_status == IOSTAT_LOCAL_REJECT && + (ulp_word4 & IOERR_PARAM_MASK) == + IOERR_SEQUENCE_TIMEOUT) { + rc = -ETIMEDOUT; + } else { + rc = -EIO; + } + } + + lpfc_els_free_iocb(phba, cmdiocbq); + + lpfc_nlp_put(ndlp); + kfree(dd_data); + + /* Complete the job if the job is still active */ + + if (job) { + bsg_reply->result = rc; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + } + return; +} + +/** + * lpfc_bsg_rport_els - send an ELS command from a bsg request + * @job: fc_bsg_job to handle + **/ +static int +lpfc_bsg_rport_els(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_hba *phba = vport->phba; + struct lpfc_rport_data *rdata = fc_bsg_to_rport(job)->dd_data; + struct lpfc_nodelist *ndlp = rdata->pnode; + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + uint32_t elscmd; + uint32_t cmdsize; + struct lpfc_iocbq *cmdiocbq; + uint16_t rpi = 0; + struct bsg_job_data *dd_data; + unsigned long flags; + uint32_t creg_val; + int rc = 0; + + /* in case no data is transferred */ + bsg_reply->reply_payload_rcv_len = 0; + + /* verify the els command is not greater than the + * maximum ELS transfer size. + */ + + if (job->request_payload.payload_len > FCELSSIZE) { + rc = -EINVAL; + goto no_dd_data; + } + + /* allocate our bsg tracking structure */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (!dd_data) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2735 Failed allocation of dd_data\n"); + rc = -ENOMEM; + goto no_dd_data; + } + + elscmd = bsg_request->rqst_data.r_els.els_code; + cmdsize = job->request_payload.payload_len; + + if (!lpfc_nlp_get(ndlp)) { + rc = -ENODEV; + goto free_dd_data; + } + + /* We will use the allocated dma buffers by prep els iocb for command + * and response to ensure if the job times out and the request is freed, + * we won't be dma into memory that is no longer allocated to for the + * request. + */ + cmdiocbq = lpfc_prep_els_iocb(vport, 1, cmdsize, 0, ndlp, + ndlp->nlp_DID, elscmd); + if (!cmdiocbq) { + rc = -EIO; + goto release_ndlp; + } + + /* Transfer the request payload to allocated command dma buffer */ + sg_copy_to_buffer(job->request_payload.sg_list, + job->request_payload.sg_cnt, + cmdiocbq->cmd_dmabuf->virt, + cmdsize); + + rpi = ndlp->nlp_rpi; + + if (phba->sli_rev == LPFC_SLI_REV4) + bf_set(wqe_ctxt_tag, &cmdiocbq->wqe.generic.wqe_com, + phba->sli4_hba.rpi_ids[rpi]); + else + cmdiocbq->iocb.ulpContext = rpi; + cmdiocbq->cmd_flag |= LPFC_IO_LIBDFC; + cmdiocbq->context_un.dd_data = dd_data; + cmdiocbq->ndlp = ndlp; + cmdiocbq->cmd_cmpl = lpfc_bsg_rport_els_cmp; + dd_data->type = TYPE_IOCB; + dd_data->set_job = job; + dd_data->context_un.iocb.cmdiocbq = cmdiocbq; + dd_data->context_un.iocb.ndlp = ndlp; + dd_data->context_un.iocb.rmp = NULL; + job->dd_data = dd_data; + + if (phba->cfg_poll & DISABLE_FCP_RING_INT) { + if (lpfc_readl(phba->HCregaddr, &creg_val)) { + rc = -EIO; + goto linkdown_err; + } + creg_val |= (HC_R0INT_ENA << LPFC_FCP_RING); + writel(creg_val, phba->HCregaddr); + readl(phba->HCregaddr); /* flush */ + } + + rc = lpfc_sli_issue_iocb(phba, LPFC_ELS_RING, cmdiocbq, 0); + if (rc == IOCB_SUCCESS) { + spin_lock_irqsave(&phba->hbalock, flags); + /* make sure the I/O had not been completed/released */ + if (cmdiocbq->cmd_flag & LPFC_IO_LIBDFC) { + /* open up abort window to timeout handler */ + cmdiocbq->cmd_flag |= LPFC_IO_CMD_OUTSTANDING; + } + spin_unlock_irqrestore(&phba->hbalock, flags); + return 0; /* done for now */ + } else if (rc == IOCB_BUSY) { + rc = -EAGAIN; + } else { + rc = -EIO; + } + + /* I/O issue failed. Cleanup resources. */ + +linkdown_err: + lpfc_els_free_iocb(phba, cmdiocbq); + +release_ndlp: + lpfc_nlp_put(ndlp); + +free_dd_data: + kfree(dd_data); + +no_dd_data: + /* make error code available to userspace */ + bsg_reply->result = rc; + job->dd_data = NULL; + return rc; +} + +/** + * lpfc_bsg_event_free - frees an allocated event structure + * @kref: Pointer to a kref. + * + * Called from kref_put. Back cast the kref into an event structure address. + * Free any events to get, delete associated nodes, free any events to see, + * free any data then free the event itself. + **/ +static void +lpfc_bsg_event_free(struct kref *kref) +{ + struct lpfc_bsg_event *evt = container_of(kref, struct lpfc_bsg_event, + kref); + struct event_data *ed; + + list_del(&evt->node); + + while (!list_empty(&evt->events_to_get)) { + ed = list_entry(evt->events_to_get.next, typeof(*ed), node); + list_del(&ed->node); + kfree(ed->data); + kfree(ed); + } + + while (!list_empty(&evt->events_to_see)) { + ed = list_entry(evt->events_to_see.next, typeof(*ed), node); + list_del(&ed->node); + kfree(ed->data); + kfree(ed); + } + + kfree(evt->dd_data); + kfree(evt); +} + +/** + * lpfc_bsg_event_ref - increments the kref for an event + * @evt: Pointer to an event structure. + **/ +static inline void +lpfc_bsg_event_ref(struct lpfc_bsg_event *evt) +{ + kref_get(&evt->kref); +} + +/** + * lpfc_bsg_event_unref - Uses kref_put to free an event structure + * @evt: Pointer to an event structure. + **/ +static inline void +lpfc_bsg_event_unref(struct lpfc_bsg_event *evt) +{ + kref_put(&evt->kref, lpfc_bsg_event_free); +} + +/** + * lpfc_bsg_event_new - allocate and initialize a event structure + * @ev_mask: Mask of events. + * @ev_reg_id: Event reg id. + * @ev_req_id: Event request id. + **/ +static struct lpfc_bsg_event * +lpfc_bsg_event_new(uint32_t ev_mask, int ev_reg_id, uint32_t ev_req_id) +{ + struct lpfc_bsg_event *evt = kzalloc(sizeof(*evt), GFP_KERNEL); + + if (!evt) + return NULL; + + INIT_LIST_HEAD(&evt->events_to_get); + INIT_LIST_HEAD(&evt->events_to_see); + evt->type_mask = ev_mask; + evt->req_id = ev_req_id; + evt->reg_id = ev_reg_id; + evt->wait_time_stamp = jiffies; + evt->dd_data = NULL; + init_waitqueue_head(&evt->wq); + kref_init(&evt->kref); + return evt; +} + +/** + * diag_cmd_data_free - Frees an lpfc dma buffer extension + * @phba: Pointer to HBA context object. + * @mlist: Pointer to an lpfc dma buffer extension. + **/ +static int +diag_cmd_data_free(struct lpfc_hba *phba, struct lpfc_dmabufext *mlist) +{ + struct lpfc_dmabufext *mlast; + struct pci_dev *pcidev; + struct list_head head, *curr, *next; + + if ((!mlist) || (!lpfc_is_link_up(phba) && + (phba->link_flag & LS_LOOPBACK_MODE))) { + return 0; + } + + pcidev = phba->pcidev; + list_add_tail(&head, &mlist->dma.list); + + list_for_each_safe(curr, next, &head) { + mlast = list_entry(curr, struct lpfc_dmabufext , dma.list); + if (mlast->dma.virt) + dma_free_coherent(&pcidev->dev, + mlast->size, + mlast->dma.virt, + mlast->dma.phys); + kfree(mlast); + } + return 0; +} + +/* + * lpfc_bsg_ct_unsol_event - process an unsolicited CT command + * + * This function is called when an unsolicited CT command is received. It + * forwards the event to any processes registered to receive CT events. + **/ +int +lpfc_bsg_ct_unsol_event(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, + struct lpfc_iocbq *piocbq) +{ + uint32_t evt_req_id = 0; + u16 cmd; + struct lpfc_dmabuf *dmabuf = NULL; + struct lpfc_bsg_event *evt; + struct event_data *evt_dat = NULL; + struct lpfc_iocbq *iocbq; + IOCB_t *iocb = NULL; + size_t offset = 0; + struct list_head head; + struct ulp_bde64 *bde; + dma_addr_t dma_addr; + int i; + struct lpfc_dmabuf *bdeBuf1 = piocbq->cmd_dmabuf; + struct lpfc_dmabuf *bdeBuf2 = piocbq->bpl_dmabuf; + struct lpfc_sli_ct_request *ct_req; + struct bsg_job *job = NULL; + struct fc_bsg_reply *bsg_reply; + struct bsg_job_data *dd_data = NULL; + unsigned long flags; + int size = 0; + u32 bde_count = 0; + + INIT_LIST_HEAD(&head); + list_add_tail(&head, &piocbq->list); + + ct_req = (struct lpfc_sli_ct_request *)bdeBuf1->virt; + evt_req_id = ct_req->FsType; + cmd = be16_to_cpu(ct_req->CommandResponse.bits.CmdRsp); + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_for_each_entry(evt, &phba->ct_ev_waiters, node) { + if (!(evt->type_mask & FC_REG_CT_EVENT) || + evt->req_id != evt_req_id) + continue; + + lpfc_bsg_event_ref(evt); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + evt_dat = kzalloc(sizeof(*evt_dat), GFP_KERNEL); + if (evt_dat == NULL) { + spin_lock_irqsave(&phba->ct_ev_lock, flags); + lpfc_bsg_event_unref(evt); + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2614 Memory allocation failed for " + "CT event\n"); + break; + } + + if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { + /* take accumulated byte count from the last iocbq */ + iocbq = list_entry(head.prev, typeof(*iocbq), list); + if (phba->sli_rev == LPFC_SLI_REV4) + evt_dat->len = iocbq->wcqe_cmpl.total_data_placed; + else + evt_dat->len = iocbq->iocb.unsli3.rcvsli3.acc_len; + } else { + list_for_each_entry(iocbq, &head, list) { + iocb = &iocbq->iocb; + for (i = 0; i < iocb->ulpBdeCount; + i++) + evt_dat->len += + iocb->un.cont64[i].tus.f.bdeSize; + } + } + + evt_dat->data = kzalloc(evt_dat->len, GFP_KERNEL); + if (evt_dat->data == NULL) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2615 Memory allocation failed for " + "CT event data, size %d\n", + evt_dat->len); + kfree(evt_dat); + spin_lock_irqsave(&phba->ct_ev_lock, flags); + lpfc_bsg_event_unref(evt); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + goto error_ct_unsol_exit; + } + + list_for_each_entry(iocbq, &head, list) { + size = 0; + if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { + bdeBuf1 = iocbq->cmd_dmabuf; + bdeBuf2 = iocbq->bpl_dmabuf; + } + if (phba->sli_rev == LPFC_SLI_REV4) + bde_count = iocbq->wcqe_cmpl.word3; + else + bde_count = iocbq->iocb.ulpBdeCount; + for (i = 0; i < bde_count; i++) { + if (phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED) { + if (i == 0) { + size = iocbq->wqe.gen_req.bde.tus.f.bdeSize; + dmabuf = bdeBuf1; + } else if (i == 1) { + size = iocbq->unsol_rcv_len; + dmabuf = bdeBuf2; + } + if ((offset + size) > evt_dat->len) + size = evt_dat->len - offset; + } else { + size = iocbq->iocb.un.cont64[i]. + tus.f.bdeSize; + bde = &iocbq->iocb.un.cont64[i]; + dma_addr = getPaddr(bde->addrHigh, + bde->addrLow); + dmabuf = lpfc_sli_ringpostbuf_get(phba, + pring, dma_addr); + } + if (!dmabuf) { + lpfc_printf_log(phba, KERN_ERR, + LOG_LIBDFC, "2616 No dmabuf " + "found for iocbq x%px\n", + iocbq); + kfree(evt_dat->data); + kfree(evt_dat); + spin_lock_irqsave(&phba->ct_ev_lock, + flags); + lpfc_bsg_event_unref(evt); + spin_unlock_irqrestore( + &phba->ct_ev_lock, flags); + goto error_ct_unsol_exit; + } + memcpy((char *)(evt_dat->data) + offset, + dmabuf->virt, size); + offset += size; + if (evt_req_id != SLI_CT_ELX_LOOPBACK && + !(phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED)) { + lpfc_sli_ringpostbuf_put(phba, pring, + dmabuf); + } else { + switch (cmd) { + case ELX_LOOPBACK_DATA: + if (phba->sli_rev < + LPFC_SLI_REV4) + diag_cmd_data_free(phba, + (struct lpfc_dmabufext + *)dmabuf); + break; + case ELX_LOOPBACK_XRI_SETUP: + if ((phba->sli_rev == + LPFC_SLI_REV2) || + (phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED + )) { + lpfc_in_buf_free(phba, + dmabuf); + } else { + lpfc_sli3_post_buffer(phba, + pring, + 1); + } + break; + default: + if (!(phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED)) + lpfc_sli3_post_buffer(phba, + pring, + 1); + break; + } + } + } + } + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + if (phba->sli_rev == LPFC_SLI_REV4) { + evt_dat->immed_dat = phba->ctx_idx; + phba->ctx_idx = (phba->ctx_idx + 1) % LPFC_CT_CTX_MAX; + /* Provide warning for over-run of the ct_ctx array */ + if (phba->ct_ctx[evt_dat->immed_dat].valid == + UNSOL_VALID) + lpfc_printf_log(phba, KERN_WARNING, LOG_ELS, + "2717 CT context array entry " + "[%d] over-run: oxid:x%x, " + "sid:x%x\n", phba->ctx_idx, + phba->ct_ctx[ + evt_dat->immed_dat].oxid, + phba->ct_ctx[ + evt_dat->immed_dat].SID); + phba->ct_ctx[evt_dat->immed_dat].rxid = + get_job_ulpcontext(phba, piocbq); + phba->ct_ctx[evt_dat->immed_dat].oxid = + get_job_rcvoxid(phba, piocbq); + phba->ct_ctx[evt_dat->immed_dat].SID = + bf_get(wqe_els_did, + &piocbq->wqe.xmit_els_rsp.wqe_dest); + phba->ct_ctx[evt_dat->immed_dat].valid = UNSOL_VALID; + } else + evt_dat->immed_dat = get_job_ulpcontext(phba, piocbq); + + evt_dat->type = FC_REG_CT_EVENT; + list_add(&evt_dat->node, &evt->events_to_see); + if (evt_req_id == SLI_CT_ELX_LOOPBACK) { + wake_up_interruptible(&evt->wq); + lpfc_bsg_event_unref(evt); + break; + } + + list_move(evt->events_to_see.prev, &evt->events_to_get); + + dd_data = (struct bsg_job_data *)evt->dd_data; + job = dd_data->set_job; + dd_data->set_job = NULL; + lpfc_bsg_event_unref(evt); + if (job) { + bsg_reply = job->reply; + bsg_reply->reply_payload_rcv_len = size; + /* make error code available to userspace */ + bsg_reply->result = 0; + job->dd_data = NULL; + /* complete the job back to userspace */ + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + spin_lock_irqsave(&phba->ct_ev_lock, flags); + } + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + +error_ct_unsol_exit: + if (!list_empty(&head)) + list_del(&head); + if ((phba->sli_rev < LPFC_SLI_REV4) && + (evt_req_id == SLI_CT_ELX_LOOPBACK)) + return 0; + return 1; +} + +/** + * lpfc_bsg_ct_unsol_abort - handler ct abort to management plane + * @phba: Pointer to HBA context object. + * @dmabuf: pointer to a dmabuf that describes the FC sequence + * + * This function handles abort to the CT command toward management plane + * for SLI4 port. + * + * If the pending context of a CT command to management plane present, clears + * such context and returns 1 for handled; otherwise, it returns 0 indicating + * no context exists. + **/ +int +lpfc_bsg_ct_unsol_abort(struct lpfc_hba *phba, struct hbq_dmabuf *dmabuf) +{ + struct fc_frame_header fc_hdr; + struct fc_frame_header *fc_hdr_ptr = &fc_hdr; + int ctx_idx, handled = 0; + uint16_t oxid, rxid; + uint32_t sid; + + memcpy(fc_hdr_ptr, dmabuf->hbuf.virt, sizeof(struct fc_frame_header)); + sid = sli4_sid_from_fc_hdr(fc_hdr_ptr); + oxid = be16_to_cpu(fc_hdr_ptr->fh_ox_id); + rxid = be16_to_cpu(fc_hdr_ptr->fh_rx_id); + + for (ctx_idx = 0; ctx_idx < LPFC_CT_CTX_MAX; ctx_idx++) { + if (phba->ct_ctx[ctx_idx].valid != UNSOL_VALID) + continue; + if (phba->ct_ctx[ctx_idx].rxid != rxid) + continue; + if (phba->ct_ctx[ctx_idx].oxid != oxid) + continue; + if (phba->ct_ctx[ctx_idx].SID != sid) + continue; + phba->ct_ctx[ctx_idx].valid = UNSOL_INVALID; + handled = 1; + } + return handled; +} + +/** + * lpfc_bsg_hba_set_event - process a SET_EVENT bsg vendor command + * @job: SET_EVENT fc_bsg_job + **/ +static int +lpfc_bsg_hba_set_event(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_hba *phba = vport->phba; + struct fc_bsg_request *bsg_request = job->request; + struct set_ct_event *event_req; + struct lpfc_bsg_event *evt; + int rc = 0; + struct bsg_job_data *dd_data = NULL; + uint32_t ev_mask; + unsigned long flags; + + if (job->request_len < + sizeof(struct fc_bsg_request) + sizeof(struct set_ct_event)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2612 Received SET_CT_EVENT below minimum " + "size\n"); + rc = -EINVAL; + goto job_error; + } + + event_req = (struct set_ct_event *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + ev_mask = ((uint32_t)(unsigned long)event_req->type_mask & + FC_REG_EVENT_MASK); + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_for_each_entry(evt, &phba->ct_ev_waiters, node) { + if (evt->reg_id == event_req->ev_reg_id) { + lpfc_bsg_event_ref(evt); + evt->wait_time_stamp = jiffies; + dd_data = (struct bsg_job_data *)evt->dd_data; + break; + } + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + if (&evt->node == &phba->ct_ev_waiters) { + /* no event waiting struct yet - first call */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (dd_data == NULL) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2734 Failed allocation of dd_data\n"); + rc = -ENOMEM; + goto job_error; + } + evt = lpfc_bsg_event_new(ev_mask, event_req->ev_reg_id, + event_req->ev_req_id); + if (!evt) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2617 Failed allocation of event " + "waiter\n"); + rc = -ENOMEM; + goto job_error; + } + dd_data->type = TYPE_EVT; + dd_data->set_job = NULL; + dd_data->context_un.evt = evt; + evt->dd_data = (void *)dd_data; + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_add(&evt->node, &phba->ct_ev_waiters); + lpfc_bsg_event_ref(evt); + evt->wait_time_stamp = jiffies; + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + } + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + evt->waiting = 1; + dd_data->set_job = job; /* for unsolicited command */ + job->dd_data = dd_data; /* for fc transport timeout callback*/ + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + return 0; /* call job done later */ + +job_error: + kfree(dd_data); + job->dd_data = NULL; + return rc; +} + +/** + * lpfc_bsg_hba_get_event - process a GET_EVENT bsg vendor command + * @job: GET_EVENT fc_bsg_job + **/ +static int +lpfc_bsg_hba_get_event(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_hba *phba = vport->phba; + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct get_ct_event *event_req; + struct get_ct_event_reply *event_reply; + struct lpfc_bsg_event *evt, *evt_next; + struct event_data *evt_dat = NULL; + unsigned long flags; + uint32_t rc = 0; + + if (job->request_len < + sizeof(struct fc_bsg_request) + sizeof(struct get_ct_event)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2613 Received GET_CT_EVENT request below " + "minimum size\n"); + rc = -EINVAL; + goto job_error; + } + + event_req = (struct get_ct_event *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + + event_reply = (struct get_ct_event_reply *) + bsg_reply->reply_data.vendor_reply.vendor_rsp; + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_for_each_entry_safe(evt, evt_next, &phba->ct_ev_waiters, node) { + if (evt->reg_id == event_req->ev_reg_id) { + if (list_empty(&evt->events_to_get)) + break; + lpfc_bsg_event_ref(evt); + evt->wait_time_stamp = jiffies; + evt_dat = list_entry(evt->events_to_get.prev, + struct event_data, node); + list_del(&evt_dat->node); + break; + } + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* The app may continue to ask for event data until it gets + * an error indicating that there isn't anymore + */ + if (evt_dat == NULL) { + bsg_reply->reply_payload_rcv_len = 0; + rc = -ENOENT; + goto job_error; + } + + if (evt_dat->len > job->request_payload.payload_len) { + evt_dat->len = job->request_payload.payload_len; + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2618 Truncated event data at %d " + "bytes\n", + job->request_payload.payload_len); + } + + event_reply->type = evt_dat->type; + event_reply->immed_data = evt_dat->immed_dat; + if (evt_dat->len > 0) + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->request_payload.sg_list, + job->request_payload.sg_cnt, + evt_dat->data, evt_dat->len); + else + bsg_reply->reply_payload_rcv_len = 0; + + if (evt_dat) { + kfree(evt_dat->data); + kfree(evt_dat); + } + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + lpfc_bsg_event_unref(evt); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + job->dd_data = NULL; + bsg_reply->result = 0; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return 0; + +job_error: + job->dd_data = NULL; + bsg_reply->result = rc; + return rc; +} + +/** + * lpfc_issue_ct_rsp_cmp - lpfc_issue_ct_rsp's completion handler + * @phba: Pointer to HBA context object. + * @cmdiocbq: Pointer to command iocb. + * @rspiocbq: Pointer to response iocb. + * + * This function is the completion handler for iocbs issued using + * lpfc_issue_ct_rsp_cmp function. This function is called by the + * ring event handler function without any lock held. This function + * can be called from both worker thread context and interrupt + * context. This function also can be called from other thread which + * cleans up the SLI layer objects. + * This function copy the contents of the response iocb to the + * response iocb memory object provided by the caller of + * lpfc_sli_issue_iocb_wait and then wakes up the thread which + * sleeps for the iocb completion. + **/ +static void +lpfc_issue_ct_rsp_cmp(struct lpfc_hba *phba, + struct lpfc_iocbq *cmdiocbq, + struct lpfc_iocbq *rspiocbq) +{ + struct bsg_job_data *dd_data; + struct bsg_job *job; + struct fc_bsg_reply *bsg_reply; + struct lpfc_dmabuf *bmp, *cmp; + struct lpfc_nodelist *ndlp; + unsigned long flags; + int rc = 0; + u32 ulp_status, ulp_word4; + + dd_data = cmdiocbq->context_un.dd_data; + + /* Determine if job has been aborted */ + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Close the timeout handler abort window */ + spin_lock_irqsave(&phba->hbalock, flags); + cmdiocbq->cmd_flag &= ~LPFC_IO_CMD_OUTSTANDING; + spin_unlock_irqrestore(&phba->hbalock, flags); + + ndlp = dd_data->context_un.iocb.ndlp; + cmp = cmdiocbq->cmd_dmabuf; + bmp = cmdiocbq->bpl_dmabuf; + + ulp_status = get_job_ulpstatus(phba, rspiocbq); + ulp_word4 = get_job_word4(phba, rspiocbq); + + /* Copy the completed job data or set the error status */ + + if (job) { + bsg_reply = job->reply; + if (ulp_status) { + if (ulp_status == IOSTAT_LOCAL_REJECT) { + switch (ulp_word4 & IOERR_PARAM_MASK) { + case IOERR_SEQUENCE_TIMEOUT: + rc = -ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = -EFAULT; + break; + default: + rc = -EACCES; + break; + } + } else { + rc = -EACCES; + } + } else { + bsg_reply->reply_payload_rcv_len = 0; + } + } + + lpfc_free_bsg_buffers(phba, cmp); + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); + kfree(bmp); + lpfc_sli_release_iocbq(phba, cmdiocbq); + lpfc_nlp_put(ndlp); + kfree(dd_data); + + /* Complete the job if the job is still active */ + + if (job) { + bsg_reply->result = rc; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + } + return; +} + +/** + * lpfc_issue_ct_rsp - issue a ct response + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @tag: tag index value into the ports context exchange array. + * @cmp: Pointer to a cmp dma buffer descriptor. + * @bmp: Pointer to a bmp dma buffer descriptor. + * @num_entry: Number of enties in the bde. + **/ +static int +lpfc_issue_ct_rsp(struct lpfc_hba *phba, struct bsg_job *job, uint32_t tag, + struct lpfc_dmabuf *cmp, struct lpfc_dmabuf *bmp, + int num_entry) +{ + struct lpfc_iocbq *ctiocb = NULL; + int rc = 0; + struct lpfc_nodelist *ndlp = NULL; + struct bsg_job_data *dd_data; + unsigned long flags; + uint32_t creg_val; + u16 ulp_context, iotag; + + ndlp = lpfc_findnode_did(phba->pport, phba->ct_ctx[tag].SID); + if (!ndlp) { + lpfc_printf_log(phba, KERN_WARNING, LOG_ELS, + "2721 ndlp null for oxid %x SID %x\n", + phba->ct_ctx[tag].rxid, + phba->ct_ctx[tag].SID); + return IOCB_ERROR; + } + + /* allocate our bsg tracking structure */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (!dd_data) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2736 Failed allocation of dd_data\n"); + rc = -ENOMEM; + goto no_dd_data; + } + + /* Allocate buffer for command iocb */ + ctiocb = lpfc_sli_get_iocbq(phba); + if (!ctiocb) { + rc = -ENOMEM; + goto no_ctiocb; + } + + if (phba->sli_rev == LPFC_SLI_REV4) { + /* Do not issue unsol response if oxid not marked as valid */ + if (phba->ct_ctx[tag].valid != UNSOL_VALID) { + rc = IOCB_ERROR; + goto issue_ct_rsp_exit; + } + + lpfc_sli_prep_xmit_seq64(phba, ctiocb, bmp, + phba->sli4_hba.rpi_ids[ndlp->nlp_rpi], + phba->ct_ctx[tag].oxid, num_entry, + FC_RCTL_DD_SOL_CTL, 1, + CMD_XMIT_SEQUENCE64_WQE); + + /* The exchange is done, mark the entry as invalid */ + phba->ct_ctx[tag].valid = UNSOL_INVALID; + iotag = get_wqe_reqtag(ctiocb); + } else { + lpfc_sli_prep_xmit_seq64(phba, ctiocb, bmp, 0, tag, num_entry, + FC_RCTL_DD_SOL_CTL, 1, + CMD_XMIT_SEQUENCE64_CX); + ctiocb->num_bdes = num_entry; + iotag = ctiocb->iocb.ulpIoTag; + } + + ulp_context = get_job_ulpcontext(phba, ctiocb); + + /* Xmit CT response on exchange <xid> */ + lpfc_printf_log(phba, KERN_INFO, LOG_ELS, + "2722 Xmit CT response on exchange x%x Data: x%x x%x x%x\n", + ulp_context, iotag, tag, phba->link_state); + + ctiocb->cmd_flag |= LPFC_IO_LIBDFC; + ctiocb->vport = phba->pport; + ctiocb->context_un.dd_data = dd_data; + ctiocb->cmd_dmabuf = cmp; + ctiocb->bpl_dmabuf = bmp; + ctiocb->ndlp = ndlp; + ctiocb->cmd_cmpl = lpfc_issue_ct_rsp_cmp; + + dd_data->type = TYPE_IOCB; + dd_data->set_job = job; + dd_data->context_un.iocb.cmdiocbq = ctiocb; + dd_data->context_un.iocb.ndlp = lpfc_nlp_get(ndlp); + if (!dd_data->context_un.iocb.ndlp) { + rc = -IOCB_ERROR; + goto issue_ct_rsp_exit; + } + dd_data->context_un.iocb.rmp = NULL; + job->dd_data = dd_data; + + if (phba->cfg_poll & DISABLE_FCP_RING_INT) { + if (lpfc_readl(phba->HCregaddr, &creg_val)) { + rc = -IOCB_ERROR; + goto issue_ct_rsp_exit; + } + creg_val |= (HC_R0INT_ENA << LPFC_FCP_RING); + writel(creg_val, phba->HCregaddr); + readl(phba->HCregaddr); /* flush */ + } + + rc = lpfc_sli_issue_iocb(phba, LPFC_ELS_RING, ctiocb, 0); + if (rc == IOCB_SUCCESS) { + spin_lock_irqsave(&phba->hbalock, flags); + /* make sure the I/O had not been completed/released */ + if (ctiocb->cmd_flag & LPFC_IO_LIBDFC) { + /* open up abort window to timeout handler */ + ctiocb->cmd_flag |= LPFC_IO_CMD_OUTSTANDING; + } + spin_unlock_irqrestore(&phba->hbalock, flags); + return 0; /* done for now */ + } + + /* iocb failed so cleanup */ + job->dd_data = NULL; + lpfc_nlp_put(ndlp); + +issue_ct_rsp_exit: + lpfc_sli_release_iocbq(phba, ctiocb); +no_ctiocb: + kfree(dd_data); +no_dd_data: + return rc; +} + +/** + * lpfc_bsg_send_mgmt_rsp - process a SEND_MGMT_RESP bsg vendor command + * @job: SEND_MGMT_RESP fc_bsg_job + **/ +static int +lpfc_bsg_send_mgmt_rsp(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_hba *phba = vport->phba; + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct send_mgmt_resp *mgmt_resp = (struct send_mgmt_resp *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + struct ulp_bde64 *bpl; + struct lpfc_dmabuf *bmp = NULL, *cmp = NULL; + int bpl_entries; + uint32_t tag = mgmt_resp->tag; + unsigned long reqbfrcnt = + (unsigned long)job->request_payload.payload_len; + int rc = 0; + + /* in case no data is transferred */ + bsg_reply->reply_payload_rcv_len = 0; + + if (!reqbfrcnt || (reqbfrcnt > (80 * BUF_SZ_4K))) { + rc = -ERANGE; + goto send_mgmt_rsp_exit; + } + + bmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!bmp) { + rc = -ENOMEM; + goto send_mgmt_rsp_exit; + } + + bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); + if (!bmp->virt) { + rc = -ENOMEM; + goto send_mgmt_rsp_free_bmp; + } + + INIT_LIST_HEAD(&bmp->list); + bpl = (struct ulp_bde64 *) bmp->virt; + bpl_entries = (LPFC_BPL_SIZE/sizeof(struct ulp_bde64)); + cmp = lpfc_alloc_bsg_buffers(phba, job->request_payload.payload_len, + 1, bpl, &bpl_entries); + if (!cmp) { + rc = -ENOMEM; + goto send_mgmt_rsp_free_bmp; + } + lpfc_bsg_copy_data(cmp, &job->request_payload, + job->request_payload.payload_len, 1); + + rc = lpfc_issue_ct_rsp(phba, job, tag, cmp, bmp, bpl_entries); + + if (rc == IOCB_SUCCESS) + return 0; /* done for now */ + + rc = -EACCES; + + lpfc_free_bsg_buffers(phba, cmp); + +send_mgmt_rsp_free_bmp: + if (bmp->virt) + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); + kfree(bmp); +send_mgmt_rsp_exit: + /* make error code available to userspace */ + bsg_reply->result = rc; + job->dd_data = NULL; + return rc; +} + +/** + * lpfc_bsg_diag_mode_enter - process preparing into device diag loopback mode + * @phba: Pointer to HBA context object. + * + * This function is responsible for preparing driver for diag loopback + * on device. + */ +static int +lpfc_bsg_diag_mode_enter(struct lpfc_hba *phba) +{ + struct lpfc_vport **vports; + struct Scsi_Host *shost; + struct lpfc_sli *psli; + struct lpfc_queue *qp = NULL; + struct lpfc_sli_ring *pring; + int i = 0; + + psli = &phba->sli; + if (!psli) + return -ENODEV; + + + if ((phba->link_state == LPFC_HBA_ERROR) || + (psli->sli_flag & LPFC_BLOCK_MGMT_IO) || + (!(psli->sli_flag & LPFC_SLI_ACTIVE))) + return -EACCES; + + vports = lpfc_create_vport_work_array(phba); + if (vports) { + for (i = 0; i <= phba->max_vpi && vports[i] != NULL; i++) { + shost = lpfc_shost_from_vport(vports[i]); + scsi_block_requests(shost); + } + lpfc_destroy_vport_work_array(phba, vports); + } else { + shost = lpfc_shost_from_vport(phba->pport); + scsi_block_requests(shost); + } + + if (phba->sli_rev != LPFC_SLI_REV4) { + pring = &psli->sli3_ring[LPFC_FCP_RING]; + lpfc_emptyq_wait(phba, &pring->txcmplq, &phba->hbalock); + return 0; + } + list_for_each_entry(qp, &phba->sli4_hba.lpfc_wq_list, wq_list) { + pring = qp->pring; + if (!pring || (pring->ringno != LPFC_FCP_RING)) + continue; + if (!lpfc_emptyq_wait(phba, &pring->txcmplq, + &pring->ring_lock)) + break; + } + return 0; +} + +/** + * lpfc_bsg_diag_mode_exit - exit process from device diag loopback mode + * @phba: Pointer to HBA context object. + * + * This function is responsible for driver exit processing of setting up + * diag loopback mode on device. + */ +static void +lpfc_bsg_diag_mode_exit(struct lpfc_hba *phba) +{ + struct Scsi_Host *shost; + struct lpfc_vport **vports; + int i; + + vports = lpfc_create_vport_work_array(phba); + if (vports) { + for (i = 0; i <= phba->max_vpi && vports[i] != NULL; i++) { + shost = lpfc_shost_from_vport(vports[i]); + scsi_unblock_requests(shost); + } + lpfc_destroy_vport_work_array(phba, vports); + } else { + shost = lpfc_shost_from_vport(phba->pport); + scsi_unblock_requests(shost); + } + return; +} + +/** + * lpfc_sli3_bsg_diag_loopback_mode - process an sli3 bsg vendor command + * @phba: Pointer to HBA context object. + * @job: LPFC_BSG_VENDOR_DIAG_MODE + * + * This function is responsible for placing an sli3 port into diagnostic + * loopback mode in order to perform a diagnostic loopback test. + * All new scsi requests are blocked, a small delay is used to allow the + * scsi requests to complete then the link is brought down. If the link is + * is placed in loopback mode then scsi requests are again allowed + * so the scsi mid-layer doesn't give up on the port. + * All of this is done in-line. + */ +static int +lpfc_sli3_bsg_diag_loopback_mode(struct lpfc_hba *phba, struct bsg_job *job) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct diag_mode_set *loopback_mode; + uint32_t link_flags; + uint32_t timeout; + LPFC_MBOXQ_t *pmboxq = NULL; + int mbxstatus = MBX_SUCCESS; + int i = 0; + int rc = 0; + + /* no data to return just the return code */ + bsg_reply->reply_payload_rcv_len = 0; + + if (job->request_len < sizeof(struct fc_bsg_request) + + sizeof(struct diag_mode_set)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2738 Received DIAG MODE request size:%d " + "below the minimum size:%d\n", + job->request_len, + (int)(sizeof(struct fc_bsg_request) + + sizeof(struct diag_mode_set))); + rc = -EINVAL; + goto job_error; + } + + rc = lpfc_bsg_diag_mode_enter(phba); + if (rc) + goto job_error; + + /* bring the link to diagnostic mode */ + loopback_mode = (struct diag_mode_set *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + link_flags = loopback_mode->type; + timeout = loopback_mode->timeout * 100; + + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) { + rc = -ENOMEM; + goto loopback_mode_exit; + } + memset((void *)pmboxq, 0, sizeof(LPFC_MBOXQ_t)); + pmboxq->u.mb.mbxCommand = MBX_DOWN_LINK; + pmboxq->u.mb.mbxOwner = OWN_HOST; + + mbxstatus = lpfc_sli_issue_mbox_wait(phba, pmboxq, LPFC_MBOX_TMO); + + if ((mbxstatus == MBX_SUCCESS) && (pmboxq->u.mb.mbxStatus == 0)) { + /* wait for link down before proceeding */ + i = 0; + while (phba->link_state != LPFC_LINK_DOWN) { + if (i++ > timeout) { + rc = -ETIMEDOUT; + goto loopback_mode_exit; + } + msleep(10); + } + + memset((void *)pmboxq, 0, sizeof(LPFC_MBOXQ_t)); + if (link_flags == INTERNAL_LOOP_BACK) + pmboxq->u.mb.un.varInitLnk.link_flags = FLAGS_LOCAL_LB; + else + pmboxq->u.mb.un.varInitLnk.link_flags = + FLAGS_TOPOLOGY_MODE_LOOP; + + pmboxq->u.mb.mbxCommand = MBX_INIT_LINK; + pmboxq->u.mb.mbxOwner = OWN_HOST; + + mbxstatus = lpfc_sli_issue_mbox_wait(phba, pmboxq, + LPFC_MBOX_TMO); + + if ((mbxstatus != MBX_SUCCESS) || (pmboxq->u.mb.mbxStatus)) + rc = -ENODEV; + else { + spin_lock_irq(&phba->hbalock); + phba->link_flag |= LS_LOOPBACK_MODE; + spin_unlock_irq(&phba->hbalock); + /* wait for the link attention interrupt */ + msleep(100); + + i = 0; + while (phba->link_state != LPFC_HBA_READY) { + if (i++ > timeout) { + rc = -ETIMEDOUT; + break; + } + + msleep(10); + } + } + + } else + rc = -ENODEV; + +loopback_mode_exit: + lpfc_bsg_diag_mode_exit(phba); + + /* + * Let SLI layer release mboxq if mbox command completed after timeout. + */ + if (pmboxq && mbxstatus != MBX_TIMEOUT) + mempool_free(pmboxq, phba->mbox_mem_pool); + +job_error: + /* make error code available to userspace */ + bsg_reply->result = rc; + /* complete the job back to userspace if no error */ + if (rc == 0) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfc_sli4_bsg_set_link_diag_state - set sli4 link diag state + * @phba: Pointer to HBA context object. + * @diag: Flag for set link to diag or nomral operation state. + * + * This function is responsible for issuing a sli4 mailbox command for setting + * link to either diag state or normal operation state. + */ +static int +lpfc_sli4_bsg_set_link_diag_state(struct lpfc_hba *phba, uint32_t diag) +{ + LPFC_MBOXQ_t *pmboxq; + struct lpfc_mbx_set_link_diag_state *link_diag_state; + uint32_t req_len, alloc_len; + int mbxstatus = MBX_SUCCESS, rc; + + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) + return -ENOMEM; + + req_len = (sizeof(struct lpfc_mbx_set_link_diag_state) - + sizeof(struct lpfc_sli4_cfg_mhdr)); + alloc_len = lpfc_sli4_config(phba, pmboxq, LPFC_MBOX_SUBSYSTEM_FCOE, + LPFC_MBOX_OPCODE_FCOE_LINK_DIAG_STATE, + req_len, LPFC_SLI4_MBX_EMBED); + if (alloc_len != req_len) { + rc = -ENOMEM; + goto link_diag_state_set_out; + } + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3128 Set link to diagnostic state:x%x (x%x/x%x)\n", + diag, phba->sli4_hba.lnk_info.lnk_tp, + phba->sli4_hba.lnk_info.lnk_no); + + link_diag_state = &pmboxq->u.mqe.un.link_diag_state; + bf_set(lpfc_mbx_set_diag_state_diag_bit_valid, &link_diag_state->u.req, + LPFC_DIAG_STATE_DIAG_BIT_VALID_CHANGE); + bf_set(lpfc_mbx_set_diag_state_link_num, &link_diag_state->u.req, + phba->sli4_hba.lnk_info.lnk_no); + bf_set(lpfc_mbx_set_diag_state_link_type, &link_diag_state->u.req, + phba->sli4_hba.lnk_info.lnk_tp); + if (diag) + bf_set(lpfc_mbx_set_diag_state_diag, + &link_diag_state->u.req, 1); + else + bf_set(lpfc_mbx_set_diag_state_diag, + &link_diag_state->u.req, 0); + + mbxstatus = lpfc_sli_issue_mbox_wait(phba, pmboxq, LPFC_MBOX_TMO); + + if ((mbxstatus == MBX_SUCCESS) && (pmboxq->u.mb.mbxStatus == 0)) + rc = 0; + else + rc = -ENODEV; + +link_diag_state_set_out: + if (pmboxq && (mbxstatus != MBX_TIMEOUT)) + mempool_free(pmboxq, phba->mbox_mem_pool); + + return rc; +} + +/** + * lpfc_sli4_bsg_set_loopback_mode - set sli4 internal loopback diagnostic + * @phba: Pointer to HBA context object. + * @mode: loopback mode to set + * @link_no: link number for loopback mode to set + * + * This function is responsible for issuing a sli4 mailbox command for setting + * up loopback diagnostic for a link. + */ +static int +lpfc_sli4_bsg_set_loopback_mode(struct lpfc_hba *phba, int mode, + uint32_t link_no) +{ + LPFC_MBOXQ_t *pmboxq; + uint32_t req_len, alloc_len; + struct lpfc_mbx_set_link_diag_loopback *link_diag_loopback; + int mbxstatus = MBX_SUCCESS, rc = 0; + + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) + return -ENOMEM; + req_len = (sizeof(struct lpfc_mbx_set_link_diag_loopback) - + sizeof(struct lpfc_sli4_cfg_mhdr)); + alloc_len = lpfc_sli4_config(phba, pmboxq, LPFC_MBOX_SUBSYSTEM_FCOE, + LPFC_MBOX_OPCODE_FCOE_LINK_DIAG_LOOPBACK, + req_len, LPFC_SLI4_MBX_EMBED); + if (alloc_len != req_len) { + mempool_free(pmboxq, phba->mbox_mem_pool); + return -ENOMEM; + } + link_diag_loopback = &pmboxq->u.mqe.un.link_diag_loopback; + bf_set(lpfc_mbx_set_diag_state_link_num, + &link_diag_loopback->u.req, link_no); + + if (phba->sli4_hba.conf_trunk & (1 << link_no)) { + bf_set(lpfc_mbx_set_diag_state_link_type, + &link_diag_loopback->u.req, LPFC_LNK_FC_TRUNKED); + } else { + bf_set(lpfc_mbx_set_diag_state_link_type, + &link_diag_loopback->u.req, + phba->sli4_hba.lnk_info.lnk_tp); + } + + bf_set(lpfc_mbx_set_diag_lpbk_type, &link_diag_loopback->u.req, + mode); + + mbxstatus = lpfc_sli_issue_mbox_wait(phba, pmboxq, LPFC_MBOX_TMO); + if ((mbxstatus != MBX_SUCCESS) || (pmboxq->u.mb.mbxStatus)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "3127 Failed setup loopback mode mailbox " + "command, rc:x%x, status:x%x\n", mbxstatus, + pmboxq->u.mb.mbxStatus); + rc = -ENODEV; + } + if (pmboxq && (mbxstatus != MBX_TIMEOUT)) + mempool_free(pmboxq, phba->mbox_mem_pool); + return rc; +} + +/** + * lpfc_sli4_diag_fcport_reg_setup - setup port registrations for diagnostic + * @phba: Pointer to HBA context object. + * + * This function set up SLI4 FC port registrations for diagnostic run, which + * includes all the rpis, vfi, and also vpi. + */ +static int +lpfc_sli4_diag_fcport_reg_setup(struct lpfc_hba *phba) +{ + if (phba->pport->fc_flag & FC_VFI_REGISTERED) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "3136 Port still had vfi registered: " + "mydid:x%x, fcfi:%d, vfi:%d, vpi:%d\n", + phba->pport->fc_myDID, phba->fcf.fcfi, + phba->sli4_hba.vfi_ids[phba->pport->vfi], + phba->vpi_ids[phba->pport->vpi]); + return -EINVAL; + } + return lpfc_issue_reg_vfi(phba->pport); +} + +/** + * lpfc_sli4_bsg_diag_loopback_mode - process an sli4 bsg vendor command + * @phba: Pointer to HBA context object. + * @job: LPFC_BSG_VENDOR_DIAG_MODE + * + * This function is responsible for placing an sli4 port into diagnostic + * loopback mode in order to perform a diagnostic loopback test. + */ +static int +lpfc_sli4_bsg_diag_loopback_mode(struct lpfc_hba *phba, struct bsg_job *job) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct diag_mode_set *loopback_mode; + uint32_t link_flags, timeout, link_no; + int i, rc = 0; + + /* no data to return just the return code */ + bsg_reply->reply_payload_rcv_len = 0; + + if (job->request_len < sizeof(struct fc_bsg_request) + + sizeof(struct diag_mode_set)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "3011 Received DIAG MODE request size:%d " + "below the minimum size:%d\n", + job->request_len, + (int)(sizeof(struct fc_bsg_request) + + sizeof(struct diag_mode_set))); + rc = -EINVAL; + goto job_done; + } + + loopback_mode = (struct diag_mode_set *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + link_flags = loopback_mode->type; + timeout = loopback_mode->timeout * 100; + + if (loopback_mode->physical_link == -1) + link_no = phba->sli4_hba.lnk_info.lnk_no; + else + link_no = loopback_mode->physical_link; + + if (link_flags == DISABLE_LOOP_BACK) { + rc = lpfc_sli4_bsg_set_loopback_mode(phba, + LPFC_DIAG_LOOPBACK_TYPE_DISABLE, + link_no); + if (!rc) { + /* Unset the need disable bit */ + phba->sli4_hba.conf_trunk &= ~((1 << link_no) << 4); + } + goto job_done; + } else { + /* Check if we need to disable the loopback state */ + if (phba->sli4_hba.conf_trunk & ((1 << link_no) << 4)) { + rc = -EPERM; + goto job_done; + } + } + + rc = lpfc_bsg_diag_mode_enter(phba); + if (rc) + goto job_done; + + /* indicate we are in loobpack diagnostic mode */ + spin_lock_irq(&phba->hbalock); + phba->link_flag |= LS_LOOPBACK_MODE; + spin_unlock_irq(&phba->hbalock); + + /* reset port to start frome scratch */ + rc = lpfc_selective_reset(phba); + if (rc) + goto job_done; + + /* bring the link to diagnostic mode */ + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3129 Bring link to diagnostic state.\n"); + + rc = lpfc_sli4_bsg_set_link_diag_state(phba, 1); + if (rc) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "3130 Failed to bring link to diagnostic " + "state, rc:x%x\n", rc); + goto loopback_mode_exit; + } + + /* wait for link down before proceeding */ + i = 0; + while (phba->link_state != LPFC_LINK_DOWN) { + if (i++ > timeout) { + rc = -ETIMEDOUT; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3131 Timeout waiting for link to " + "diagnostic mode, timeout:%d ms\n", + timeout * 10); + goto loopback_mode_exit; + } + msleep(10); + } + + /* set up loopback mode */ + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3132 Set up loopback mode:x%x\n", link_flags); + + switch (link_flags) { + case INTERNAL_LOOP_BACK: + if (phba->sli4_hba.conf_trunk & (1 << link_no)) { + rc = lpfc_sli4_bsg_set_loopback_mode(phba, + LPFC_DIAG_LOOPBACK_TYPE_INTERNAL, + link_no); + } else { + /* Trunk is configured, but link is not in this trunk */ + if (phba->sli4_hba.conf_trunk) { + rc = -ELNRNG; + goto loopback_mode_exit; + } + + rc = lpfc_sli4_bsg_set_loopback_mode(phba, + LPFC_DIAG_LOOPBACK_TYPE_INTERNAL, + link_no); + } + + if (!rc) { + /* Set the need disable bit */ + phba->sli4_hba.conf_trunk |= (1 << link_no) << 4; + } + + break; + case EXTERNAL_LOOP_BACK: + if (phba->sli4_hba.conf_trunk & (1 << link_no)) { + rc = lpfc_sli4_bsg_set_loopback_mode(phba, + LPFC_DIAG_LOOPBACK_TYPE_EXTERNAL_TRUNKED, + link_no); + } else { + /* Trunk is configured, but link is not in this trunk */ + if (phba->sli4_hba.conf_trunk) { + rc = -ELNRNG; + goto loopback_mode_exit; + } + + rc = lpfc_sli4_bsg_set_loopback_mode(phba, + LPFC_DIAG_LOOPBACK_TYPE_SERDES, + link_no); + } + + if (!rc) { + /* Set the need disable bit */ + phba->sli4_hba.conf_trunk |= (1 << link_no) << 4; + } + + break; + default: + rc = -EINVAL; + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "3141 Loopback mode:x%x not supported\n", + link_flags); + goto loopback_mode_exit; + } + + if (!rc) { + /* wait for the link attention interrupt */ + msleep(100); + i = 0; + while (phba->link_state < LPFC_LINK_UP) { + if (i++ > timeout) { + rc = -ETIMEDOUT; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3137 Timeout waiting for link up " + "in loopback mode, timeout:%d ms\n", + timeout * 10); + break; + } + msleep(10); + } + } + + /* port resource registration setup for loopback diagnostic */ + if (!rc) { + /* set up a none zero myDID for loopback test */ + phba->pport->fc_myDID = 1; + rc = lpfc_sli4_diag_fcport_reg_setup(phba); + } else + goto loopback_mode_exit; + + if (!rc) { + /* wait for the port ready */ + msleep(100); + i = 0; + while (phba->link_state != LPFC_HBA_READY) { + if (i++ > timeout) { + rc = -ETIMEDOUT; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3133 Timeout waiting for port " + "loopback mode ready, timeout:%d ms\n", + timeout * 10); + break; + } + msleep(10); + } + } + +loopback_mode_exit: + /* clear loopback diagnostic mode */ + if (rc) { + spin_lock_irq(&phba->hbalock); + phba->link_flag &= ~LS_LOOPBACK_MODE; + spin_unlock_irq(&phba->hbalock); + } + lpfc_bsg_diag_mode_exit(phba); + +job_done: + /* make error code available to userspace */ + bsg_reply->result = rc; + /* complete the job back to userspace if no error */ + if (rc == 0) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfc_bsg_diag_loopback_mode - bsg vendor command for diag loopback mode + * @job: LPFC_BSG_VENDOR_DIAG_MODE + * + * This function is responsible for responding to check and dispatch bsg diag + * command from the user to proper driver action routines. + */ +static int +lpfc_bsg_diag_loopback_mode(struct bsg_job *job) +{ + struct Scsi_Host *shost; + struct lpfc_vport *vport; + struct lpfc_hba *phba; + int rc; + + shost = fc_bsg_to_shost(job); + if (!shost) + return -ENODEV; + vport = shost_priv(shost); + if (!vport) + return -ENODEV; + phba = vport->phba; + if (!phba) + return -ENODEV; + + if (phba->sli_rev < LPFC_SLI_REV4) + rc = lpfc_sli3_bsg_diag_loopback_mode(phba, job); + else if (bf_get(lpfc_sli_intf_if_type, &phba->sli4_hba.sli_intf) >= + LPFC_SLI_INTF_IF_TYPE_2) + rc = lpfc_sli4_bsg_diag_loopback_mode(phba, job); + else + rc = -ENODEV; + + return rc; +} + +/** + * lpfc_sli4_bsg_diag_mode_end - sli4 bsg vendor command for ending diag mode + * @job: LPFC_BSG_VENDOR_DIAG_MODE_END + * + * This function is responsible for responding to check and dispatch bsg diag + * command from the user to proper driver action routines. + */ +static int +lpfc_sli4_bsg_diag_mode_end(struct bsg_job *job) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct Scsi_Host *shost; + struct lpfc_vport *vport; + struct lpfc_hba *phba; + struct diag_mode_set *loopback_mode_end_cmd; + uint32_t timeout; + int rc, i; + + shost = fc_bsg_to_shost(job); + if (!shost) + return -ENODEV; + vport = shost_priv(shost); + if (!vport) + return -ENODEV; + phba = vport->phba; + if (!phba) + return -ENODEV; + + if (phba->sli_rev < LPFC_SLI_REV4) + return -ENODEV; + if (bf_get(lpfc_sli_intf_if_type, &phba->sli4_hba.sli_intf) < + LPFC_SLI_INTF_IF_TYPE_2) + return -ENODEV; + + /* clear loopback diagnostic mode */ + spin_lock_irq(&phba->hbalock); + phba->link_flag &= ~LS_LOOPBACK_MODE; + spin_unlock_irq(&phba->hbalock); + loopback_mode_end_cmd = (struct diag_mode_set *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + timeout = loopback_mode_end_cmd->timeout * 100; + + rc = lpfc_sli4_bsg_set_link_diag_state(phba, 0); + if (rc) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "3139 Failed to bring link to diagnostic " + "state, rc:x%x\n", rc); + goto loopback_mode_end_exit; + } + + /* wait for link down before proceeding */ + i = 0; + while (phba->link_state != LPFC_LINK_DOWN) { + if (i++ > timeout) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3140 Timeout waiting for link to " + "diagnostic mode_end, timeout:%d ms\n", + timeout * 10); + /* there is nothing much we can do here */ + break; + } + msleep(10); + } + + /* reset port resource registrations */ + rc = lpfc_selective_reset(phba); + phba->pport->fc_myDID = 0; + +loopback_mode_end_exit: + /* make return code available to userspace */ + bsg_reply->result = rc; + /* complete the job back to userspace if no error */ + if (rc == 0) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfc_sli4_bsg_link_diag_test - sli4 bsg vendor command for diag link test + * @job: LPFC_BSG_VENDOR_DIAG_LINK_TEST + * + * This function is to perform SLI4 diag link test request from the user + * applicaiton. + */ +static int +lpfc_sli4_bsg_link_diag_test(struct bsg_job *job) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct Scsi_Host *shost; + struct lpfc_vport *vport; + struct lpfc_hba *phba; + LPFC_MBOXQ_t *pmboxq; + struct sli4_link_diag *link_diag_test_cmd; + uint32_t req_len, alloc_len; + struct lpfc_mbx_run_link_diag_test *run_link_diag_test; + union lpfc_sli4_cfg_shdr *shdr; + uint32_t shdr_status, shdr_add_status; + struct diag_status *diag_status_reply; + int mbxstatus, rc = -ENODEV, rc1 = 0; + + shost = fc_bsg_to_shost(job); + if (!shost) + goto job_error; + + vport = shost_priv(shost); + if (!vport) + goto job_error; + + phba = vport->phba; + if (!phba) + goto job_error; + + + if (phba->sli_rev < LPFC_SLI_REV4) + goto job_error; + + if (bf_get(lpfc_sli_intf_if_type, &phba->sli4_hba.sli_intf) < + LPFC_SLI_INTF_IF_TYPE_2) + goto job_error; + + if (job->request_len < sizeof(struct fc_bsg_request) + + sizeof(struct sli4_link_diag)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "3013 Received LINK DIAG TEST request " + " size:%d below the minimum size:%d\n", + job->request_len, + (int)(sizeof(struct fc_bsg_request) + + sizeof(struct sli4_link_diag))); + rc = -EINVAL; + goto job_error; + } + + rc = lpfc_bsg_diag_mode_enter(phba); + if (rc) + goto job_error; + + link_diag_test_cmd = (struct sli4_link_diag *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + + rc = lpfc_sli4_bsg_set_link_diag_state(phba, 1); + + if (rc) + goto job_error; + + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) { + rc = -ENOMEM; + goto link_diag_test_exit; + } + + req_len = (sizeof(struct lpfc_mbx_set_link_diag_state) - + sizeof(struct lpfc_sli4_cfg_mhdr)); + alloc_len = lpfc_sli4_config(phba, pmboxq, LPFC_MBOX_SUBSYSTEM_FCOE, + LPFC_MBOX_OPCODE_FCOE_LINK_DIAG_STATE, + req_len, LPFC_SLI4_MBX_EMBED); + if (alloc_len != req_len) { + rc = -ENOMEM; + goto link_diag_test_exit; + } + + run_link_diag_test = &pmboxq->u.mqe.un.link_diag_test; + bf_set(lpfc_mbx_run_diag_test_link_num, &run_link_diag_test->u.req, + phba->sli4_hba.lnk_info.lnk_no); + bf_set(lpfc_mbx_run_diag_test_link_type, &run_link_diag_test->u.req, + phba->sli4_hba.lnk_info.lnk_tp); + bf_set(lpfc_mbx_run_diag_test_test_id, &run_link_diag_test->u.req, + link_diag_test_cmd->test_id); + bf_set(lpfc_mbx_run_diag_test_loops, &run_link_diag_test->u.req, + link_diag_test_cmd->loops); + bf_set(lpfc_mbx_run_diag_test_test_ver, &run_link_diag_test->u.req, + link_diag_test_cmd->test_version); + bf_set(lpfc_mbx_run_diag_test_err_act, &run_link_diag_test->u.req, + link_diag_test_cmd->error_action); + + mbxstatus = lpfc_sli_issue_mbox(phba, pmboxq, MBX_POLL); + + shdr = (union lpfc_sli4_cfg_shdr *) + &pmboxq->u.mqe.un.sli4_config.header.cfg_shdr; + shdr_status = bf_get(lpfc_mbox_hdr_status, &shdr->response); + shdr_add_status = bf_get(lpfc_mbox_hdr_add_status, &shdr->response); + if (shdr_status || shdr_add_status || mbxstatus) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "3010 Run link diag test mailbox failed with " + "mbx_status x%x status x%x, add_status x%x\n", + mbxstatus, shdr_status, shdr_add_status); + } + + diag_status_reply = (struct diag_status *) + bsg_reply->reply_data.vendor_reply.vendor_rsp; + + if (job->reply_len < sizeof(*bsg_reply) + sizeof(*diag_status_reply)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "3012 Received Run link diag test reply " + "below minimum size (%d): reply_len:%d\n", + (int)(sizeof(*bsg_reply) + + sizeof(*diag_status_reply)), + job->reply_len); + rc = -EINVAL; + goto job_error; + } + + diag_status_reply->mbox_status = mbxstatus; + diag_status_reply->shdr_status = shdr_status; + diag_status_reply->shdr_add_status = shdr_add_status; + +link_diag_test_exit: + rc1 = lpfc_sli4_bsg_set_link_diag_state(phba, 0); + + if (pmboxq) + mempool_free(pmboxq, phba->mbox_mem_pool); + + lpfc_bsg_diag_mode_exit(phba); + +job_error: + /* make error code available to userspace */ + if (rc1 && !rc) + rc = rc1; + bsg_reply->result = rc; + /* complete the job back to userspace if no error */ + if (rc == 0) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfcdiag_loop_self_reg - obtains a remote port login id + * @phba: Pointer to HBA context object + * @rpi: Pointer to a remote port login id + * + * This function obtains a remote port login id so the diag loopback test + * can send and receive its own unsolicited CT command. + **/ +static int lpfcdiag_loop_self_reg(struct lpfc_hba *phba, uint16_t *rpi) +{ + LPFC_MBOXQ_t *mbox; + struct lpfc_dmabuf *dmabuff; + int status; + + mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + if (phba->sli_rev < LPFC_SLI_REV4) + status = lpfc_reg_rpi(phba, 0, phba->pport->fc_myDID, + (uint8_t *)&phba->pport->fc_sparam, + mbox, *rpi); + else { + *rpi = lpfc_sli4_alloc_rpi(phba); + if (*rpi == LPFC_RPI_ALLOC_ERROR) { + mempool_free(mbox, phba->mbox_mem_pool); + return -EBUSY; + } + status = lpfc_reg_rpi(phba, phba->pport->vpi, + phba->pport->fc_myDID, + (uint8_t *)&phba->pport->fc_sparam, + mbox, *rpi); + } + + if (status) { + mempool_free(mbox, phba->mbox_mem_pool); + if (phba->sli_rev == LPFC_SLI_REV4) + lpfc_sli4_free_rpi(phba, *rpi); + return -ENOMEM; + } + + dmabuff = (struct lpfc_dmabuf *)mbox->ctx_buf; + mbox->ctx_buf = NULL; + mbox->ctx_ndlp = NULL; + status = lpfc_sli_issue_mbox_wait(phba, mbox, LPFC_MBOX_TMO); + + if ((status != MBX_SUCCESS) || (mbox->u.mb.mbxStatus)) { + lpfc_mbuf_free(phba, dmabuff->virt, dmabuff->phys); + kfree(dmabuff); + if (status != MBX_TIMEOUT) + mempool_free(mbox, phba->mbox_mem_pool); + if (phba->sli_rev == LPFC_SLI_REV4) + lpfc_sli4_free_rpi(phba, *rpi); + return -ENODEV; + } + + if (phba->sli_rev < LPFC_SLI_REV4) + *rpi = mbox->u.mb.un.varWords[0]; + + lpfc_mbuf_free(phba, dmabuff->virt, dmabuff->phys); + kfree(dmabuff); + mempool_free(mbox, phba->mbox_mem_pool); + return 0; +} + +/** + * lpfcdiag_loop_self_unreg - unregs from the rpi + * @phba: Pointer to HBA context object + * @rpi: Remote port login id + * + * This function unregisters the rpi obtained in lpfcdiag_loop_self_reg + **/ +static int lpfcdiag_loop_self_unreg(struct lpfc_hba *phba, uint16_t rpi) +{ + LPFC_MBOXQ_t *mbox; + int status; + + /* Allocate mboxq structure */ + mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (mbox == NULL) + return -ENOMEM; + + if (phba->sli_rev < LPFC_SLI_REV4) + lpfc_unreg_login(phba, 0, rpi, mbox); + else + lpfc_unreg_login(phba, phba->pport->vpi, + phba->sli4_hba.rpi_ids[rpi], mbox); + + status = lpfc_sli_issue_mbox_wait(phba, mbox, LPFC_MBOX_TMO); + + if ((status != MBX_SUCCESS) || (mbox->u.mb.mbxStatus)) { + if (status != MBX_TIMEOUT) + mempool_free(mbox, phba->mbox_mem_pool); + return -EIO; + } + mempool_free(mbox, phba->mbox_mem_pool); + if (phba->sli_rev == LPFC_SLI_REV4) + lpfc_sli4_free_rpi(phba, rpi); + return 0; +} + +/** + * lpfcdiag_loop_get_xri - obtains the transmit and receive ids + * @phba: Pointer to HBA context object + * @rpi: Remote port login id + * @txxri: Pointer to transmit exchange id + * @rxxri: Pointer to response exchabge id + * + * This function obtains the transmit and receive ids required to send + * an unsolicited ct command with a payload. A special lpfc FsType and CmdRsp + * flags are used to the unsolicited response handler is able to process + * the ct command sent on the same port. + **/ +static int lpfcdiag_loop_get_xri(struct lpfc_hba *phba, uint16_t rpi, + uint16_t *txxri, uint16_t * rxxri) +{ + struct lpfc_bsg_event *evt; + struct lpfc_iocbq *cmdiocbq, *rspiocbq; + struct lpfc_dmabuf *dmabuf; + struct ulp_bde64 *bpl = NULL; + struct lpfc_sli_ct_request *ctreq = NULL; + int ret_val = 0; + int time_left; + int iocb_stat = IOCB_SUCCESS; + unsigned long flags; + u32 status; + + *txxri = 0; + *rxxri = 0; + evt = lpfc_bsg_event_new(FC_REG_CT_EVENT, current->pid, + SLI_CT_ELX_LOOPBACK); + if (!evt) + return -ENOMEM; + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_add(&evt->node, &phba->ct_ev_waiters); + lpfc_bsg_event_ref(evt); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + cmdiocbq = lpfc_sli_get_iocbq(phba); + rspiocbq = lpfc_sli_get_iocbq(phba); + + dmabuf = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (dmabuf) { + dmabuf->virt = lpfc_mbuf_alloc(phba, 0, &dmabuf->phys); + if (dmabuf->virt) { + INIT_LIST_HEAD(&dmabuf->list); + bpl = (struct ulp_bde64 *) dmabuf->virt; + memset(bpl, 0, sizeof(*bpl)); + ctreq = (struct lpfc_sli_ct_request *)(bpl + 1); + bpl->addrHigh = + le32_to_cpu(putPaddrHigh(dmabuf->phys + + sizeof(*bpl))); + bpl->addrLow = + le32_to_cpu(putPaddrLow(dmabuf->phys + + sizeof(*bpl))); + bpl->tus.f.bdeFlags = 0; + bpl->tus.f.bdeSize = ELX_LOOPBACK_HEADER_SZ; + bpl->tus.w = le32_to_cpu(bpl->tus.w); + } + } + + if (cmdiocbq == NULL || rspiocbq == NULL || + dmabuf == NULL || bpl == NULL || ctreq == NULL || + dmabuf->virt == NULL) { + ret_val = -ENOMEM; + goto err_get_xri_exit; + } + + memset(ctreq, 0, ELX_LOOPBACK_HEADER_SZ); + + ctreq->RevisionId.bits.Revision = SLI_CT_REVISION; + ctreq->RevisionId.bits.InId = 0; + ctreq->FsType = SLI_CT_ELX_LOOPBACK; + ctreq->FsSubType = 0; + ctreq->CommandResponse.bits.CmdRsp = ELX_LOOPBACK_XRI_SETUP; + ctreq->CommandResponse.bits.Size = 0; + + cmdiocbq->bpl_dmabuf = dmabuf; + cmdiocbq->cmd_flag |= LPFC_IO_LIBDFC; + cmdiocbq->vport = phba->pport; + cmdiocbq->cmd_cmpl = NULL; + + lpfc_sli_prep_xmit_seq64(phba, cmdiocbq, dmabuf, rpi, 0, 1, + FC_RCTL_DD_SOL_CTL, 0, CMD_XMIT_SEQUENCE64_CR); + + iocb_stat = lpfc_sli_issue_iocb_wait(phba, LPFC_ELS_RING, cmdiocbq, + rspiocbq, (phba->fc_ratov * 2) + + LPFC_DRVR_TIMEOUT); + + status = get_job_ulpstatus(phba, rspiocbq); + if (iocb_stat != IOCB_SUCCESS || status != IOCB_SUCCESS) { + ret_val = -EIO; + goto err_get_xri_exit; + } + *txxri = get_job_ulpcontext(phba, rspiocbq); + + evt->waiting = 1; + evt->wait_time_stamp = jiffies; + time_left = wait_event_interruptible_timeout( + evt->wq, !list_empty(&evt->events_to_see), + msecs_to_jiffies(1000 * + ((phba->fc_ratov * 2) + LPFC_DRVR_TIMEOUT))); + if (list_empty(&evt->events_to_see)) + ret_val = (time_left) ? -EINTR : -ETIMEDOUT; + else { + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_move(evt->events_to_see.prev, &evt->events_to_get); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + *rxxri = (list_entry(evt->events_to_get.prev, + typeof(struct event_data), + node))->immed_dat; + } + evt->waiting = 0; + +err_get_xri_exit: + spin_lock_irqsave(&phba->ct_ev_lock, flags); + lpfc_bsg_event_unref(evt); /* release ref */ + lpfc_bsg_event_unref(evt); /* delete */ + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + if (dmabuf) { + if (dmabuf->virt) + lpfc_mbuf_free(phba, dmabuf->virt, dmabuf->phys); + kfree(dmabuf); + } + + if (cmdiocbq && (iocb_stat != IOCB_TIMEDOUT)) + lpfc_sli_release_iocbq(phba, cmdiocbq); + if (rspiocbq) + lpfc_sli_release_iocbq(phba, rspiocbq); + return ret_val; +} + +/** + * lpfc_bsg_dma_page_alloc - allocate a bsg mbox page sized dma buffers + * @phba: Pointer to HBA context object + * + * This function allocates BSG_MBOX_SIZE (4KB) page size dma buffer and + * returns the pointer to the buffer. + **/ +static struct lpfc_dmabuf * +lpfc_bsg_dma_page_alloc(struct lpfc_hba *phba) +{ + struct lpfc_dmabuf *dmabuf; + struct pci_dev *pcidev = phba->pcidev; + + /* allocate dma buffer struct */ + dmabuf = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!dmabuf) + return NULL; + + INIT_LIST_HEAD(&dmabuf->list); + + /* now, allocate dma buffer */ + dmabuf->virt = dma_alloc_coherent(&pcidev->dev, BSG_MBOX_SIZE, + &(dmabuf->phys), GFP_KERNEL); + + if (!dmabuf->virt) { + kfree(dmabuf); + return NULL; + } + + return dmabuf; +} + +/** + * lpfc_bsg_dma_page_free - free a bsg mbox page sized dma buffer + * @phba: Pointer to HBA context object. + * @dmabuf: Pointer to the bsg mbox page sized dma buffer descriptor. + * + * This routine just simply frees a dma buffer and its associated buffer + * descriptor referred by @dmabuf. + **/ +static void +lpfc_bsg_dma_page_free(struct lpfc_hba *phba, struct lpfc_dmabuf *dmabuf) +{ + struct pci_dev *pcidev = phba->pcidev; + + if (!dmabuf) + return; + + if (dmabuf->virt) + dma_free_coherent(&pcidev->dev, BSG_MBOX_SIZE, + dmabuf->virt, dmabuf->phys); + kfree(dmabuf); + return; +} + +/** + * lpfc_bsg_dma_page_list_free - free a list of bsg mbox page sized dma buffers + * @phba: Pointer to HBA context object. + * @dmabuf_list: Pointer to a list of bsg mbox page sized dma buffer descs. + * + * This routine just simply frees all dma buffers and their associated buffer + * descriptors referred by @dmabuf_list. + **/ +static void +lpfc_bsg_dma_page_list_free(struct lpfc_hba *phba, + struct list_head *dmabuf_list) +{ + struct lpfc_dmabuf *dmabuf, *next_dmabuf; + + if (list_empty(dmabuf_list)) + return; + + list_for_each_entry_safe(dmabuf, next_dmabuf, dmabuf_list, list) { + list_del_init(&dmabuf->list); + lpfc_bsg_dma_page_free(phba, dmabuf); + } + return; +} + +/** + * diag_cmd_data_alloc - fills in a bde struct with dma buffers + * @phba: Pointer to HBA context object + * @bpl: Pointer to 64 bit bde structure + * @size: Number of bytes to process + * @nocopydata: Flag to copy user data into the allocated buffer + * + * This function allocates page size buffers and populates an lpfc_dmabufext. + * If allowed the user data pointed to with indataptr is copied into the kernel + * memory. The chained list of page size buffers is returned. + **/ +static struct lpfc_dmabufext * +diag_cmd_data_alloc(struct lpfc_hba *phba, + struct ulp_bde64 *bpl, uint32_t size, + int nocopydata) +{ + struct lpfc_dmabufext *mlist = NULL; + struct lpfc_dmabufext *dmp; + int cnt, offset = 0, i = 0; + struct pci_dev *pcidev; + + pcidev = phba->pcidev; + + while (size) { + /* We get chunks of 4K */ + if (size > BUF_SZ_4K) + cnt = BUF_SZ_4K; + else + cnt = size; + + /* allocate struct lpfc_dmabufext buffer header */ + dmp = kmalloc(sizeof(struct lpfc_dmabufext), GFP_KERNEL); + if (!dmp) + goto out; + + INIT_LIST_HEAD(&dmp->dma.list); + + /* Queue it to a linked list */ + if (mlist) + list_add_tail(&dmp->dma.list, &mlist->dma.list); + else + mlist = dmp; + + /* allocate buffer */ + dmp->dma.virt = dma_alloc_coherent(&pcidev->dev, + cnt, + &(dmp->dma.phys), + GFP_KERNEL); + + if (!dmp->dma.virt) + goto out; + + dmp->size = cnt; + + if (nocopydata) { + bpl->tus.f.bdeFlags = 0; + } else { + memset((uint8_t *)dmp->dma.virt, 0, cnt); + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; + } + + /* build buffer ptr list for IOCB */ + bpl->addrLow = le32_to_cpu(putPaddrLow(dmp->dma.phys)); + bpl->addrHigh = le32_to_cpu(putPaddrHigh(dmp->dma.phys)); + bpl->tus.f.bdeSize = (ushort) cnt; + bpl->tus.w = le32_to_cpu(bpl->tus.w); + bpl++; + + i++; + offset += cnt; + size -= cnt; + } + + if (mlist) { + mlist->flag = i; + return mlist; + } +out: + diag_cmd_data_free(phba, mlist); + return NULL; +} + +/** + * lpfcdiag_sli3_loop_post_rxbufs - post the receive buffers for an unsol CT cmd + * @phba: Pointer to HBA context object + * @rxxri: Receive exchange id + * @len: Number of data bytes + * + * This function allocates and posts a data buffer of sufficient size to receive + * an unsolicited CT command. + **/ +static int lpfcdiag_sli3_loop_post_rxbufs(struct lpfc_hba *phba, uint16_t rxxri, + size_t len) +{ + struct lpfc_sli_ring *pring; + struct lpfc_iocbq *cmdiocbq; + IOCB_t *cmd = NULL; + struct list_head head, *curr, *next; + struct lpfc_dmabuf *rxbmp; + struct lpfc_dmabuf *dmp; + struct lpfc_dmabuf *mp[2] = {NULL, NULL}; + struct ulp_bde64 *rxbpl = NULL; + uint32_t num_bde; + struct lpfc_dmabufext *rxbuffer = NULL; + int ret_val = 0; + int iocb_stat; + int i = 0; + + pring = lpfc_phba_elsring(phba); + + cmdiocbq = lpfc_sli_get_iocbq(phba); + rxbmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (rxbmp != NULL) { + rxbmp->virt = lpfc_mbuf_alloc(phba, 0, &rxbmp->phys); + if (rxbmp->virt) { + INIT_LIST_HEAD(&rxbmp->list); + rxbpl = (struct ulp_bde64 *) rxbmp->virt; + rxbuffer = diag_cmd_data_alloc(phba, rxbpl, len, 0); + } + } + + if (!cmdiocbq || !rxbmp || !rxbpl || !rxbuffer || !pring) { + ret_val = -ENOMEM; + goto err_post_rxbufs_exit; + } + + /* Queue buffers for the receive exchange */ + num_bde = (uint32_t)rxbuffer->flag; + dmp = &rxbuffer->dma; + cmd = &cmdiocbq->iocb; + i = 0; + + INIT_LIST_HEAD(&head); + list_add_tail(&head, &dmp->list); + list_for_each_safe(curr, next, &head) { + mp[i] = list_entry(curr, struct lpfc_dmabuf, list); + list_del(curr); + + if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { + mp[i]->buffer_tag = lpfc_sli_get_buffer_tag(phba); + cmd->un.quexri64cx.buff.bde.addrHigh = + putPaddrHigh(mp[i]->phys); + cmd->un.quexri64cx.buff.bde.addrLow = + putPaddrLow(mp[i]->phys); + cmd->un.quexri64cx.buff.bde.tus.f.bdeSize = + ((struct lpfc_dmabufext *)mp[i])->size; + cmd->un.quexri64cx.buff.buffer_tag = mp[i]->buffer_tag; + cmd->ulpCommand = CMD_QUE_XRI64_CX; + cmd->ulpPU = 0; + cmd->ulpLe = 1; + cmd->ulpBdeCount = 1; + cmd->unsli3.que_xri64cx_ext_words.ebde_count = 0; + + } else { + cmd->un.cont64[i].addrHigh = putPaddrHigh(mp[i]->phys); + cmd->un.cont64[i].addrLow = putPaddrLow(mp[i]->phys); + cmd->un.cont64[i].tus.f.bdeSize = + ((struct lpfc_dmabufext *)mp[i])->size; + cmd->ulpBdeCount = ++i; + + if ((--num_bde > 0) && (i < 2)) + continue; + + cmd->ulpCommand = CMD_QUE_XRI_BUF64_CX; + cmd->ulpLe = 1; + } + + cmd->ulpClass = CLASS3; + cmd->ulpContext = rxxri; + + iocb_stat = lpfc_sli_issue_iocb(phba, LPFC_ELS_RING, cmdiocbq, + 0); + if (iocb_stat == IOCB_ERROR) { + diag_cmd_data_free(phba, + (struct lpfc_dmabufext *)mp[0]); + if (mp[1]) + diag_cmd_data_free(phba, + (struct lpfc_dmabufext *)mp[1]); + dmp = list_entry(next, struct lpfc_dmabuf, list); + ret_val = -EIO; + goto err_post_rxbufs_exit; + } + + lpfc_sli_ringpostbuf_put(phba, pring, mp[0]); + if (mp[1]) { + lpfc_sli_ringpostbuf_put(phba, pring, mp[1]); + mp[1] = NULL; + } + + /* The iocb was freed by lpfc_sli_issue_iocb */ + cmdiocbq = lpfc_sli_get_iocbq(phba); + if (!cmdiocbq) { + dmp = list_entry(next, struct lpfc_dmabuf, list); + ret_val = -EIO; + goto err_post_rxbufs_exit; + } + cmd = &cmdiocbq->iocb; + i = 0; + } + list_del(&head); + +err_post_rxbufs_exit: + + if (rxbmp) { + if (rxbmp->virt) + lpfc_mbuf_free(phba, rxbmp->virt, rxbmp->phys); + kfree(rxbmp); + } + + if (cmdiocbq) + lpfc_sli_release_iocbq(phba, cmdiocbq); + return ret_val; +} + +/** + * lpfc_bsg_diag_loopback_run - run loopback on a port by issue ct cmd to itself + * @job: LPFC_BSG_VENDOR_DIAG_TEST fc_bsg_job + * + * This function receives a user data buffer to be transmitted and received on + * the same port, the link must be up and in loopback mode prior + * to being called. + * 1. A kernel buffer is allocated to copy the user data into. + * 2. The port registers with "itself". + * 3. The transmit and receive exchange ids are obtained. + * 4. The receive exchange id is posted. + * 5. A new els loopback event is created. + * 6. The command and response iocbs are allocated. + * 7. The cmd iocb FsType is set to elx loopback and the CmdRsp to looppback. + * + * This function is meant to be called n times while the port is in loopback + * so it is the apps responsibility to issue a reset to take the port out + * of loopback mode. + **/ +static int +lpfc_bsg_diag_loopback_run(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct fc_bsg_reply *bsg_reply = job->reply; + struct lpfc_hba *phba = vport->phba; + struct lpfc_bsg_event *evt; + struct event_data *evdat; + struct lpfc_sli *psli = &phba->sli; + uint32_t size; + uint32_t full_size; + size_t segment_len = 0, segment_offset = 0, current_offset = 0; + uint16_t rpi = 0; + struct lpfc_iocbq *cmdiocbq, *rspiocbq = NULL; + union lpfc_wqe128 *cmdwqe, *rspwqe; + struct lpfc_sli_ct_request *ctreq; + struct lpfc_dmabuf *txbmp; + struct ulp_bde64 *txbpl = NULL; + struct lpfc_dmabufext *txbuffer = NULL; + struct list_head head; + struct lpfc_dmabuf *curr; + uint16_t txxri = 0, rxxri; + uint32_t num_bde; + uint8_t *ptr = NULL, *rx_databuf = NULL; + int rc = 0; + int time_left; + int iocb_stat = IOCB_SUCCESS; + unsigned long flags; + void *dataout = NULL; + uint32_t total_mem; + + /* in case no data is returned return just the return code */ + bsg_reply->reply_payload_rcv_len = 0; + + if (job->request_len < + sizeof(struct fc_bsg_request) + sizeof(struct diag_mode_test)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2739 Received DIAG TEST request below minimum " + "size\n"); + rc = -EINVAL; + goto loopback_test_exit; + } + + if (job->request_payload.payload_len != + job->reply_payload.payload_len) { + rc = -EINVAL; + goto loopback_test_exit; + } + + if ((phba->link_state == LPFC_HBA_ERROR) || + (psli->sli_flag & LPFC_BLOCK_MGMT_IO) || + (!(psli->sli_flag & LPFC_SLI_ACTIVE))) { + rc = -EACCES; + goto loopback_test_exit; + } + + if (!lpfc_is_link_up(phba) || !(phba->link_flag & LS_LOOPBACK_MODE)) { + rc = -EACCES; + goto loopback_test_exit; + } + + size = job->request_payload.payload_len; + full_size = size + ELX_LOOPBACK_HEADER_SZ; /* plus the header */ + + if ((size == 0) || (size > 80 * BUF_SZ_4K)) { + rc = -ERANGE; + goto loopback_test_exit; + } + + if (full_size >= BUF_SZ_4K) { + /* + * Allocate memory for ioctl data. If buffer is bigger than 64k, + * then we allocate 64k and re-use that buffer over and over to + * xfer the whole block. This is because Linux kernel has a + * problem allocating more than 120k of kernel space memory. Saw + * problem with GET_FCPTARGETMAPPING... + */ + if (size <= (64 * 1024)) + total_mem = full_size; + else + total_mem = 64 * 1024; + } else + /* Allocate memory for ioctl data */ + total_mem = BUF_SZ_4K; + + dataout = kmalloc(total_mem, GFP_KERNEL); + if (dataout == NULL) { + rc = -ENOMEM; + goto loopback_test_exit; + } + + ptr = dataout; + ptr += ELX_LOOPBACK_HEADER_SZ; + sg_copy_to_buffer(job->request_payload.sg_list, + job->request_payload.sg_cnt, + ptr, size); + rc = lpfcdiag_loop_self_reg(phba, &rpi); + if (rc) + goto loopback_test_exit; + + if (phba->sli_rev < LPFC_SLI_REV4) { + rc = lpfcdiag_loop_get_xri(phba, rpi, &txxri, &rxxri); + if (rc) { + lpfcdiag_loop_self_unreg(phba, rpi); + goto loopback_test_exit; + } + + rc = lpfcdiag_sli3_loop_post_rxbufs(phba, rxxri, full_size); + if (rc) { + lpfcdiag_loop_self_unreg(phba, rpi); + goto loopback_test_exit; + } + } + evt = lpfc_bsg_event_new(FC_REG_CT_EVENT, current->pid, + SLI_CT_ELX_LOOPBACK); + if (!evt) { + lpfcdiag_loop_self_unreg(phba, rpi); + rc = -ENOMEM; + goto loopback_test_exit; + } + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_add(&evt->node, &phba->ct_ev_waiters); + lpfc_bsg_event_ref(evt); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + cmdiocbq = lpfc_sli_get_iocbq(phba); + if (phba->sli_rev < LPFC_SLI_REV4) + rspiocbq = lpfc_sli_get_iocbq(phba); + txbmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + + if (txbmp) { + txbmp->virt = lpfc_mbuf_alloc(phba, 0, &txbmp->phys); + if (txbmp->virt) { + INIT_LIST_HEAD(&txbmp->list); + txbpl = (struct ulp_bde64 *) txbmp->virt; + txbuffer = diag_cmd_data_alloc(phba, + txbpl, full_size, 0); + } + } + + if (!cmdiocbq || !txbmp || !txbpl || !txbuffer || !txbmp->virt) { + rc = -ENOMEM; + goto err_loopback_test_exit; + } + if ((phba->sli_rev < LPFC_SLI_REV4) && !rspiocbq) { + rc = -ENOMEM; + goto err_loopback_test_exit; + } + + cmdwqe = &cmdiocbq->wqe; + memset(cmdwqe, 0, sizeof(union lpfc_wqe)); + if (phba->sli_rev < LPFC_SLI_REV4) { + rspwqe = &rspiocbq->wqe; + memset(rspwqe, 0, sizeof(union lpfc_wqe)); + } + + INIT_LIST_HEAD(&head); + list_add_tail(&head, &txbuffer->dma.list); + list_for_each_entry(curr, &head, list) { + segment_len = ((struct lpfc_dmabufext *)curr)->size; + if (current_offset == 0) { + ctreq = curr->virt; + memset(ctreq, 0, ELX_LOOPBACK_HEADER_SZ); + ctreq->RevisionId.bits.Revision = SLI_CT_REVISION; + ctreq->RevisionId.bits.InId = 0; + ctreq->FsType = SLI_CT_ELX_LOOPBACK; + ctreq->FsSubType = 0; + ctreq->CommandResponse.bits.CmdRsp = cpu_to_be16(ELX_LOOPBACK_DATA); + ctreq->CommandResponse.bits.Size = cpu_to_be16(size); + segment_offset = ELX_LOOPBACK_HEADER_SZ; + } else + segment_offset = 0; + + BUG_ON(segment_offset >= segment_len); + memcpy(curr->virt + segment_offset, + ptr + current_offset, + segment_len - segment_offset); + + current_offset += segment_len - segment_offset; + BUG_ON(current_offset > size); + } + list_del(&head); + + /* Build the XMIT_SEQUENCE iocb */ + num_bde = (uint32_t)txbuffer->flag; + + cmdiocbq->num_bdes = num_bde; + cmdiocbq->cmd_flag |= LPFC_IO_LIBDFC; + cmdiocbq->cmd_flag |= LPFC_IO_LOOPBACK; + cmdiocbq->vport = phba->pport; + cmdiocbq->cmd_cmpl = NULL; + cmdiocbq->bpl_dmabuf = txbmp; + + if (phba->sli_rev < LPFC_SLI_REV4) { + lpfc_sli_prep_xmit_seq64(phba, cmdiocbq, txbmp, 0, txxri, + num_bde, FC_RCTL_DD_UNSOL_CTL, 1, + CMD_XMIT_SEQUENCE64_CX); + + } else { + lpfc_sli_prep_xmit_seq64(phba, cmdiocbq, txbmp, + phba->sli4_hba.rpi_ids[rpi], 0xffff, + full_size, FC_RCTL_DD_UNSOL_CTL, 1, + CMD_XMIT_SEQUENCE64_WQE); + cmdiocbq->sli4_xritag = NO_XRI; + } + + iocb_stat = lpfc_sli_issue_iocb_wait(phba, LPFC_ELS_RING, cmdiocbq, + rspiocbq, (phba->fc_ratov * 2) + + LPFC_DRVR_TIMEOUT); + if (iocb_stat != IOCB_SUCCESS || + (phba->sli_rev < LPFC_SLI_REV4 && + (get_job_ulpstatus(phba, rspiocbq) != IOSTAT_SUCCESS))) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "3126 Failed loopback test issue iocb: " + "iocb_stat:x%x\n", iocb_stat); + rc = -EIO; + goto err_loopback_test_exit; + } + + evt->waiting = 1; + time_left = wait_event_interruptible_timeout( + evt->wq, !list_empty(&evt->events_to_see), + msecs_to_jiffies(1000 * + ((phba->fc_ratov * 2) + LPFC_DRVR_TIMEOUT))); + evt->waiting = 0; + if (list_empty(&evt->events_to_see)) { + rc = (time_left) ? -EINTR : -ETIMEDOUT; + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "3125 Not receiving unsolicited event, " + "rc:x%x\n", rc); + } else { + spin_lock_irqsave(&phba->ct_ev_lock, flags); + list_move(evt->events_to_see.prev, &evt->events_to_get); + evdat = list_entry(evt->events_to_get.prev, + typeof(*evdat), node); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + rx_databuf = evdat->data; + if (evdat->len != full_size) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1603 Loopback test did not receive expected " + "data length. actual length 0x%x expected " + "length 0x%x\n", + evdat->len, full_size); + rc = -EIO; + } else if (rx_databuf == NULL) + rc = -EIO; + else { + rc = IOCB_SUCCESS; + /* skip over elx loopback header */ + rx_databuf += ELX_LOOPBACK_HEADER_SZ; + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + rx_databuf, size); + bsg_reply->reply_payload_rcv_len = size; + } + } + +err_loopback_test_exit: + lpfcdiag_loop_self_unreg(phba, rpi); + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + lpfc_bsg_event_unref(evt); /* release ref */ + lpfc_bsg_event_unref(evt); /* delete */ + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + if ((cmdiocbq != NULL) && (iocb_stat != IOCB_TIMEDOUT)) + lpfc_sli_release_iocbq(phba, cmdiocbq); + + if (rspiocbq != NULL) + lpfc_sli_release_iocbq(phba, rspiocbq); + + if (txbmp != NULL) { + if (txbpl != NULL) { + if (txbuffer != NULL) + diag_cmd_data_free(phba, txbuffer); + lpfc_mbuf_free(phba, txbmp->virt, txbmp->phys); + } + kfree(txbmp); + } + +loopback_test_exit: + kfree(dataout); + /* make error code available to userspace */ + bsg_reply->result = rc; + job->dd_data = NULL; + /* complete the job back to userspace if no error */ + if (rc == IOCB_SUCCESS) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfc_bsg_get_dfc_rev - process a GET_DFC_REV bsg vendor command + * @job: GET_DFC_REV fc_bsg_job + **/ +static int +lpfc_bsg_get_dfc_rev(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct fc_bsg_reply *bsg_reply = job->reply; + struct lpfc_hba *phba = vport->phba; + struct get_mgmt_rev_reply *event_reply; + int rc = 0; + + if (job->request_len < + sizeof(struct fc_bsg_request) + sizeof(struct get_mgmt_rev)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2740 Received GET_DFC_REV request below " + "minimum size\n"); + rc = -EINVAL; + goto job_error; + } + + event_reply = (struct get_mgmt_rev_reply *) + bsg_reply->reply_data.vendor_reply.vendor_rsp; + + if (job->reply_len < sizeof(*bsg_reply) + sizeof(*event_reply)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2741 Received GET_DFC_REV reply below " + "minimum size\n"); + rc = -EINVAL; + goto job_error; + } + + event_reply->info.a_Major = MANAGEMENT_MAJOR_REV; + event_reply->info.a_Minor = MANAGEMENT_MINOR_REV; +job_error: + bsg_reply->result = rc; + if (rc == 0) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfc_bsg_issue_mbox_cmpl - lpfc_bsg_issue_mbox mbox completion handler + * @phba: Pointer to HBA context object. + * @pmboxq: Pointer to mailbox command. + * + * This is completion handler function for mailbox commands issued from + * lpfc_bsg_issue_mbox function. This function is called by the + * mailbox event handler function with no lock held. This function + * will wake up thread waiting on the wait queue pointed by dd_data + * of the mailbox. + **/ +static void +lpfc_bsg_issue_mbox_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) +{ + struct bsg_job_data *dd_data; + struct fc_bsg_reply *bsg_reply; + struct bsg_job *job; + uint32_t size; + unsigned long flags; + uint8_t *pmb, *pmb_buf; + + dd_data = pmboxq->ctx_ndlp; + + /* + * The outgoing buffer is readily referred from the dma buffer, + * just need to get header part from mailboxq structure. + */ + pmb = (uint8_t *)&pmboxq->u.mb; + pmb_buf = (uint8_t *)dd_data->context_un.mbox.mb; + memcpy(pmb_buf, pmb, sizeof(MAILBOX_t)); + + /* Determine if job has been aborted */ + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Copy the mailbox data to the job if it is still active */ + + if (job) { + bsg_reply = job->reply; + size = job->reply_payload.payload_len; + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + pmb_buf, size); + } + + dd_data->set_job = NULL; + mempool_free(dd_data->context_un.mbox.pmboxq, phba->mbox_mem_pool); + lpfc_bsg_dma_page_free(phba, dd_data->context_un.mbox.dmabuffers); + kfree(dd_data); + + /* Complete the job if the job is still active */ + + if (job) { + bsg_reply->result = 0; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + } + return; +} + +/** + * lpfc_bsg_check_cmd_access - test for a supported mailbox command + * @phba: Pointer to HBA context object. + * @mb: Pointer to a mailbox object. + * @vport: Pointer to a vport object. + * + * Some commands require the port to be offline, some may not be called from + * the application. + **/ +static int lpfc_bsg_check_cmd_access(struct lpfc_hba *phba, + MAILBOX_t *mb, struct lpfc_vport *vport) +{ + /* return negative error values for bsg job */ + switch (mb->mbxCommand) { + /* Offline only */ + case MBX_INIT_LINK: + case MBX_DOWN_LINK: + case MBX_CONFIG_LINK: + case MBX_CONFIG_RING: + case MBX_RESET_RING: + case MBX_UNREG_LOGIN: + case MBX_CLEAR_LA: + case MBX_DUMP_CONTEXT: + case MBX_RUN_DIAGS: + case MBX_RESTART: + case MBX_SET_MASK: + if (!(vport->fc_flag & FC_OFFLINE_MODE)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2743 Command 0x%x is illegal in on-line " + "state\n", + mb->mbxCommand); + return -EPERM; + } + break; + case MBX_WRITE_NV: + case MBX_WRITE_VPARMS: + case MBX_LOAD_SM: + case MBX_READ_NV: + case MBX_READ_CONFIG: + case MBX_READ_RCONFIG: + case MBX_READ_STATUS: + case MBX_READ_XRI: + case MBX_READ_REV: + case MBX_READ_LNK_STAT: + case MBX_DUMP_MEMORY: + case MBX_DOWN_LOAD: + case MBX_UPDATE_CFG: + case MBX_KILL_BOARD: + case MBX_READ_TOPOLOGY: + case MBX_LOAD_AREA: + case MBX_LOAD_EXP_ROM: + case MBX_BEACON: + case MBX_DEL_LD_ENTRY: + case MBX_SET_DEBUG: + case MBX_WRITE_WWN: + case MBX_SLI4_CONFIG: + case MBX_READ_EVENT_LOG: + case MBX_READ_EVENT_LOG_STATUS: + case MBX_WRITE_EVENT_LOG: + case MBX_PORT_CAPABILITIES: + case MBX_PORT_IOV_CONTROL: + case MBX_RUN_BIU_DIAG64: + break; + case MBX_SET_VARIABLE: + lpfc_printf_log(phba, KERN_INFO, LOG_INIT, + "1226 mbox: set_variable 0x%x, 0x%x\n", + mb->un.varWords[0], + mb->un.varWords[1]); + break; + case MBX_READ_SPARM64: + case MBX_REG_LOGIN: + case MBX_REG_LOGIN64: + case MBX_CONFIG_PORT: + case MBX_RUN_BIU_DIAG: + default: + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2742 Unknown Command 0x%x\n", + mb->mbxCommand); + return -EPERM; + } + + return 0; /* ok */ +} + +/** + * lpfc_bsg_mbox_ext_session_reset - clean up context of multi-buffer mbox session + * @phba: Pointer to HBA context object. + * + * This is routine clean up and reset BSG handling of multi-buffer mbox + * command session. + **/ +static void +lpfc_bsg_mbox_ext_session_reset(struct lpfc_hba *phba) +{ + if (phba->mbox_ext_buf_ctx.state == LPFC_BSG_MBOX_IDLE) + return; + + /* free all memory, including dma buffers */ + lpfc_bsg_dma_page_list_free(phba, + &phba->mbox_ext_buf_ctx.ext_dmabuf_list); + lpfc_bsg_dma_page_free(phba, phba->mbox_ext_buf_ctx.mbx_dmabuf); + /* multi-buffer write mailbox command pass-through complete */ + memset((char *)&phba->mbox_ext_buf_ctx, 0, + sizeof(struct lpfc_mbox_ext_buf_ctx)); + INIT_LIST_HEAD(&phba->mbox_ext_buf_ctx.ext_dmabuf_list); + + return; +} + +/** + * lpfc_bsg_issue_mbox_ext_handle_job - job handler for multi-buffer mbox cmpl + * @phba: Pointer to HBA context object. + * @pmboxq: Pointer to mailbox command. + * + * This is routine handles BSG job for mailbox commands completions with + * multiple external buffers. + **/ +static struct bsg_job * +lpfc_bsg_issue_mbox_ext_handle_job(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) +{ + struct bsg_job_data *dd_data; + struct bsg_job *job; + struct fc_bsg_reply *bsg_reply; + uint8_t *pmb, *pmb_buf; + unsigned long flags; + uint32_t size; + int rc = 0; + struct lpfc_dmabuf *dmabuf; + struct lpfc_sli_config_mbox *sli_cfg_mbx; + uint8_t *pmbx; + + dd_data = pmboxq->ctx_buf; + + /* Determine if job has been aborted */ + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + bsg_reply = job->reply; + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* + * The outgoing buffer is readily referred from the dma buffer, + * just need to get header part from mailboxq structure. + */ + + pmb = (uint8_t *)&pmboxq->u.mb; + pmb_buf = (uint8_t *)dd_data->context_un.mbox.mb; + /* Copy the byte swapped response mailbox back to the user */ + memcpy(pmb_buf, pmb, sizeof(MAILBOX_t)); + /* if there is any non-embedded extended data copy that too */ + dmabuf = phba->mbox_ext_buf_ctx.mbx_dmabuf; + sli_cfg_mbx = (struct lpfc_sli_config_mbox *)dmabuf->virt; + if (!bsg_bf_get(lpfc_mbox_hdr_emb, + &sli_cfg_mbx->un.sli_config_emb0_subsys.sli_config_hdr)) { + pmbx = (uint8_t *)dmabuf->virt; + /* byte swap the extended data following the mailbox command */ + lpfc_sli_pcimem_bcopy(&pmbx[sizeof(MAILBOX_t)], + &pmbx[sizeof(MAILBOX_t)], + sli_cfg_mbx->un.sli_config_emb0_subsys.mse[0].buf_len); + } + + /* Complete the job if the job is still active */ + + if (job) { + size = job->reply_payload.payload_len; + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + pmb_buf, size); + + /* result for successful */ + bsg_reply->result = 0; + + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2937 SLI_CONFIG ext-buffer mailbox command " + "(x%x/x%x) complete bsg job done, bsize:%d\n", + phba->mbox_ext_buf_ctx.nembType, + phba->mbox_ext_buf_ctx.mboxType, size); + lpfc_idiag_mbxacc_dump_bsg_mbox(phba, + phba->mbox_ext_buf_ctx.nembType, + phba->mbox_ext_buf_ctx.mboxType, + dma_ebuf, sta_pos_addr, + phba->mbox_ext_buf_ctx.mbx_dmabuf, 0); + } else { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2938 SLI_CONFIG ext-buffer mailbox " + "command (x%x/x%x) failure, rc:x%x\n", + phba->mbox_ext_buf_ctx.nembType, + phba->mbox_ext_buf_ctx.mboxType, rc); + } + + + /* state change */ + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_DONE; + kfree(dd_data); + return job; +} + +/** + * lpfc_bsg_issue_read_mbox_ext_cmpl - compl handler for multi-buffer read mbox + * @phba: Pointer to HBA context object. + * @pmboxq: Pointer to mailbox command. + * + * This is completion handler function for mailbox read commands with multiple + * external buffers. + **/ +static void +lpfc_bsg_issue_read_mbox_ext_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) +{ + struct bsg_job *job; + struct fc_bsg_reply *bsg_reply; + + job = lpfc_bsg_issue_mbox_ext_handle_job(phba, pmboxq); + + /* handle the BSG job with mailbox command */ + if (!job) + pmboxq->u.mb.mbxStatus = MBXERR_ERROR; + + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2939 SLI_CONFIG ext-buffer rd mailbox command " + "complete, ctxState:x%x, mbxStatus:x%x\n", + phba->mbox_ext_buf_ctx.state, pmboxq->u.mb.mbxStatus); + + if (pmboxq->u.mb.mbxStatus || phba->mbox_ext_buf_ctx.numBuf == 1) + lpfc_bsg_mbox_ext_session_reset(phba); + + /* free base driver mailbox structure memory */ + mempool_free(pmboxq, phba->mbox_mem_pool); + + /* if the job is still active, call job done */ + if (job) { + bsg_reply = job->reply; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + } + return; +} + +/** + * lpfc_bsg_issue_write_mbox_ext_cmpl - cmpl handler for multi-buffer write mbox + * @phba: Pointer to HBA context object. + * @pmboxq: Pointer to mailbox command. + * + * This is completion handler function for mailbox write commands with multiple + * external buffers. + **/ +static void +lpfc_bsg_issue_write_mbox_ext_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) +{ + struct bsg_job *job; + struct fc_bsg_reply *bsg_reply; + + job = lpfc_bsg_issue_mbox_ext_handle_job(phba, pmboxq); + + /* handle the BSG job with the mailbox command */ + if (!job) + pmboxq->u.mb.mbxStatus = MBXERR_ERROR; + + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2940 SLI_CONFIG ext-buffer wr mailbox command " + "complete, ctxState:x%x, mbxStatus:x%x\n", + phba->mbox_ext_buf_ctx.state, pmboxq->u.mb.mbxStatus); + + /* free all memory, including dma buffers */ + mempool_free(pmboxq, phba->mbox_mem_pool); + lpfc_bsg_mbox_ext_session_reset(phba); + + /* if the job is still active, call job done */ + if (job) { + bsg_reply = job->reply; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + } + + return; +} + +static void +lpfc_bsg_sli_cfg_dma_desc_setup(struct lpfc_hba *phba, enum nemb_type nemb_tp, + uint32_t index, struct lpfc_dmabuf *mbx_dmabuf, + struct lpfc_dmabuf *ext_dmabuf) +{ + struct lpfc_sli_config_mbox *sli_cfg_mbx; + + /* pointer to the start of mailbox command */ + sli_cfg_mbx = (struct lpfc_sli_config_mbox *)mbx_dmabuf->virt; + + if (nemb_tp == nemb_mse) { + if (index == 0) { + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_hi = + putPaddrHigh(mbx_dmabuf->phys + + sizeof(MAILBOX_t)); + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_lo = + putPaddrLow(mbx_dmabuf->phys + + sizeof(MAILBOX_t)); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2943 SLI_CONFIG(mse)[%d], " + "bufLen:%d, addrHi:x%x, addrLo:x%x\n", + index, + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].buf_len, + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_hi, + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_lo); + } else { + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_hi = + putPaddrHigh(ext_dmabuf->phys); + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_lo = + putPaddrLow(ext_dmabuf->phys); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2944 SLI_CONFIG(mse)[%d], " + "bufLen:%d, addrHi:x%x, addrLo:x%x\n", + index, + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].buf_len, + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_hi, + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[index].pa_lo); + } + } else { + if (index == 0) { + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_hi = + putPaddrHigh(mbx_dmabuf->phys + + sizeof(MAILBOX_t)); + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_lo = + putPaddrLow(mbx_dmabuf->phys + + sizeof(MAILBOX_t)); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3007 SLI_CONFIG(hbd)[%d], " + "bufLen:%d, addrHi:x%x, addrLo:x%x\n", + index, + bsg_bf_get(lpfc_mbox_sli_config_ecmn_hbd_len, + &sli_cfg_mbx->un. + sli_config_emb1_subsys.hbd[index]), + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_hi, + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_lo); + + } else { + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_hi = + putPaddrHigh(ext_dmabuf->phys); + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_lo = + putPaddrLow(ext_dmabuf->phys); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3008 SLI_CONFIG(hbd)[%d], " + "bufLen:%d, addrHi:x%x, addrLo:x%x\n", + index, + bsg_bf_get(lpfc_mbox_sli_config_ecmn_hbd_len, + &sli_cfg_mbx->un. + sli_config_emb1_subsys.hbd[index]), + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_hi, + sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[index].pa_lo); + } + } + return; +} + +/** + * lpfc_bsg_sli_cfg_read_cmd_ext - sli_config non-embedded mailbox cmd read + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @nemb_tp: Enumerate of non-embedded mailbox command type. + * @dmabuf: Pointer to a DMA buffer descriptor. + * + * This routine performs SLI_CONFIG (0x9B) read mailbox command operation with + * non-embedded external buffers. + **/ +static int +lpfc_bsg_sli_cfg_read_cmd_ext(struct lpfc_hba *phba, struct bsg_job *job, + enum nemb_type nemb_tp, + struct lpfc_dmabuf *dmabuf) +{ + struct fc_bsg_request *bsg_request = job->request; + struct lpfc_sli_config_mbox *sli_cfg_mbx; + struct dfc_mbox_req *mbox_req; + struct lpfc_dmabuf *curr_dmabuf, *next_dmabuf; + uint32_t ext_buf_cnt, ext_buf_index; + struct lpfc_dmabuf *ext_dmabuf = NULL; + struct bsg_job_data *dd_data = NULL; + LPFC_MBOXQ_t *pmboxq = NULL; + MAILBOX_t *pmb; + uint8_t *pmbx; + int rc, i; + + mbox_req = + (struct dfc_mbox_req *)bsg_request->rqst_data.h_vendor.vendor_cmd; + + /* pointer to the start of mailbox command */ + sli_cfg_mbx = (struct lpfc_sli_config_mbox *)dmabuf->virt; + + if (nemb_tp == nemb_mse) { + ext_buf_cnt = bsg_bf_get(lpfc_mbox_hdr_mse_cnt, + &sli_cfg_mbx->un.sli_config_emb0_subsys.sli_config_hdr); + if (ext_buf_cnt > LPFC_MBX_SLI_CONFIG_MAX_MSE) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2945 Handled SLI_CONFIG(mse) rd, " + "ext_buf_cnt(%d) out of range(%d)\n", + ext_buf_cnt, + LPFC_MBX_SLI_CONFIG_MAX_MSE); + rc = -ERANGE; + goto job_error; + } + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2941 Handled SLI_CONFIG(mse) rd, " + "ext_buf_cnt:%d\n", ext_buf_cnt); + } else { + /* sanity check on interface type for support */ + if (bf_get(lpfc_sli_intf_if_type, &phba->sli4_hba.sli_intf) < + LPFC_SLI_INTF_IF_TYPE_2) { + rc = -ENODEV; + goto job_error; + } + /* nemb_tp == nemb_hbd */ + ext_buf_cnt = sli_cfg_mbx->un.sli_config_emb1_subsys.hbd_count; + if (ext_buf_cnt > LPFC_MBX_SLI_CONFIG_MAX_HBD) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2946 Handled SLI_CONFIG(hbd) rd, " + "ext_buf_cnt(%d) out of range(%d)\n", + ext_buf_cnt, + LPFC_MBX_SLI_CONFIG_MAX_HBD); + rc = -ERANGE; + goto job_error; + } + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2942 Handled SLI_CONFIG(hbd) rd, " + "ext_buf_cnt:%d\n", ext_buf_cnt); + } + + /* before dma descriptor setup */ + lpfc_idiag_mbxacc_dump_bsg_mbox(phba, nemb_tp, mbox_rd, dma_mbox, + sta_pre_addr, dmabuf, ext_buf_cnt); + + /* reject non-embedded mailbox command with none external buffer */ + if (ext_buf_cnt == 0) { + rc = -EPERM; + goto job_error; + } else if (ext_buf_cnt > 1) { + /* additional external read buffers */ + for (i = 1; i < ext_buf_cnt; i++) { + ext_dmabuf = lpfc_bsg_dma_page_alloc(phba); + if (!ext_dmabuf) { + rc = -ENOMEM; + goto job_error; + } + list_add_tail(&ext_dmabuf->list, + &phba->mbox_ext_buf_ctx.ext_dmabuf_list); + } + } + + /* bsg tracking structure */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (!dd_data) { + rc = -ENOMEM; + goto job_error; + } + + /* mailbox command structure for base driver */ + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) { + rc = -ENOMEM; + goto job_error; + } + memset(pmboxq, 0, sizeof(LPFC_MBOXQ_t)); + + /* for the first external buffer */ + lpfc_bsg_sli_cfg_dma_desc_setup(phba, nemb_tp, 0, dmabuf, dmabuf); + + /* for the rest of external buffer descriptors if any */ + if (ext_buf_cnt > 1) { + ext_buf_index = 1; + list_for_each_entry_safe(curr_dmabuf, next_dmabuf, + &phba->mbox_ext_buf_ctx.ext_dmabuf_list, list) { + lpfc_bsg_sli_cfg_dma_desc_setup(phba, nemb_tp, + ext_buf_index, dmabuf, + curr_dmabuf); + ext_buf_index++; + } + } + + /* after dma descriptor setup */ + lpfc_idiag_mbxacc_dump_bsg_mbox(phba, nemb_tp, mbox_rd, dma_mbox, + sta_pos_addr, dmabuf, ext_buf_cnt); + + /* construct base driver mbox command */ + pmb = &pmboxq->u.mb; + pmbx = (uint8_t *)dmabuf->virt; + memcpy(pmb, pmbx, sizeof(*pmb)); + pmb->mbxOwner = OWN_HOST; + pmboxq->vport = phba->pport; + + /* multi-buffer handling context */ + phba->mbox_ext_buf_ctx.nembType = nemb_tp; + phba->mbox_ext_buf_ctx.mboxType = mbox_rd; + phba->mbox_ext_buf_ctx.numBuf = ext_buf_cnt; + phba->mbox_ext_buf_ctx.mbxTag = mbox_req->extMboxTag; + phba->mbox_ext_buf_ctx.seqNum = mbox_req->extSeqNum; + phba->mbox_ext_buf_ctx.mbx_dmabuf = dmabuf; + + /* callback for multi-buffer read mailbox command */ + pmboxq->mbox_cmpl = lpfc_bsg_issue_read_mbox_ext_cmpl; + + /* context fields to callback function */ + pmboxq->ctx_buf = dd_data; + dd_data->type = TYPE_MBOX; + dd_data->set_job = job; + dd_data->context_un.mbox.pmboxq = pmboxq; + dd_data->context_un.mbox.mb = (MAILBOX_t *)pmbx; + job->dd_data = dd_data; + + /* state change */ + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_PORT; + + /* + * Non-embedded mailbox subcommand data gets byte swapped here because + * the lower level driver code only does the first 64 mailbox words. + */ + if ((!bsg_bf_get(lpfc_mbox_hdr_emb, + &sli_cfg_mbx->un.sli_config_emb0_subsys.sli_config_hdr)) && + (nemb_tp == nemb_mse)) + lpfc_sli_pcimem_bcopy(&pmbx[sizeof(MAILBOX_t)], + &pmbx[sizeof(MAILBOX_t)], + sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[0].buf_len); + + rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_NOWAIT); + if ((rc == MBX_SUCCESS) || (rc == MBX_BUSY)) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2947 Issued SLI_CONFIG ext-buffer " + "mailbox command, rc:x%x\n", rc); + return SLI_CONFIG_HANDLED; + } + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2948 Failed to issue SLI_CONFIG ext-buffer " + "mailbox command, rc:x%x\n", rc); + rc = -EPIPE; + +job_error: + if (pmboxq) + mempool_free(pmboxq, phba->mbox_mem_pool); + lpfc_bsg_dma_page_list_free(phba, + &phba->mbox_ext_buf_ctx.ext_dmabuf_list); + kfree(dd_data); + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_IDLE; + return rc; +} + +/** + * lpfc_bsg_sli_cfg_write_cmd_ext - sli_config non-embedded mailbox cmd write + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @nemb_tp: Enumerate of non-embedded mailbox command type. + * @dmabuf: Pointer to a DMA buffer descriptor. + * + * This routine performs SLI_CONFIG (0x9B) write mailbox command operation with + * non-embedded external buffers. + **/ +static int +lpfc_bsg_sli_cfg_write_cmd_ext(struct lpfc_hba *phba, struct bsg_job *job, + enum nemb_type nemb_tp, + struct lpfc_dmabuf *dmabuf) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct dfc_mbox_req *mbox_req; + struct lpfc_sli_config_mbox *sli_cfg_mbx; + uint32_t ext_buf_cnt; + struct bsg_job_data *dd_data = NULL; + LPFC_MBOXQ_t *pmboxq = NULL; + MAILBOX_t *pmb; + uint8_t *mbx; + int rc = SLI_CONFIG_NOT_HANDLED, i; + + mbox_req = + (struct dfc_mbox_req *)bsg_request->rqst_data.h_vendor.vendor_cmd; + + /* pointer to the start of mailbox command */ + sli_cfg_mbx = (struct lpfc_sli_config_mbox *)dmabuf->virt; + + if (nemb_tp == nemb_mse) { + ext_buf_cnt = bsg_bf_get(lpfc_mbox_hdr_mse_cnt, + &sli_cfg_mbx->un.sli_config_emb0_subsys.sli_config_hdr); + if (ext_buf_cnt > LPFC_MBX_SLI_CONFIG_MAX_MSE) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2953 Failed SLI_CONFIG(mse) wr, " + "ext_buf_cnt(%d) out of range(%d)\n", + ext_buf_cnt, + LPFC_MBX_SLI_CONFIG_MAX_MSE); + return -ERANGE; + } + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2949 Handled SLI_CONFIG(mse) wr, " + "ext_buf_cnt:%d\n", ext_buf_cnt); + } else { + /* sanity check on interface type for support */ + if (bf_get(lpfc_sli_intf_if_type, &phba->sli4_hba.sli_intf) < + LPFC_SLI_INTF_IF_TYPE_2) + return -ENODEV; + /* nemb_tp == nemb_hbd */ + ext_buf_cnt = sli_cfg_mbx->un.sli_config_emb1_subsys.hbd_count; + if (ext_buf_cnt > LPFC_MBX_SLI_CONFIG_MAX_HBD) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2954 Failed SLI_CONFIG(hbd) wr, " + "ext_buf_cnt(%d) out of range(%d)\n", + ext_buf_cnt, + LPFC_MBX_SLI_CONFIG_MAX_HBD); + return -ERANGE; + } + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2950 Handled SLI_CONFIG(hbd) wr, " + "ext_buf_cnt:%d\n", ext_buf_cnt); + } + + /* before dma buffer descriptor setup */ + lpfc_idiag_mbxacc_dump_bsg_mbox(phba, nemb_tp, mbox_wr, dma_mbox, + sta_pre_addr, dmabuf, ext_buf_cnt); + + if (ext_buf_cnt == 0) + return -EPERM; + + /* for the first external buffer */ + lpfc_bsg_sli_cfg_dma_desc_setup(phba, nemb_tp, 0, dmabuf, dmabuf); + + /* after dma descriptor setup */ + lpfc_idiag_mbxacc_dump_bsg_mbox(phba, nemb_tp, mbox_wr, dma_mbox, + sta_pos_addr, dmabuf, ext_buf_cnt); + + /* log for looking forward */ + for (i = 1; i < ext_buf_cnt; i++) { + if (nemb_tp == nemb_mse) + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2951 SLI_CONFIG(mse), buf[%d]-length:%d\n", + i, sli_cfg_mbx->un.sli_config_emb0_subsys. + mse[i].buf_len); + else + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2952 SLI_CONFIG(hbd), buf[%d]-length:%d\n", + i, bsg_bf_get(lpfc_mbox_sli_config_ecmn_hbd_len, + &sli_cfg_mbx->un.sli_config_emb1_subsys. + hbd[i])); + } + + /* multi-buffer handling context */ + phba->mbox_ext_buf_ctx.nembType = nemb_tp; + phba->mbox_ext_buf_ctx.mboxType = mbox_wr; + phba->mbox_ext_buf_ctx.numBuf = ext_buf_cnt; + phba->mbox_ext_buf_ctx.mbxTag = mbox_req->extMboxTag; + phba->mbox_ext_buf_ctx.seqNum = mbox_req->extSeqNum; + phba->mbox_ext_buf_ctx.mbx_dmabuf = dmabuf; + + if (ext_buf_cnt == 1) { + /* bsg tracking structure */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (!dd_data) { + rc = -ENOMEM; + goto job_error; + } + + /* mailbox command structure for base driver */ + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) { + rc = -ENOMEM; + goto job_error; + } + memset(pmboxq, 0, sizeof(LPFC_MBOXQ_t)); + pmb = &pmboxq->u.mb; + mbx = (uint8_t *)dmabuf->virt; + memcpy(pmb, mbx, sizeof(*pmb)); + pmb->mbxOwner = OWN_HOST; + pmboxq->vport = phba->pport; + + /* callback for multi-buffer read mailbox command */ + pmboxq->mbox_cmpl = lpfc_bsg_issue_write_mbox_ext_cmpl; + + /* context fields to callback function */ + pmboxq->ctx_buf = dd_data; + dd_data->type = TYPE_MBOX; + dd_data->set_job = job; + dd_data->context_un.mbox.pmboxq = pmboxq; + dd_data->context_un.mbox.mb = (MAILBOX_t *)mbx; + job->dd_data = dd_data; + + /* state change */ + + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_PORT; + rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_NOWAIT); + if ((rc == MBX_SUCCESS) || (rc == MBX_BUSY)) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2955 Issued SLI_CONFIG ext-buffer " + "mailbox command, rc:x%x\n", rc); + return SLI_CONFIG_HANDLED; + } + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2956 Failed to issue SLI_CONFIG ext-buffer " + "mailbox command, rc:x%x\n", rc); + rc = -EPIPE; + goto job_error; + } + + /* wait for additional external buffers */ + + bsg_reply->result = 0; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return SLI_CONFIG_HANDLED; + +job_error: + if (pmboxq) + mempool_free(pmboxq, phba->mbox_mem_pool); + kfree(dd_data); + + return rc; +} + +/** + * lpfc_bsg_handle_sli_cfg_mbox - handle sli-cfg mailbox cmd with ext buffer + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @dmabuf: Pointer to a DMA buffer descriptor. + * + * This routine handles SLI_CONFIG (0x9B) mailbox command with non-embedded + * external buffers, including both 0x9B with non-embedded MSEs and 0x9B + * with embedded subsystem 0x1 and opcodes with external HBDs. + **/ +static int +lpfc_bsg_handle_sli_cfg_mbox(struct lpfc_hba *phba, struct bsg_job *job, + struct lpfc_dmabuf *dmabuf) +{ + struct lpfc_sli_config_mbox *sli_cfg_mbx; + uint32_t subsys; + uint32_t opcode; + int rc = SLI_CONFIG_NOT_HANDLED; + + /* state change on new multi-buffer pass-through mailbox command */ + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_HOST; + + sli_cfg_mbx = (struct lpfc_sli_config_mbox *)dmabuf->virt; + + if (!bsg_bf_get(lpfc_mbox_hdr_emb, + &sli_cfg_mbx->un.sli_config_emb0_subsys.sli_config_hdr)) { + subsys = bsg_bf_get(lpfc_emb0_subcmnd_subsys, + &sli_cfg_mbx->un.sli_config_emb0_subsys); + opcode = bsg_bf_get(lpfc_emb0_subcmnd_opcode, + &sli_cfg_mbx->un.sli_config_emb0_subsys); + if (subsys == SLI_CONFIG_SUBSYS_FCOE) { + switch (opcode) { + case FCOE_OPCODE_READ_FCF: + case FCOE_OPCODE_GET_DPORT_RESULTS: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2957 Handled SLI_CONFIG " + "subsys_fcoe, opcode:x%x\n", + opcode); + rc = lpfc_bsg_sli_cfg_read_cmd_ext(phba, job, + nemb_mse, dmabuf); + break; + case FCOE_OPCODE_ADD_FCF: + case FCOE_OPCODE_SET_DPORT_MODE: + case LPFC_MBOX_OPCODE_FCOE_LINK_DIAG_STATE: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2958 Handled SLI_CONFIG " + "subsys_fcoe, opcode:x%x\n", + opcode); + rc = lpfc_bsg_sli_cfg_write_cmd_ext(phba, job, + nemb_mse, dmabuf); + break; + default: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2959 Reject SLI_CONFIG " + "subsys_fcoe, opcode:x%x\n", + opcode); + rc = -EPERM; + break; + } + } else if (subsys == SLI_CONFIG_SUBSYS_COMN) { + switch (opcode) { + case COMN_OPCODE_GET_CNTL_ADDL_ATTRIBUTES: + case COMN_OPCODE_GET_CNTL_ATTRIBUTES: + case COMN_OPCODE_GET_PROFILE_CONFIG: + case COMN_OPCODE_SET_FEATURES: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3106 Handled SLI_CONFIG " + "subsys_comn, opcode:x%x\n", + opcode); + rc = lpfc_bsg_sli_cfg_read_cmd_ext(phba, job, + nemb_mse, dmabuf); + break; + default: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "3107 Reject SLI_CONFIG " + "subsys_comn, opcode:x%x\n", + opcode); + rc = -EPERM; + break; + } + } else { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2977 Reject SLI_CONFIG " + "subsys:x%d, opcode:x%x\n", + subsys, opcode); + rc = -EPERM; + } + } else { + subsys = bsg_bf_get(lpfc_emb1_subcmnd_subsys, + &sli_cfg_mbx->un.sli_config_emb1_subsys); + opcode = bsg_bf_get(lpfc_emb1_subcmnd_opcode, + &sli_cfg_mbx->un.sli_config_emb1_subsys); + if (subsys == SLI_CONFIG_SUBSYS_COMN) { + switch (opcode) { + case COMN_OPCODE_READ_OBJECT: + case COMN_OPCODE_READ_OBJECT_LIST: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2960 Handled SLI_CONFIG " + "subsys_comn, opcode:x%x\n", + opcode); + rc = lpfc_bsg_sli_cfg_read_cmd_ext(phba, job, + nemb_hbd, dmabuf); + break; + case COMN_OPCODE_WRITE_OBJECT: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2961 Handled SLI_CONFIG " + "subsys_comn, opcode:x%x\n", + opcode); + rc = lpfc_bsg_sli_cfg_write_cmd_ext(phba, job, + nemb_hbd, dmabuf); + break; + default: + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2962 Not handled SLI_CONFIG " + "subsys_comn, opcode:x%x\n", + opcode); + rc = SLI_CONFIG_NOT_HANDLED; + break; + } + } else { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2978 Not handled SLI_CONFIG " + "subsys:x%d, opcode:x%x\n", + subsys, opcode); + rc = SLI_CONFIG_NOT_HANDLED; + } + } + + /* state reset on not handled new multi-buffer mailbox command */ + if (rc != SLI_CONFIG_HANDLED) + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_IDLE; + + return rc; +} + +/** + * lpfc_bsg_mbox_ext_abort - request to abort mbox command with ext buffers + * @phba: Pointer to HBA context object. + * + * This routine is for requesting to abort a pass-through mailbox command with + * multiple external buffers due to error condition. + **/ +static void +lpfc_bsg_mbox_ext_abort(struct lpfc_hba *phba) +{ + if (phba->mbox_ext_buf_ctx.state == LPFC_BSG_MBOX_PORT) + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_ABTS; + else + lpfc_bsg_mbox_ext_session_reset(phba); + return; +} + +/** + * lpfc_bsg_read_ebuf_get - get the next mailbox read external buffer + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * + * This routine extracts the next mailbox read external buffer back to + * user space through BSG. + **/ +static int +lpfc_bsg_read_ebuf_get(struct lpfc_hba *phba, struct bsg_job *job) +{ + struct fc_bsg_reply *bsg_reply = job->reply; + struct lpfc_sli_config_mbox *sli_cfg_mbx; + struct lpfc_dmabuf *dmabuf; + uint8_t *pbuf; + uint32_t size; + uint32_t index; + + index = phba->mbox_ext_buf_ctx.seqNum; + phba->mbox_ext_buf_ctx.seqNum++; + + sli_cfg_mbx = (struct lpfc_sli_config_mbox *) + phba->mbox_ext_buf_ctx.mbx_dmabuf->virt; + + if (phba->mbox_ext_buf_ctx.nembType == nemb_mse) { + size = bsg_bf_get(lpfc_mbox_sli_config_mse_len, + &sli_cfg_mbx->un.sli_config_emb0_subsys.mse[index]); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2963 SLI_CONFIG (mse) ext-buffer rd get " + "buffer[%d], size:%d\n", index, size); + } else { + size = bsg_bf_get(lpfc_mbox_sli_config_ecmn_hbd_len, + &sli_cfg_mbx->un.sli_config_emb1_subsys.hbd[index]); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2964 SLI_CONFIG (hbd) ext-buffer rd get " + "buffer[%d], size:%d\n", index, size); + } + if (list_empty(&phba->mbox_ext_buf_ctx.ext_dmabuf_list)) + return -EPIPE; + dmabuf = list_first_entry(&phba->mbox_ext_buf_ctx.ext_dmabuf_list, + struct lpfc_dmabuf, list); + list_del_init(&dmabuf->list); + + /* after dma buffer descriptor setup */ + lpfc_idiag_mbxacc_dump_bsg_mbox(phba, phba->mbox_ext_buf_ctx.nembType, + mbox_rd, dma_ebuf, sta_pos_addr, + dmabuf, index); + + pbuf = (uint8_t *)dmabuf->virt; + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + pbuf, size); + + lpfc_bsg_dma_page_free(phba, dmabuf); + + if (phba->mbox_ext_buf_ctx.seqNum == phba->mbox_ext_buf_ctx.numBuf) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2965 SLI_CONFIG (hbd) ext-buffer rd mbox " + "command session done\n"); + lpfc_bsg_mbox_ext_session_reset(phba); + } + + bsg_reply->result = 0; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + + return SLI_CONFIG_HANDLED; +} + +/** + * lpfc_bsg_write_ebuf_set - set the next mailbox write external buffer + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @dmabuf: Pointer to a DMA buffer descriptor. + * + * This routine sets up the next mailbox read external buffer obtained + * from user space through BSG. + **/ +static int +lpfc_bsg_write_ebuf_set(struct lpfc_hba *phba, struct bsg_job *job, + struct lpfc_dmabuf *dmabuf) +{ + struct fc_bsg_reply *bsg_reply = job->reply; + struct bsg_job_data *dd_data = NULL; + LPFC_MBOXQ_t *pmboxq = NULL; + MAILBOX_t *pmb; + enum nemb_type nemb_tp; + uint8_t *pbuf; + uint32_t size; + uint32_t index; + int rc; + + index = phba->mbox_ext_buf_ctx.seqNum; + phba->mbox_ext_buf_ctx.seqNum++; + nemb_tp = phba->mbox_ext_buf_ctx.nembType; + + pbuf = (uint8_t *)dmabuf->virt; + size = job->request_payload.payload_len; + sg_copy_to_buffer(job->request_payload.sg_list, + job->request_payload.sg_cnt, + pbuf, size); + + if (phba->mbox_ext_buf_ctx.nembType == nemb_mse) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2966 SLI_CONFIG (mse) ext-buffer wr set " + "buffer[%d], size:%d\n", + phba->mbox_ext_buf_ctx.seqNum, size); + + } else { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2967 SLI_CONFIG (hbd) ext-buffer wr set " + "buffer[%d], size:%d\n", + phba->mbox_ext_buf_ctx.seqNum, size); + + } + + /* set up external buffer descriptor and add to external buffer list */ + lpfc_bsg_sli_cfg_dma_desc_setup(phba, nemb_tp, index, + phba->mbox_ext_buf_ctx.mbx_dmabuf, + dmabuf); + list_add_tail(&dmabuf->list, &phba->mbox_ext_buf_ctx.ext_dmabuf_list); + + /* after write dma buffer */ + lpfc_idiag_mbxacc_dump_bsg_mbox(phba, phba->mbox_ext_buf_ctx.nembType, + mbox_wr, dma_ebuf, sta_pos_addr, + dmabuf, index); + + if (phba->mbox_ext_buf_ctx.seqNum == phba->mbox_ext_buf_ctx.numBuf) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2968 SLI_CONFIG ext-buffer wr all %d " + "ebuffers received\n", + phba->mbox_ext_buf_ctx.numBuf); + + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (!dd_data) { + rc = -ENOMEM; + goto job_error; + } + + /* mailbox command structure for base driver */ + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) { + rc = -ENOMEM; + goto job_error; + } + memset(pmboxq, 0, sizeof(LPFC_MBOXQ_t)); + pbuf = (uint8_t *)phba->mbox_ext_buf_ctx.mbx_dmabuf->virt; + pmb = &pmboxq->u.mb; + memcpy(pmb, pbuf, sizeof(*pmb)); + pmb->mbxOwner = OWN_HOST; + pmboxq->vport = phba->pport; + + /* callback for multi-buffer write mailbox command */ + pmboxq->mbox_cmpl = lpfc_bsg_issue_write_mbox_ext_cmpl; + + /* context fields to callback function */ + pmboxq->ctx_buf = dd_data; + dd_data->type = TYPE_MBOX; + dd_data->set_job = job; + dd_data->context_un.mbox.pmboxq = pmboxq; + dd_data->context_un.mbox.mb = (MAILBOX_t *)pbuf; + job->dd_data = dd_data; + + /* state change */ + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_PORT; + + rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_NOWAIT); + if ((rc == MBX_SUCCESS) || (rc == MBX_BUSY)) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2969 Issued SLI_CONFIG ext-buffer " + "mailbox command, rc:x%x\n", rc); + return SLI_CONFIG_HANDLED; + } + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2970 Failed to issue SLI_CONFIG ext-buffer " + "mailbox command, rc:x%x\n", rc); + rc = -EPIPE; + goto job_error; + } + + /* wait for additional external buffers */ + bsg_reply->result = 0; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return SLI_CONFIG_HANDLED; + +job_error: + if (pmboxq) + mempool_free(pmboxq, phba->mbox_mem_pool); + lpfc_bsg_dma_page_free(phba, dmabuf); + kfree(dd_data); + + return rc; +} + +/** + * lpfc_bsg_handle_sli_cfg_ebuf - handle ext buffer with sli-cfg mailbox cmd + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @dmabuf: Pointer to a DMA buffer descriptor. + * + * This routine handles the external buffer with SLI_CONFIG (0x9B) mailbox + * command with multiple non-embedded external buffers. + **/ +static int +lpfc_bsg_handle_sli_cfg_ebuf(struct lpfc_hba *phba, struct bsg_job *job, + struct lpfc_dmabuf *dmabuf) +{ + int rc; + + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2971 SLI_CONFIG buffer (type:x%x)\n", + phba->mbox_ext_buf_ctx.mboxType); + + if (phba->mbox_ext_buf_ctx.mboxType == mbox_rd) { + if (phba->mbox_ext_buf_ctx.state != LPFC_BSG_MBOX_DONE) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2972 SLI_CONFIG rd buffer state " + "mismatch:x%x\n", + phba->mbox_ext_buf_ctx.state); + lpfc_bsg_mbox_ext_abort(phba); + return -EPIPE; + } + rc = lpfc_bsg_read_ebuf_get(phba, job); + if (rc == SLI_CONFIG_HANDLED) + lpfc_bsg_dma_page_free(phba, dmabuf); + } else { /* phba->mbox_ext_buf_ctx.mboxType == mbox_wr */ + if (phba->mbox_ext_buf_ctx.state != LPFC_BSG_MBOX_HOST) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2973 SLI_CONFIG wr buffer state " + "mismatch:x%x\n", + phba->mbox_ext_buf_ctx.state); + lpfc_bsg_mbox_ext_abort(phba); + return -EPIPE; + } + rc = lpfc_bsg_write_ebuf_set(phba, job, dmabuf); + } + return rc; +} + +/** + * lpfc_bsg_handle_sli_cfg_ext - handle sli-cfg mailbox with external buffer + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @dmabuf: Pointer to a DMA buffer descriptor. + * + * This routine checks and handles non-embedded multi-buffer SLI_CONFIG + * (0x9B) mailbox commands and external buffers. + **/ +static int +lpfc_bsg_handle_sli_cfg_ext(struct lpfc_hba *phba, struct bsg_job *job, + struct lpfc_dmabuf *dmabuf) +{ + struct fc_bsg_request *bsg_request = job->request; + struct dfc_mbox_req *mbox_req; + int rc = SLI_CONFIG_NOT_HANDLED; + + mbox_req = + (struct dfc_mbox_req *)bsg_request->rqst_data.h_vendor.vendor_cmd; + + /* mbox command with/without single external buffer */ + if (mbox_req->extMboxTag == 0 && mbox_req->extSeqNum == 0) + return rc; + + /* mbox command and first external buffer */ + if (phba->mbox_ext_buf_ctx.state == LPFC_BSG_MBOX_IDLE) { + if (mbox_req->extSeqNum == 1) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2974 SLI_CONFIG mailbox: tag:%d, " + "seq:%d\n", mbox_req->extMboxTag, + mbox_req->extSeqNum); + rc = lpfc_bsg_handle_sli_cfg_mbox(phba, job, dmabuf); + return rc; + } else + goto sli_cfg_ext_error; + } + + /* + * handle additional external buffers + */ + + /* check broken pipe conditions */ + if (mbox_req->extMboxTag != phba->mbox_ext_buf_ctx.mbxTag) + goto sli_cfg_ext_error; + if (mbox_req->extSeqNum > phba->mbox_ext_buf_ctx.numBuf) + goto sli_cfg_ext_error; + if (mbox_req->extSeqNum != phba->mbox_ext_buf_ctx.seqNum + 1) + goto sli_cfg_ext_error; + + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2975 SLI_CONFIG mailbox external buffer: " + "extSta:x%x, tag:%d, seq:%d\n", + phba->mbox_ext_buf_ctx.state, mbox_req->extMboxTag, + mbox_req->extSeqNum); + rc = lpfc_bsg_handle_sli_cfg_ebuf(phba, job, dmabuf); + return rc; + +sli_cfg_ext_error: + /* all other cases, broken pipe */ + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2976 SLI_CONFIG mailbox broken pipe: " + "ctxSta:x%x, ctxNumBuf:%d " + "ctxTag:%d, ctxSeq:%d, tag:%d, seq:%d\n", + phba->mbox_ext_buf_ctx.state, + phba->mbox_ext_buf_ctx.numBuf, + phba->mbox_ext_buf_ctx.mbxTag, + phba->mbox_ext_buf_ctx.seqNum, + mbox_req->extMboxTag, mbox_req->extSeqNum); + + lpfc_bsg_mbox_ext_session_reset(phba); + + return -EPIPE; +} + +/** + * lpfc_bsg_issue_mbox - issues a mailbox command on behalf of an app + * @phba: Pointer to HBA context object. + * @job: Pointer to the job object. + * @vport: Pointer to a vport object. + * + * Allocate a tracking object, mailbox command memory, get a mailbox + * from the mailbox pool, copy the caller mailbox command. + * + * If offline and the sli is active we need to poll for the command (port is + * being reset) and complete the job, otherwise issue the mailbox command and + * let our completion handler finish the command. + **/ +static int +lpfc_bsg_issue_mbox(struct lpfc_hba *phba, struct bsg_job *job, + struct lpfc_vport *vport) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + LPFC_MBOXQ_t *pmboxq = NULL; /* internal mailbox queue */ + MAILBOX_t *pmb; /* shortcut to the pmboxq mailbox */ + /* a 4k buffer to hold the mb and extended data from/to the bsg */ + uint8_t *pmbx = NULL; + struct bsg_job_data *dd_data = NULL; /* bsg data tracking structure */ + struct lpfc_dmabuf *dmabuf = NULL; + struct dfc_mbox_req *mbox_req; + struct READ_EVENT_LOG_VAR *rdEventLog; + uint32_t transmit_length, receive_length, mode; + struct lpfc_mbx_sli4_config *sli4_config; + struct lpfc_mbx_nembed_cmd *nembed_sge; + struct ulp_bde64 *bde; + uint8_t *ext = NULL; + int rc = 0; + uint8_t *from; + uint32_t size; + + /* in case no data is transferred */ + bsg_reply->reply_payload_rcv_len = 0; + + /* sanity check to protect driver */ + if (job->reply_payload.payload_len > BSG_MBOX_SIZE || + job->request_payload.payload_len > BSG_MBOX_SIZE) { + rc = -ERANGE; + goto job_done; + } + + /* + * Don't allow mailbox commands to be sent when blocked or when in + * the middle of discovery + */ + if (phba->sli.sli_flag & LPFC_BLOCK_MGMT_IO) { + rc = -EAGAIN; + goto job_done; + } + + mbox_req = + (struct dfc_mbox_req *)bsg_request->rqst_data.h_vendor.vendor_cmd; + + /* check if requested extended data lengths are valid */ + if ((mbox_req->inExtWLen > BSG_MBOX_SIZE/sizeof(uint32_t)) || + (mbox_req->outExtWLen > BSG_MBOX_SIZE/sizeof(uint32_t))) { + rc = -ERANGE; + goto job_done; + } + + dmabuf = lpfc_bsg_dma_page_alloc(phba); + if (!dmabuf || !dmabuf->virt) { + rc = -ENOMEM; + goto job_done; + } + + /* Get the mailbox command or external buffer from BSG */ + pmbx = (uint8_t *)dmabuf->virt; + size = job->request_payload.payload_len; + sg_copy_to_buffer(job->request_payload.sg_list, + job->request_payload.sg_cnt, pmbx, size); + + /* Handle possible SLI_CONFIG with non-embedded payloads */ + if (phba->sli_rev == LPFC_SLI_REV4) { + rc = lpfc_bsg_handle_sli_cfg_ext(phba, job, dmabuf); + if (rc == SLI_CONFIG_HANDLED) + goto job_cont; + if (rc) + goto job_done; + /* SLI_CONFIG_NOT_HANDLED for other mailbox commands */ + } + + rc = lpfc_bsg_check_cmd_access(phba, (MAILBOX_t *)pmbx, vport); + if (rc != 0) + goto job_done; /* must be negative */ + + /* allocate our bsg tracking structure */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (!dd_data) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2727 Failed allocation of dd_data\n"); + rc = -ENOMEM; + goto job_done; + } + + pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (!pmboxq) { + rc = -ENOMEM; + goto job_done; + } + memset(pmboxq, 0, sizeof(LPFC_MBOXQ_t)); + + pmb = &pmboxq->u.mb; + memcpy(pmb, pmbx, sizeof(*pmb)); + pmb->mbxOwner = OWN_HOST; + pmboxq->vport = vport; + + /* If HBA encountered an error attention, allow only DUMP + * or RESTART mailbox commands until the HBA is restarted. + */ + if (phba->pport->stopped && + pmb->mbxCommand != MBX_DUMP_MEMORY && + pmb->mbxCommand != MBX_RESTART && + pmb->mbxCommand != MBX_WRITE_VPARMS && + pmb->mbxCommand != MBX_WRITE_WWN) + lpfc_printf_log(phba, KERN_WARNING, LOG_MBOX, + "2797 mbox: Issued mailbox cmd " + "0x%x while in stopped state.\n", + pmb->mbxCommand); + + /* extended mailbox commands will need an extended buffer */ + if (mbox_req->inExtWLen || mbox_req->outExtWLen) { + from = pmbx; + ext = from + sizeof(MAILBOX_t); + pmboxq->ctx_buf = ext; + pmboxq->in_ext_byte_len = + mbox_req->inExtWLen * sizeof(uint32_t); + pmboxq->out_ext_byte_len = + mbox_req->outExtWLen * sizeof(uint32_t); + pmboxq->mbox_offset_word = mbox_req->mbOffset; + } + + /* biu diag will need a kernel buffer to transfer the data + * allocate our own buffer and setup the mailbox command to + * use ours + */ + if (pmb->mbxCommand == MBX_RUN_BIU_DIAG64) { + transmit_length = pmb->un.varWords[1]; + receive_length = pmb->un.varWords[4]; + /* transmit length cannot be greater than receive length or + * mailbox extension size + */ + if ((transmit_length > receive_length) || + (transmit_length > BSG_MBOX_SIZE - sizeof(MAILBOX_t))) { + rc = -ERANGE; + goto job_done; + } + pmb->un.varBIUdiag.un.s2.xmit_bde64.addrHigh = + putPaddrHigh(dmabuf->phys + sizeof(MAILBOX_t)); + pmb->un.varBIUdiag.un.s2.xmit_bde64.addrLow = + putPaddrLow(dmabuf->phys + sizeof(MAILBOX_t)); + + pmb->un.varBIUdiag.un.s2.rcv_bde64.addrHigh = + putPaddrHigh(dmabuf->phys + sizeof(MAILBOX_t) + + pmb->un.varBIUdiag.un.s2.xmit_bde64.tus.f.bdeSize); + pmb->un.varBIUdiag.un.s2.rcv_bde64.addrLow = + putPaddrLow(dmabuf->phys + sizeof(MAILBOX_t) + + pmb->un.varBIUdiag.un.s2.xmit_bde64.tus.f.bdeSize); + } else if (pmb->mbxCommand == MBX_READ_EVENT_LOG) { + rdEventLog = &pmb->un.varRdEventLog; + receive_length = rdEventLog->rcv_bde64.tus.f.bdeSize; + mode = bf_get(lpfc_event_log, rdEventLog); + + /* receive length cannot be greater than mailbox + * extension size + */ + if (receive_length > BSG_MBOX_SIZE - sizeof(MAILBOX_t)) { + rc = -ERANGE; + goto job_done; + } + + /* mode zero uses a bde like biu diags command */ + if (mode == 0) { + pmb->un.varWords[3] = putPaddrLow(dmabuf->phys + + sizeof(MAILBOX_t)); + pmb->un.varWords[4] = putPaddrHigh(dmabuf->phys + + sizeof(MAILBOX_t)); + } + } else if (phba->sli_rev == LPFC_SLI_REV4) { + /* Let type 4 (well known data) through because the data is + * returned in varwords[4-8] + * otherwise check the recieve length and fetch the buffer addr + */ + if ((pmb->mbxCommand == MBX_DUMP_MEMORY) && + (pmb->un.varDmp.type != DMP_WELL_KNOWN)) { + /* rebuild the command for sli4 using our own buffers + * like we do for biu diags + */ + receive_length = pmb->un.varWords[2]; + /* receive length cannot be greater than mailbox + * extension size + */ + if (receive_length == 0) { + rc = -ERANGE; + goto job_done; + } + pmb->un.varWords[3] = putPaddrLow(dmabuf->phys + + sizeof(MAILBOX_t)); + pmb->un.varWords[4] = putPaddrHigh(dmabuf->phys + + sizeof(MAILBOX_t)); + } else if ((pmb->mbxCommand == MBX_UPDATE_CFG) && + pmb->un.varUpdateCfg.co) { + bde = (struct ulp_bde64 *)&pmb->un.varWords[4]; + + /* bde size cannot be greater than mailbox ext size */ + if (bde->tus.f.bdeSize > + BSG_MBOX_SIZE - sizeof(MAILBOX_t)) { + rc = -ERANGE; + goto job_done; + } + bde->addrHigh = putPaddrHigh(dmabuf->phys + + sizeof(MAILBOX_t)); + bde->addrLow = putPaddrLow(dmabuf->phys + + sizeof(MAILBOX_t)); + } else if (pmb->mbxCommand == MBX_SLI4_CONFIG) { + /* Handling non-embedded SLI_CONFIG mailbox command */ + sli4_config = &pmboxq->u.mqe.un.sli4_config; + if (!bf_get(lpfc_mbox_hdr_emb, + &sli4_config->header.cfg_mhdr)) { + /* rebuild the command for sli4 using our + * own buffers like we do for biu diags + */ + nembed_sge = (struct lpfc_mbx_nembed_cmd *) + &pmb->un.varWords[0]; + receive_length = nembed_sge->sge[0].length; + + /* receive length cannot be greater than + * mailbox extension size + */ + if ((receive_length == 0) || + (receive_length > + BSG_MBOX_SIZE - sizeof(MAILBOX_t))) { + rc = -ERANGE; + goto job_done; + } + + nembed_sge->sge[0].pa_hi = + putPaddrHigh(dmabuf->phys + + sizeof(MAILBOX_t)); + nembed_sge->sge[0].pa_lo = + putPaddrLow(dmabuf->phys + + sizeof(MAILBOX_t)); + } + } + } + + dd_data->context_un.mbox.dmabuffers = dmabuf; + + /* setup wake call as IOCB callback */ + pmboxq->mbox_cmpl = lpfc_bsg_issue_mbox_cmpl; + + /* setup context field to pass wait_queue pointer to wake function */ + pmboxq->ctx_ndlp = dd_data; + dd_data->type = TYPE_MBOX; + dd_data->set_job = job; + dd_data->context_un.mbox.pmboxq = pmboxq; + dd_data->context_un.mbox.mb = (MAILBOX_t *)pmbx; + dd_data->context_un.mbox.ext = ext; + dd_data->context_un.mbox.mbOffset = mbox_req->mbOffset; + dd_data->context_un.mbox.inExtWLen = mbox_req->inExtWLen; + dd_data->context_un.mbox.outExtWLen = mbox_req->outExtWLen; + job->dd_data = dd_data; + + if ((vport->fc_flag & FC_OFFLINE_MODE) || + (!(phba->sli.sli_flag & LPFC_SLI_ACTIVE))) { + rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_POLL); + if (rc != MBX_SUCCESS) { + rc = (rc == MBX_TIMEOUT) ? -ETIME : -ENODEV; + goto job_done; + } + + /* job finished, copy the data */ + memcpy(pmbx, pmb, sizeof(*pmb)); + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + pmbx, size); + /* not waiting mbox already done */ + rc = 0; + goto job_done; + } + + rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_NOWAIT); + if ((rc == MBX_SUCCESS) || (rc == MBX_BUSY)) + return 1; /* job started */ + +job_done: + /* common exit for error or job completed inline */ + if (pmboxq) + mempool_free(pmboxq, phba->mbox_mem_pool); + lpfc_bsg_dma_page_free(phba, dmabuf); + kfree(dd_data); + +job_cont: + return rc; +} + +/** + * lpfc_bsg_mbox_cmd - process an fc bsg LPFC_BSG_VENDOR_MBOX command + * @job: MBOX fc_bsg_job for LPFC_BSG_VENDOR_MBOX. + **/ +static int +lpfc_bsg_mbox_cmd(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct lpfc_hba *phba = vport->phba; + struct dfc_mbox_req *mbox_req; + int rc = 0; + + /* mix-and-match backward compatibility */ + bsg_reply->reply_payload_rcv_len = 0; + if (job->request_len < + sizeof(struct fc_bsg_request) + sizeof(struct dfc_mbox_req)) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "2737 Mix-and-match backward compatibility " + "between MBOX_REQ old size:%d and " + "new request size:%d\n", + (int)(job->request_len - + sizeof(struct fc_bsg_request)), + (int)sizeof(struct dfc_mbox_req)); + mbox_req = (struct dfc_mbox_req *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + mbox_req->extMboxTag = 0; + mbox_req->extSeqNum = 0; + } + + rc = lpfc_bsg_issue_mbox(phba, job, vport); + + if (rc == 0) { + /* job done */ + bsg_reply->result = 0; + job->dd_data = NULL; + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + } else if (rc == 1) + /* job submitted, will complete later*/ + rc = 0; /* return zero, no error */ + else { + /* some error occurred */ + bsg_reply->result = rc; + job->dd_data = NULL; + } + + return rc; +} + +static int +lpfc_forced_link_speed(struct bsg_job *job) +{ + struct Scsi_Host *shost = fc_bsg_to_shost(job); + struct lpfc_vport *vport = shost_priv(shost); + struct lpfc_hba *phba = vport->phba; + struct fc_bsg_reply *bsg_reply = job->reply; + struct forced_link_speed_support_reply *forced_reply; + int rc = 0; + + if (job->request_len < + sizeof(struct fc_bsg_request) + + sizeof(struct get_forced_link_speed_support)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "0048 Received FORCED_LINK_SPEED request " + "below minimum size\n"); + rc = -EINVAL; + goto job_error; + } + + forced_reply = (struct forced_link_speed_support_reply *) + bsg_reply->reply_data.vendor_reply.vendor_rsp; + + if (job->reply_len < sizeof(*bsg_reply) + sizeof(*forced_reply)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "0049 Received FORCED_LINK_SPEED reply below " + "minimum size\n"); + rc = -EINVAL; + goto job_error; + } + + forced_reply->supported = (phba->hba_flag & HBA_FORCED_LINK_SPEED) + ? LPFC_FORCED_LINK_SPEED_SUPPORTED + : LPFC_FORCED_LINK_SPEED_NOT_SUPPORTED; +job_error: + bsg_reply->result = rc; + if (rc == 0) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfc_check_fwlog_support: Check FW log support on the adapter + * @phba: Pointer to HBA context object. + * + * Check if FW Logging support by the adapter + **/ +int +lpfc_check_fwlog_support(struct lpfc_hba *phba) +{ + struct lpfc_ras_fwlog *ras_fwlog = NULL; + + ras_fwlog = &phba->ras_fwlog; + + if (!ras_fwlog->ras_hwsupport) + return -EACCES; + else if (!ras_fwlog->ras_enabled) + return -EPERM; + else + return 0; +} + +/** + * lpfc_bsg_get_ras_config: Get RAS configuration settings + * @job: fc_bsg_job to handle + * + * Get RAS configuration values set. + **/ +static int +lpfc_bsg_get_ras_config(struct bsg_job *job) +{ + struct Scsi_Host *shost = fc_bsg_to_shost(job); + struct lpfc_vport *vport = shost_priv(shost); + struct fc_bsg_reply *bsg_reply = job->reply; + struct lpfc_hba *phba = vport->phba; + struct lpfc_bsg_get_ras_config_reply *ras_reply; + struct lpfc_ras_fwlog *ras_fwlog = &phba->ras_fwlog; + int rc = 0; + + if (job->request_len < + sizeof(struct fc_bsg_request) + + sizeof(struct lpfc_bsg_ras_req)) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "6192 FW_LOG request received " + "below minimum size\n"); + rc = -EINVAL; + goto ras_job_error; + } + + /* Check FW log status */ + rc = lpfc_check_fwlog_support(phba); + if (rc) + goto ras_job_error; + + ras_reply = (struct lpfc_bsg_get_ras_config_reply *) + bsg_reply->reply_data.vendor_reply.vendor_rsp; + + /* Current logging state */ + spin_lock_irq(&phba->hbalock); + if (ras_fwlog->state == ACTIVE) + ras_reply->state = LPFC_RASLOG_STATE_RUNNING; + else + ras_reply->state = LPFC_RASLOG_STATE_STOPPED; + spin_unlock_irq(&phba->hbalock); + + ras_reply->log_level = phba->ras_fwlog.fw_loglevel; + ras_reply->log_buff_sz = phba->cfg_ras_fwlog_buffsize; + +ras_job_error: + /* make error code available to userspace */ + bsg_reply->result = rc; + + /* complete the job back to userspace */ + if (!rc) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; +} + +/** + * lpfc_bsg_set_ras_config: Set FW logging parameters + * @job: fc_bsg_job to handle + * + * Set log-level parameters for FW-logging in host memory + **/ +static int +lpfc_bsg_set_ras_config(struct bsg_job *job) +{ + struct Scsi_Host *shost = fc_bsg_to_shost(job); + struct lpfc_vport *vport = shost_priv(shost); + struct lpfc_hba *phba = vport->phba; + struct lpfc_bsg_set_ras_config_req *ras_req; + struct fc_bsg_request *bsg_request = job->request; + struct lpfc_ras_fwlog *ras_fwlog = &phba->ras_fwlog; + struct fc_bsg_reply *bsg_reply = job->reply; + uint8_t action = 0, log_level = 0; + int rc = 0, action_status = 0; + + if (job->request_len < + sizeof(struct fc_bsg_request) + + sizeof(struct lpfc_bsg_set_ras_config_req)) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "6182 Received RAS_LOG request " + "below minimum size\n"); + rc = -EINVAL; + goto ras_job_error; + } + + /* Check FW log status */ + rc = lpfc_check_fwlog_support(phba); + if (rc) + goto ras_job_error; + + ras_req = (struct lpfc_bsg_set_ras_config_req *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + action = ras_req->action; + log_level = ras_req->log_level; + + if (action == LPFC_RASACTION_STOP_LOGGING) { + /* Check if already disabled */ + spin_lock_irq(&phba->hbalock); + if (ras_fwlog->state != ACTIVE) { + spin_unlock_irq(&phba->hbalock); + rc = -ESRCH; + goto ras_job_error; + } + spin_unlock_irq(&phba->hbalock); + + /* Disable logging */ + lpfc_ras_stop_fwlog(phba); + } else { + /*action = LPFC_RASACTION_START_LOGGING*/ + + /* Even though FW-logging is active re-initialize + * FW-logging with new log-level. Return status + * "Logging already Running" to caller. + **/ + spin_lock_irq(&phba->hbalock); + if (ras_fwlog->state != INACTIVE) + action_status = -EINPROGRESS; + spin_unlock_irq(&phba->hbalock); + + /* Enable logging */ + rc = lpfc_sli4_ras_fwlog_init(phba, log_level, + LPFC_RAS_ENABLE_LOGGING); + if (rc) { + rc = -EINVAL; + goto ras_job_error; + } + + /* Check if FW-logging is re-initialized */ + if (action_status == -EINPROGRESS) + rc = action_status; + } +ras_job_error: + /* make error code available to userspace */ + bsg_reply->result = rc; + + /* complete the job back to userspace */ + if (!rc) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + + return rc; +} + +/** + * lpfc_bsg_get_ras_lwpd: Get log write position data + * @job: fc_bsg_job to handle + * + * Get Offset/Wrap count of the log message written + * in host memory + **/ +static int +lpfc_bsg_get_ras_lwpd(struct bsg_job *job) +{ + struct Scsi_Host *shost = fc_bsg_to_shost(job); + struct lpfc_vport *vport = shost_priv(shost); + struct lpfc_bsg_get_ras_lwpd *ras_reply; + struct lpfc_hba *phba = vport->phba; + struct lpfc_ras_fwlog *ras_fwlog = &phba->ras_fwlog; + struct fc_bsg_reply *bsg_reply = job->reply; + u32 *lwpd_ptr = NULL; + int rc = 0; + + rc = lpfc_check_fwlog_support(phba); + if (rc) + goto ras_job_error; + + if (job->request_len < + sizeof(struct fc_bsg_request) + + sizeof(struct lpfc_bsg_ras_req)) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "6183 Received RAS_LOG request " + "below minimum size\n"); + rc = -EINVAL; + goto ras_job_error; + } + + ras_reply = (struct lpfc_bsg_get_ras_lwpd *) + bsg_reply->reply_data.vendor_reply.vendor_rsp; + + if (!ras_fwlog->lwpd.virt) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "6193 Restart FW Logging\n"); + rc = -EINVAL; + goto ras_job_error; + } + + /* Get lwpd offset */ + lwpd_ptr = (uint32_t *)(ras_fwlog->lwpd.virt); + ras_reply->offset = be32_to_cpu(*lwpd_ptr & 0xffffffff); + + /* Get wrap count */ + ras_reply->wrap_count = be32_to_cpu(*(++lwpd_ptr) & 0xffffffff); + +ras_job_error: + /* make error code available to userspace */ + bsg_reply->result = rc; + + /* complete the job back to userspace */ + if (!rc) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + + return rc; +} + +/** + * lpfc_bsg_get_ras_fwlog: Read FW log + * @job: fc_bsg_job to handle + * + * Copy the FW log into the passed buffer. + **/ +static int +lpfc_bsg_get_ras_fwlog(struct bsg_job *job) +{ + struct Scsi_Host *shost = fc_bsg_to_shost(job); + struct lpfc_vport *vport = shost_priv(shost); + struct lpfc_hba *phba = vport->phba; + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct lpfc_bsg_get_fwlog_req *ras_req; + u32 rd_offset, rd_index, offset; + void *src, *fwlog_buff; + struct lpfc_ras_fwlog *ras_fwlog = NULL; + struct lpfc_dmabuf *dmabuf, *next; + int rc = 0; + + ras_fwlog = &phba->ras_fwlog; + + rc = lpfc_check_fwlog_support(phba); + if (rc) + goto ras_job_error; + + /* Logging to be stopped before reading */ + spin_lock_irq(&phba->hbalock); + if (ras_fwlog->state == ACTIVE) { + spin_unlock_irq(&phba->hbalock); + rc = -EINPROGRESS; + goto ras_job_error; + } + spin_unlock_irq(&phba->hbalock); + + if (job->request_len < + sizeof(struct fc_bsg_request) + + sizeof(struct lpfc_bsg_get_fwlog_req)) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "6184 Received RAS_LOG request " + "below minimum size\n"); + rc = -EINVAL; + goto ras_job_error; + } + + ras_req = (struct lpfc_bsg_get_fwlog_req *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + rd_offset = ras_req->read_offset; + + /* Allocate memory to read fw log*/ + fwlog_buff = vmalloc(ras_req->read_size); + if (!fwlog_buff) { + rc = -ENOMEM; + goto ras_job_error; + } + + rd_index = (rd_offset / LPFC_RAS_MAX_ENTRY_SIZE); + offset = (rd_offset % LPFC_RAS_MAX_ENTRY_SIZE); + + list_for_each_entry_safe(dmabuf, next, + &ras_fwlog->fwlog_buff_list, list) { + + if (dmabuf->buffer_tag < rd_index) + continue; + + src = dmabuf->virt + offset; + memcpy(fwlog_buff, src, ras_req->read_size); + break; + } + + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + fwlog_buff, ras_req->read_size); + + vfree(fwlog_buff); + +ras_job_error: + bsg_reply->result = rc; + if (!rc) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + + return rc; +} + +static int +lpfc_get_trunk_info(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_hba *phba = vport->phba; + struct fc_bsg_reply *bsg_reply = job->reply; + struct lpfc_trunk_info *event_reply; + int rc = 0; + + if (job->request_len < + sizeof(struct fc_bsg_request) + sizeof(struct get_trunk_info_req)) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2744 Received GET TRUNK _INFO request below " + "minimum size\n"); + rc = -EINVAL; + goto job_error; + } + + event_reply = (struct lpfc_trunk_info *) + bsg_reply->reply_data.vendor_reply.vendor_rsp; + + if (job->reply_len < sizeof(*bsg_reply) + sizeof(*event_reply)) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2728 Received GET TRUNK _INFO reply below " + "minimum size\n"); + rc = -EINVAL; + goto job_error; + } + if (event_reply == NULL) { + rc = -EINVAL; + goto job_error; + } + + bsg_bf_set(lpfc_trunk_info_link_status, event_reply, + (phba->link_state >= LPFC_LINK_UP) ? 1 : 0); + + bsg_bf_set(lpfc_trunk_info_trunk_active0, event_reply, + (phba->trunk_link.link0.state == LPFC_LINK_UP) ? 1 : 0); + + bsg_bf_set(lpfc_trunk_info_trunk_active1, event_reply, + (phba->trunk_link.link1.state == LPFC_LINK_UP) ? 1 : 0); + + bsg_bf_set(lpfc_trunk_info_trunk_active2, event_reply, + (phba->trunk_link.link2.state == LPFC_LINK_UP) ? 1 : 0); + + bsg_bf_set(lpfc_trunk_info_trunk_active3, event_reply, + (phba->trunk_link.link3.state == LPFC_LINK_UP) ? 1 : 0); + + bsg_bf_set(lpfc_trunk_info_trunk_config0, event_reply, + bf_get(lpfc_conf_trunk_port0, &phba->sli4_hba)); + + bsg_bf_set(lpfc_trunk_info_trunk_config1, event_reply, + bf_get(lpfc_conf_trunk_port1, &phba->sli4_hba)); + + bsg_bf_set(lpfc_trunk_info_trunk_config2, event_reply, + bf_get(lpfc_conf_trunk_port2, &phba->sli4_hba)); + + bsg_bf_set(lpfc_trunk_info_trunk_config3, event_reply, + bf_get(lpfc_conf_trunk_port3, &phba->sli4_hba)); + + event_reply->port_speed = phba->sli4_hba.link_state.speed / 1000; + event_reply->logical_speed = + phba->sli4_hba.link_state.logical_speed / 1000; +job_error: + bsg_reply->result = rc; + if (!rc) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + return rc; + +} + +static int +lpfc_get_cgnbuf_info(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_hba *phba = vport->phba; + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + struct get_cgnbuf_info_req *cgnbuf_req; + struct lpfc_cgn_info *cp; + uint8_t *cgn_buff; + int size, cinfosz; + int rc = 0; + + if (job->request_len < sizeof(struct fc_bsg_request) + + sizeof(struct get_cgnbuf_info_req)) { + rc = -ENOMEM; + goto job_exit; + } + + if (!phba->sli4_hba.pc_sli4_params.cmf) { + rc = -ENOENT; + goto job_exit; + } + + if (!phba->cgn_i || !phba->cgn_i->virt) { + rc = -ENOENT; + goto job_exit; + } + + cp = phba->cgn_i->virt; + if (cp->cgn_info_version < LPFC_CGN_INFO_V3) { + rc = -EPERM; + goto job_exit; + } + + cgnbuf_req = (struct get_cgnbuf_info_req *) + bsg_request->rqst_data.h_vendor.vendor_cmd; + + /* For reset or size == 0 */ + bsg_reply->reply_payload_rcv_len = 0; + + if (cgnbuf_req->reset == LPFC_BSG_CGN_RESET_STAT) { + lpfc_init_congestion_stat(phba); + goto job_exit; + } + + /* We don't want to include the CRC at the end */ + cinfosz = sizeof(struct lpfc_cgn_info) - sizeof(uint32_t); + + size = cgnbuf_req->read_size; + if (!size) + goto job_exit; + + if (size < cinfosz) { + /* Just copy back what we can */ + cinfosz = size; + rc = -E2BIG; + } + + /* Allocate memory to read congestion info */ + cgn_buff = vmalloc(cinfosz); + if (!cgn_buff) { + rc = -ENOMEM; + goto job_exit; + } + + memcpy(cgn_buff, cp, cinfosz); + + bsg_reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + cgn_buff, cinfosz); + + vfree(cgn_buff); + +job_exit: + bsg_reply->result = rc; + if (!rc) + bsg_job_done(job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); + else + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "2724 GET CGNBUF error: %d\n", rc); + return rc; +} + +/** + * lpfc_bsg_hst_vendor - process a vendor-specific fc_bsg_job + * @job: fc_bsg_job to handle + **/ +static int +lpfc_bsg_hst_vendor(struct bsg_job *job) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + int command = bsg_request->rqst_data.h_vendor.vendor_cmd[0]; + int rc; + + switch (command) { + case LPFC_BSG_VENDOR_SET_CT_EVENT: + rc = lpfc_bsg_hba_set_event(job); + break; + case LPFC_BSG_VENDOR_GET_CT_EVENT: + rc = lpfc_bsg_hba_get_event(job); + break; + case LPFC_BSG_VENDOR_SEND_MGMT_RESP: + rc = lpfc_bsg_send_mgmt_rsp(job); + break; + case LPFC_BSG_VENDOR_DIAG_MODE: + rc = lpfc_bsg_diag_loopback_mode(job); + break; + case LPFC_BSG_VENDOR_DIAG_MODE_END: + rc = lpfc_sli4_bsg_diag_mode_end(job); + break; + case LPFC_BSG_VENDOR_DIAG_RUN_LOOPBACK: + rc = lpfc_bsg_diag_loopback_run(job); + break; + case LPFC_BSG_VENDOR_LINK_DIAG_TEST: + rc = lpfc_sli4_bsg_link_diag_test(job); + break; + case LPFC_BSG_VENDOR_GET_MGMT_REV: + rc = lpfc_bsg_get_dfc_rev(job); + break; + case LPFC_BSG_VENDOR_MBOX: + rc = lpfc_bsg_mbox_cmd(job); + break; + case LPFC_BSG_VENDOR_FORCED_LINK_SPEED: + rc = lpfc_forced_link_speed(job); + break; + case LPFC_BSG_VENDOR_RAS_GET_LWPD: + rc = lpfc_bsg_get_ras_lwpd(job); + break; + case LPFC_BSG_VENDOR_RAS_GET_FWLOG: + rc = lpfc_bsg_get_ras_fwlog(job); + break; + case LPFC_BSG_VENDOR_RAS_GET_CONFIG: + rc = lpfc_bsg_get_ras_config(job); + break; + case LPFC_BSG_VENDOR_RAS_SET_CONFIG: + rc = lpfc_bsg_set_ras_config(job); + break; + case LPFC_BSG_VENDOR_GET_TRUNK_INFO: + rc = lpfc_get_trunk_info(job); + break; + case LPFC_BSG_VENDOR_GET_CGNBUF_INFO: + rc = lpfc_get_cgnbuf_info(job); + break; + default: + rc = -EINVAL; + bsg_reply->reply_payload_rcv_len = 0; + /* make error code available to userspace */ + bsg_reply->result = rc; + break; + } + + return rc; +} + +/** + * lpfc_bsg_request - handle a bsg request from the FC transport + * @job: bsg_job to handle + **/ +int +lpfc_bsg_request(struct bsg_job *job) +{ + struct fc_bsg_request *bsg_request = job->request; + struct fc_bsg_reply *bsg_reply = job->reply; + uint32_t msgcode; + int rc; + + msgcode = bsg_request->msgcode; + switch (msgcode) { + case FC_BSG_HST_VENDOR: + rc = lpfc_bsg_hst_vendor(job); + break; + case FC_BSG_RPT_ELS: + rc = lpfc_bsg_rport_els(job); + break; + case FC_BSG_RPT_CT: + rc = lpfc_bsg_send_mgmt_cmd(job); + break; + default: + rc = -EINVAL; + bsg_reply->reply_payload_rcv_len = 0; + /* make error code available to userspace */ + bsg_reply->result = rc; + break; + } + + return rc; +} + +/** + * lpfc_bsg_timeout - handle timeout of a bsg request from the FC transport + * @job: bsg_job that has timed out + * + * This function just aborts the job's IOCB. The aborted IOCB will return to + * the waiting function which will handle passing the error back to userspace + **/ +int +lpfc_bsg_timeout(struct bsg_job *job) +{ + struct lpfc_vport *vport = shost_priv(fc_bsg_to_shost(job)); + struct lpfc_hba *phba = vport->phba; + struct lpfc_iocbq *cmdiocb; + struct lpfc_sli_ring *pring; + struct bsg_job_data *dd_data; + unsigned long flags; + int rc = 0; + LIST_HEAD(completions); + struct lpfc_iocbq *check_iocb, *next_iocb; + + pring = lpfc_phba_elsring(phba); + if (unlikely(!pring)) + return -EIO; + + /* if job's driver data is NULL, the command completed or is in the + * the process of completing. In this case, return status to request + * so the timeout is retried. This avoids double completion issues + * and the request will be pulled off the timer queue when the + * command's completion handler executes. Otherwise, prevent the + * command's completion handler from executing the job done callback + * and continue processing to abort the outstanding the command. + */ + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + dd_data = (struct bsg_job_data *)job->dd_data; + if (dd_data) { + dd_data->set_job = NULL; + job->dd_data = NULL; + } else { + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + return -EAGAIN; + } + + switch (dd_data->type) { + case TYPE_IOCB: + /* Check to see if IOCB was issued to the port or not. If not, + * remove it from the txq queue and call cancel iocbs. + * Otherwise, call abort iotag + */ + cmdiocb = dd_data->context_un.iocb.cmdiocbq; + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + spin_lock_irqsave(&phba->hbalock, flags); + /* make sure the I/O abort window is still open */ + if (!(cmdiocb->cmd_flag & LPFC_IO_CMD_OUTSTANDING)) { + spin_unlock_irqrestore(&phba->hbalock, flags); + return -EAGAIN; + } + list_for_each_entry_safe(check_iocb, next_iocb, &pring->txq, + list) { + if (check_iocb == cmdiocb) { + list_move_tail(&check_iocb->list, &completions); + break; + } + } + if (list_empty(&completions)) + lpfc_sli_issue_abort_iotag(phba, pring, cmdiocb, NULL); + spin_unlock_irqrestore(&phba->hbalock, flags); + if (!list_empty(&completions)) { + lpfc_sli_cancel_iocbs(phba, &completions, + IOSTAT_LOCAL_REJECT, + IOERR_SLI_ABORTED); + } + break; + + case TYPE_EVT: + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + break; + + case TYPE_MBOX: + /* Update the ext buf ctx state if needed */ + + if (phba->mbox_ext_buf_ctx.state == LPFC_BSG_MBOX_PORT) + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_ABTS; + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + break; + default: + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + break; + } + + /* scsi transport fc fc_bsg_job_timeout expects a zero return code, + * otherwise an error message will be displayed on the console + * so always return success (zero) + */ + return rc; +} |