|  | /******************************************************************* | 
|  | * This file is part of the Emulex Linux Device Driver for         * | 
|  | * Fibre Channel Host Bus Adapters.                                * | 
|  | * Copyright (C) 2009 Emulex.  All rights reserved.                * | 
|  | * EMULEX and SLI are trademarks of Emulex.                        * | 
|  | * www.emulex.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 <scsi/scsi.h> | 
|  | #include <scsi/scsi_host.h> | 
|  | #include <scsi/scsi_transport_fc.h> | 
|  | #include <scsi/scsi_bsg_fc.h> | 
|  |  | 
|  | #include "lpfc_hw4.h" | 
|  | #include "lpfc_hw.h" | 
|  | #include "lpfc_sli.h" | 
|  | #include "lpfc_sli4.h" | 
|  | #include "lpfc_nl.h" | 
|  | #include "lpfc_disc.h" | 
|  | #include "lpfc_scsi.h" | 
|  | #include "lpfc.h" | 
|  | #include "lpfc_logmsg.h" | 
|  | #include "lpfc_crtn.h" | 
|  | #include "lpfc_vport.h" | 
|  | #include "lpfc_version.h" | 
|  |  | 
|  | /** | 
|  | * lpfc_bsg_rport_ct - send a CT command from a bsg request | 
|  | * @job: fc_bsg_job to handle | 
|  | */ | 
|  | static int | 
|  | lpfc_bsg_rport_ct(struct fc_bsg_job *job) | 
|  | { | 
|  | struct Scsi_Host *shost = job->shost; | 
|  | struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; | 
|  | struct lpfc_hba *phba = vport->phba; | 
|  | struct lpfc_rport_data *rdata = job->rport->dd_data; | 
|  | struct lpfc_nodelist *ndlp = rdata->pnode; | 
|  | struct ulp_bde64 *bpl = NULL; | 
|  | uint32_t timeout; | 
|  | struct lpfc_iocbq *cmdiocbq = NULL; | 
|  | struct lpfc_iocbq *rspiocbq = NULL; | 
|  | IOCB_t *cmd; | 
|  | IOCB_t *rsp; | 
|  | struct lpfc_dmabuf *bmp = NULL; | 
|  | int request_nseg; | 
|  | int reply_nseg; | 
|  | struct scatterlist *sgel = NULL; | 
|  | int numbde; | 
|  | dma_addr_t busaddr; | 
|  | int rc = 0; | 
|  |  | 
|  | /* in case no data is transferred */ | 
|  | job->reply->reply_payload_rcv_len = 0; | 
|  |  | 
|  | if (!lpfc_nlp_get(ndlp)) { | 
|  | job->reply->result = -ENODEV; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (ndlp->nlp_flag & NLP_ELS_SND_MASK) { | 
|  | rc = -ENODEV; | 
|  | goto free_ndlp_exit; | 
|  | } | 
|  |  | 
|  | spin_lock_irq(shost->host_lock); | 
|  | cmdiocbq = lpfc_sli_get_iocbq(phba); | 
|  | if (!cmdiocbq) { | 
|  | rc = -ENOMEM; | 
|  | spin_unlock_irq(shost->host_lock); | 
|  | goto free_ndlp_exit; | 
|  | } | 
|  | cmd = &cmdiocbq->iocb; | 
|  |  | 
|  | rspiocbq = lpfc_sli_get_iocbq(phba); | 
|  | if (!rspiocbq) { | 
|  | rc = -ENOMEM; | 
|  | goto free_cmdiocbq; | 
|  | } | 
|  | spin_unlock_irq(shost->host_lock); | 
|  |  | 
|  | rsp = &rspiocbq->iocb; | 
|  |  | 
|  | bmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); | 
|  | if (!bmp) { | 
|  | rc = -ENOMEM; | 
|  | spin_lock_irq(shost->host_lock); | 
|  | goto free_rspiocbq; | 
|  | } | 
|  |  | 
|  | spin_lock_irq(shost->host_lock); | 
|  | bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); | 
|  | if (!bmp->virt) { | 
|  | rc = -ENOMEM; | 
|  | goto free_bmp; | 
|  | } | 
|  | spin_unlock_irq(shost->host_lock); | 
|  |  | 
|  | INIT_LIST_HEAD(&bmp->list); | 
|  | bpl = (struct ulp_bde64 *) bmp->virt; | 
|  |  | 
|  | request_nseg = pci_map_sg(phba->pcidev, job->request_payload.sg_list, | 
|  | job->request_payload.sg_cnt, DMA_TO_DEVICE); | 
|  | for_each_sg(job->request_payload.sg_list, sgel, request_nseg, numbde) { | 
|  | busaddr = sg_dma_address(sgel); | 
|  | bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; | 
|  | bpl->tus.f.bdeSize = sg_dma_len(sgel); | 
|  | bpl->tus.w = cpu_to_le32(bpl->tus.w); | 
|  | bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); | 
|  | bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); | 
|  | bpl++; | 
|  | } | 
|  |  | 
|  | reply_nseg = pci_map_sg(phba->pcidev, job->reply_payload.sg_list, | 
|  | job->reply_payload.sg_cnt, DMA_FROM_DEVICE); | 
|  | for_each_sg(job->reply_payload.sg_list, sgel, reply_nseg, numbde) { | 
|  | busaddr = sg_dma_address(sgel); | 
|  | bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; | 
|  | bpl->tus.f.bdeSize = sg_dma_len(sgel); | 
|  | bpl->tus.w = cpu_to_le32(bpl->tus.w); | 
|  | bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); | 
|  | bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); | 
|  | bpl++; | 
|  | } | 
|  |  | 
|  | cmd->un.genreq64.bdl.ulpIoTag32 = 0; | 
|  | cmd->un.genreq64.bdl.addrHigh = putPaddrHigh(bmp->phys); | 
|  | cmd->un.genreq64.bdl.addrLow = putPaddrLow(bmp->phys); | 
|  | cmd->un.genreq64.bdl.bdeFlags = BUFF_TYPE_BLP_64; | 
|  | cmd->un.genreq64.bdl.bdeSize = | 
|  | (request_nseg + reply_nseg) * sizeof(struct ulp_bde64); | 
|  | cmd->ulpCommand = CMD_GEN_REQUEST64_CR; | 
|  | cmd->un.genreq64.w5.hcsw.Fctl = (SI | LA); | 
|  | cmd->un.genreq64.w5.hcsw.Dfctl = 0; | 
|  | cmd->un.genreq64.w5.hcsw.Rctl = FC_UNSOL_CTL; | 
|  | cmd->un.genreq64.w5.hcsw.Type = FC_COMMON_TRANSPORT_ULP; | 
|  | cmd->ulpBdeCount = 1; | 
|  | cmd->ulpLe = 1; | 
|  | cmd->ulpClass = CLASS3; | 
|  | cmd->ulpContext = ndlp->nlp_rpi; | 
|  | cmd->ulpOwner = OWN_CHIP; | 
|  | cmdiocbq->vport = phba->pport; | 
|  | cmdiocbq->context1 = NULL; | 
|  | cmdiocbq->context2 = NULL; | 
|  | cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; | 
|  |  | 
|  | timeout = phba->fc_ratov * 2; | 
|  | job->dd_data = cmdiocbq; | 
|  |  | 
|  | rc = lpfc_sli_issue_iocb_wait(phba, LPFC_ELS_RING, cmdiocbq, rspiocbq, | 
|  | timeout + LPFC_DRVR_TIMEOUT); | 
|  |  | 
|  | if (rc != IOCB_TIMEDOUT) { | 
|  | pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, | 
|  | job->request_payload.sg_cnt, DMA_TO_DEVICE); | 
|  | pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, | 
|  | job->reply_payload.sg_cnt, DMA_FROM_DEVICE); | 
|  | } | 
|  |  | 
|  | if (rc == IOCB_TIMEDOUT) { | 
|  | lpfc_sli_release_iocbq(phba, rspiocbq); | 
|  | rc = -EACCES; | 
|  | goto free_ndlp_exit; | 
|  | } | 
|  |  | 
|  | if (rc != IOCB_SUCCESS) { | 
|  | rc = -EACCES; | 
|  | goto free_outdmp; | 
|  | } | 
|  |  | 
|  | if (rsp->ulpStatus) { | 
|  | if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { | 
|  | switch (rsp->un.ulpWord[4] & 0xff) { | 
|  | case IOERR_SEQUENCE_TIMEOUT: | 
|  | rc = -ETIMEDOUT; | 
|  | break; | 
|  | case IOERR_INVALID_RPI: | 
|  | rc = -EFAULT; | 
|  | break; | 
|  | default: | 
|  | rc = -EACCES; | 
|  | break; | 
|  | } | 
|  | goto free_outdmp; | 
|  | } | 
|  | } else | 
|  | job->reply->reply_payload_rcv_len = | 
|  | rsp->un.genreq64.bdl.bdeSize; | 
|  |  | 
|  | free_outdmp: | 
|  | spin_lock_irq(shost->host_lock); | 
|  | lpfc_mbuf_free(phba, bmp->virt, bmp->phys); | 
|  | free_bmp: | 
|  | kfree(bmp); | 
|  | free_rspiocbq: | 
|  | lpfc_sli_release_iocbq(phba, rspiocbq); | 
|  | free_cmdiocbq: | 
|  | lpfc_sli_release_iocbq(phba, cmdiocbq); | 
|  | spin_unlock_irq(shost->host_lock); | 
|  | free_ndlp_exit: | 
|  | lpfc_nlp_put(ndlp); | 
|  |  | 
|  | /* make error code available to userspace */ | 
|  | job->reply->result = rc; | 
|  | /* complete the job back to userspace */ | 
|  | job->job_done(job); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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 fc_bsg_job *job) | 
|  | { | 
|  | struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; | 
|  | struct lpfc_hba *phba = vport->phba; | 
|  | struct lpfc_rport_data *rdata = job->rport->dd_data; | 
|  | struct lpfc_nodelist *ndlp = rdata->pnode; | 
|  |  | 
|  | uint32_t elscmd; | 
|  | uint32_t cmdsize; | 
|  | uint32_t rspsize; | 
|  | struct lpfc_iocbq *rspiocbq; | 
|  | struct lpfc_iocbq *cmdiocbq; | 
|  | IOCB_t *rsp; | 
|  | uint16_t rpi = 0; | 
|  | struct lpfc_dmabuf *pcmd; | 
|  | struct lpfc_dmabuf *prsp; | 
|  | struct lpfc_dmabuf *pbuflist = NULL; | 
|  | struct ulp_bde64 *bpl; | 
|  | int iocb_status; | 
|  | int request_nseg; | 
|  | int reply_nseg; | 
|  | struct scatterlist *sgel = NULL; | 
|  | int numbde; | 
|  | dma_addr_t busaddr; | 
|  | int rc = 0; | 
|  |  | 
|  | /* in case no data is transferred */ | 
|  | job->reply->reply_payload_rcv_len = 0; | 
|  |  | 
|  | if (!lpfc_nlp_get(ndlp)) { | 
|  | rc = -ENODEV; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | elscmd = job->request->rqst_data.r_els.els_code; | 
|  | cmdsize = job->request_payload.payload_len; | 
|  | rspsize = job->reply_payload.payload_len; | 
|  | rspiocbq = lpfc_sli_get_iocbq(phba); | 
|  | if (!rspiocbq) { | 
|  | lpfc_nlp_put(ndlp); | 
|  | rc = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | rsp = &rspiocbq->iocb; | 
|  | rpi = ndlp->nlp_rpi; | 
|  |  | 
|  | cmdiocbq = lpfc_prep_els_iocb(phba->pport, 1, cmdsize, 0, ndlp, | 
|  | ndlp->nlp_DID, elscmd); | 
|  |  | 
|  | if (!cmdiocbq) { | 
|  | lpfc_sli_release_iocbq(phba, rspiocbq); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | job->dd_data = cmdiocbq; | 
|  | pcmd = (struct lpfc_dmabuf *) cmdiocbq->context2; | 
|  | prsp = (struct lpfc_dmabuf *) pcmd->list.next; | 
|  |  | 
|  | lpfc_mbuf_free(phba, pcmd->virt, pcmd->phys); | 
|  | kfree(pcmd); | 
|  | lpfc_mbuf_free(phba, prsp->virt, prsp->phys); | 
|  | kfree(prsp); | 
|  | cmdiocbq->context2 = NULL; | 
|  |  | 
|  | pbuflist = (struct lpfc_dmabuf *) cmdiocbq->context3; | 
|  | bpl = (struct ulp_bde64 *) pbuflist->virt; | 
|  |  | 
|  | request_nseg = pci_map_sg(phba->pcidev, job->request_payload.sg_list, | 
|  | job->request_payload.sg_cnt, DMA_TO_DEVICE); | 
|  |  | 
|  | for_each_sg(job->request_payload.sg_list, sgel, request_nseg, numbde) { | 
|  | busaddr = sg_dma_address(sgel); | 
|  | bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; | 
|  | bpl->tus.f.bdeSize = sg_dma_len(sgel); | 
|  | bpl->tus.w = cpu_to_le32(bpl->tus.w); | 
|  | bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); | 
|  | bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); | 
|  | bpl++; | 
|  | } | 
|  |  | 
|  | reply_nseg = pci_map_sg(phba->pcidev, job->reply_payload.sg_list, | 
|  | job->reply_payload.sg_cnt, DMA_FROM_DEVICE); | 
|  | for_each_sg(job->reply_payload.sg_list, sgel, reply_nseg, numbde) { | 
|  | busaddr = sg_dma_address(sgel); | 
|  | bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; | 
|  | bpl->tus.f.bdeSize = sg_dma_len(sgel); | 
|  | bpl->tus.w = cpu_to_le32(bpl->tus.w); | 
|  | bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); | 
|  | bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); | 
|  | bpl++; | 
|  | } | 
|  |  | 
|  | cmdiocbq->iocb.un.elsreq64.bdl.bdeSize = | 
|  | (request_nseg + reply_nseg) * sizeof(struct ulp_bde64); | 
|  | cmdiocbq->iocb.ulpContext = rpi; | 
|  | cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; | 
|  | cmdiocbq->context1 = NULL; | 
|  | cmdiocbq->context2 = NULL; | 
|  |  | 
|  | iocb_status = lpfc_sli_issue_iocb_wait(phba, LPFC_ELS_RING, cmdiocbq, | 
|  | rspiocbq, (phba->fc_ratov * 2) | 
|  | + LPFC_DRVR_TIMEOUT); | 
|  |  | 
|  | /* release the new ndlp once the iocb completes */ | 
|  | lpfc_nlp_put(ndlp); | 
|  | if (iocb_status != IOCB_TIMEDOUT) { | 
|  | pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, | 
|  | job->request_payload.sg_cnt, DMA_TO_DEVICE); | 
|  | pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, | 
|  | job->reply_payload.sg_cnt, DMA_FROM_DEVICE); | 
|  | } | 
|  |  | 
|  | if (iocb_status == IOCB_SUCCESS) { | 
|  | if (rsp->ulpStatus == IOSTAT_SUCCESS) { | 
|  | job->reply->reply_payload_rcv_len = | 
|  | rsp->un.elsreq64.bdl.bdeSize; | 
|  | rc = 0; | 
|  | } else if (rsp->ulpStatus == IOSTAT_LS_RJT) { | 
|  | struct fc_bsg_ctels_reply *els_reply; | 
|  | /* LS_RJT data returned in word 4 */ | 
|  | uint8_t *rjt_data = (uint8_t *)&rsp->un.ulpWord[4]; | 
|  |  | 
|  | els_reply = &job->reply->reply_data.ctels_reply; | 
|  | job->reply->result = 0; | 
|  | els_reply->status = FC_CTELS_STATUS_REJECT; | 
|  | els_reply->rjt_data.action = rjt_data[0]; | 
|  | els_reply->rjt_data.reason_code = rjt_data[1]; | 
|  | els_reply->rjt_data.reason_explanation = rjt_data[2]; | 
|  | els_reply->rjt_data.vendor_unique = rjt_data[3]; | 
|  | } else | 
|  | rc = -EIO; | 
|  | } else | 
|  | rc = -EIO; | 
|  |  | 
|  | if (iocb_status != IOCB_TIMEDOUT) | 
|  | lpfc_els_free_iocb(phba, cmdiocbq); | 
|  |  | 
|  | lpfc_sli_release_iocbq(phba, rspiocbq); | 
|  |  | 
|  | out: | 
|  | /* make error code available to userspace */ | 
|  | job->reply->result = rc; | 
|  | /* complete the job back to userspace */ | 
|  | job->job_done(job); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | struct lpfc_ct_event { | 
|  | struct list_head node; | 
|  | int ref; | 
|  | 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; | 
|  | }; | 
|  |  | 
|  | struct event_data { | 
|  | struct list_head node; | 
|  | uint32_t type; | 
|  | uint32_t immed_dat; | 
|  | void *data; | 
|  | uint32_t len; | 
|  | }; | 
|  |  | 
|  | static struct lpfc_ct_event * | 
|  | lpfc_ct_event_new(int ev_reg_id, uint32_t ev_req_id) | 
|  | { | 
|  | struct lpfc_ct_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->req_id = ev_req_id; | 
|  | evt->reg_id = ev_reg_id; | 
|  | evt->wait_time_stamp = jiffies; | 
|  | init_waitqueue_head(&evt->wq); | 
|  |  | 
|  | return evt; | 
|  | } | 
|  |  | 
|  | static void | 
|  | lpfc_ct_event_free(struct lpfc_ct_event *evt) | 
|  | { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | static inline void | 
|  | lpfc_ct_event_ref(struct lpfc_ct_event *evt) | 
|  | { | 
|  | evt->ref++; | 
|  | } | 
|  |  | 
|  | static inline void | 
|  | lpfc_ct_event_unref(struct lpfc_ct_event *evt) | 
|  | { | 
|  | if (--evt->ref < 0) | 
|  | lpfc_ct_event_free(evt); | 
|  | } | 
|  |  | 
|  | #define SLI_CT_ELX_LOOPBACK 0x10 | 
|  |  | 
|  | enum ELX_LOOPBACK_CMD { | 
|  | ELX_LOOPBACK_XRI_SETUP, | 
|  | ELX_LOOPBACK_DATA, | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * lpfc_bsg_ct_unsol_event - process an unsolicited CT command | 
|  | * @phba: | 
|  | * @pring: | 
|  | * @piocbq: | 
|  | * | 
|  | * This function is called when an unsolicited CT command is received.  It | 
|  | * forwards the event to any processes registerd to receive CT events. | 
|  | */ | 
|  | void | 
|  | lpfc_bsg_ct_unsol_event(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, | 
|  | struct lpfc_iocbq *piocbq) | 
|  | { | 
|  | uint32_t evt_req_id = 0; | 
|  | uint32_t cmd; | 
|  | uint32_t len; | 
|  | struct lpfc_dmabuf *dmabuf = NULL; | 
|  | struct lpfc_ct_event *evt; | 
|  | struct event_data *evt_dat = NULL; | 
|  | struct lpfc_iocbq *iocbq; | 
|  | size_t offset = 0; | 
|  | struct list_head head; | 
|  | struct ulp_bde64 *bde; | 
|  | dma_addr_t dma_addr; | 
|  | int i; | 
|  | struct lpfc_dmabuf *bdeBuf1 = piocbq->context2; | 
|  | struct lpfc_dmabuf *bdeBuf2 = piocbq->context3; | 
|  | struct lpfc_hbq_entry *hbqe; | 
|  | struct lpfc_sli_ct_request *ct_req; | 
|  |  | 
|  | INIT_LIST_HEAD(&head); | 
|  | list_add_tail(&head, &piocbq->list); | 
|  |  | 
|  | if (piocbq->iocb.ulpBdeCount == 0 || | 
|  | piocbq->iocb.un.cont64[0].tus.f.bdeSize == 0) | 
|  | goto error_ct_unsol_exit; | 
|  |  | 
|  | if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) | 
|  | dmabuf = bdeBuf1; | 
|  | else { | 
|  | dma_addr = getPaddr(piocbq->iocb.un.cont64[0].addrHigh, | 
|  | piocbq->iocb.un.cont64[0].addrLow); | 
|  | dmabuf = lpfc_sli_ringpostbuf_get(phba, pring, dma_addr); | 
|  | } | 
|  |  | 
|  | ct_req = (struct lpfc_sli_ct_request *)dmabuf->virt; | 
|  | evt_req_id = ct_req->FsType; | 
|  | cmd = ct_req->CommandResponse.bits.CmdRsp; | 
|  | len = ct_req->CommandResponse.bits.Size; | 
|  | if (!(phba->sli3_options & LPFC_SLI3_HBQ_ENABLED)) | 
|  | lpfc_sli_ringpostbuf_put(phba, pring, dmabuf); | 
|  |  | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | list_for_each_entry(evt, &phba->ct_ev_waiters, node) { | 
|  | if (evt->req_id != evt_req_id) | 
|  | continue; | 
|  |  | 
|  | lpfc_ct_event_ref(evt); | 
|  |  | 
|  | evt_dat = kzalloc(sizeof(*evt_dat), GFP_KERNEL); | 
|  | if (!evt_dat) { | 
|  | lpfc_ct_event_unref(evt); | 
|  | lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, | 
|  | "2614 Memory allocation failed for " | 
|  | "CT event\n"); | 
|  | break; | 
|  | } | 
|  |  | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  |  | 
|  | if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { | 
|  | /* take accumulated byte count from the last iocbq */ | 
|  | iocbq = list_entry(head.prev, typeof(*iocbq), list); | 
|  | evt_dat->len = iocbq->iocb.unsli3.rcvsli3.acc_len; | 
|  | } else { | 
|  | list_for_each_entry(iocbq, &head, list) { | 
|  | for (i = 0; i < iocbq->iocb.ulpBdeCount; i++) | 
|  | evt_dat->len += | 
|  | iocbq->iocb.un.cont64[i].tus.f.bdeSize; | 
|  | } | 
|  | } | 
|  |  | 
|  | evt_dat->data = kzalloc(evt_dat->len, GFP_KERNEL); | 
|  | if (!evt_dat->data) { | 
|  | 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); | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | lpfc_ct_event_unref(evt); | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  | goto error_ct_unsol_exit; | 
|  | } | 
|  |  | 
|  | list_for_each_entry(iocbq, &head, list) { | 
|  | if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { | 
|  | bdeBuf1 = iocbq->context2; | 
|  | bdeBuf2 = iocbq->context3; | 
|  | } | 
|  | for (i = 0; i < iocbq->iocb.ulpBdeCount; i++) { | 
|  | int size = 0; | 
|  | if (phba->sli3_options & | 
|  | LPFC_SLI3_HBQ_ENABLED) { | 
|  | if (i == 0) { | 
|  | hbqe = (struct lpfc_hbq_entry *) | 
|  | &iocbq->iocb.un.ulpWord[0]; | 
|  | size = hbqe->bde.tus.f.bdeSize; | 
|  | dmabuf = bdeBuf1; | 
|  | } else if (i == 1) { | 
|  | hbqe = (struct lpfc_hbq_entry *) | 
|  | &iocbq->iocb.unsli3. | 
|  | sli3Words[4]; | 
|  | size = hbqe->bde.tus.f.bdeSize; | 
|  | 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 0x%p\n", | 
|  | iocbq); | 
|  | kfree(evt_dat->data); | 
|  | kfree(evt_dat); | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | lpfc_ct_event_unref(evt); | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  | 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_XRI_SETUP: | 
|  | if (!(phba->sli3_options & | 
|  | LPFC_SLI3_HBQ_ENABLED)) | 
|  | lpfc_post_buffer(phba, | 
|  | pring, | 
|  | 1); | 
|  | else | 
|  | lpfc_in_buf_free(phba, | 
|  | dmabuf); | 
|  | break; | 
|  | default: | 
|  | if (!(phba->sli3_options & | 
|  | LPFC_SLI3_HBQ_ENABLED)) | 
|  | lpfc_post_buffer(phba, | 
|  | pring, | 
|  | 1); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | if (phba->sli_rev == LPFC_SLI_REV4) { | 
|  | evt_dat->immed_dat = phba->ctx_idx; | 
|  | phba->ctx_idx = (phba->ctx_idx + 1) % 64; | 
|  | phba->ct_ctx[evt_dat->immed_dat].oxid = | 
|  | piocbq->iocb.ulpContext; | 
|  | phba->ct_ctx[evt_dat->immed_dat].SID = | 
|  | piocbq->iocb.un.rcvels.remoteID; | 
|  | } else | 
|  | evt_dat->immed_dat = piocbq->iocb.ulpContext; | 
|  |  | 
|  | evt_dat->type = FC_REG_CT_EVENT; | 
|  | list_add(&evt_dat->node, &evt->events_to_see); | 
|  | wake_up_interruptible(&evt->wq); | 
|  | lpfc_ct_event_unref(evt); | 
|  | if (evt_req_id == SLI_CT_ELX_LOOPBACK) | 
|  | break; | 
|  | } | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  |  | 
|  | error_ct_unsol_exit: | 
|  | if (!list_empty(&head)) | 
|  | list_del(&head); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * lpfc_bsg_set_event - process a SET_EVENT bsg vendor command | 
|  | * @job: SET_EVENT fc_bsg_job | 
|  | */ | 
|  | static int | 
|  | lpfc_bsg_set_event(struct fc_bsg_job *job) | 
|  | { | 
|  | struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; | 
|  | struct lpfc_hba *phba = vport->phba; | 
|  | struct set_ct_event *event_req; | 
|  | struct lpfc_ct_event *evt; | 
|  | int rc = 0; | 
|  |  | 
|  | 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"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | event_req = (struct set_ct_event *) | 
|  | job->request->rqst_data.h_vendor.vendor_cmd; | 
|  |  | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | list_for_each_entry(evt, &phba->ct_ev_waiters, node) { | 
|  | if (evt->reg_id == event_req->ev_reg_id) { | 
|  | lpfc_ct_event_ref(evt); | 
|  | evt->wait_time_stamp = jiffies; | 
|  | break; | 
|  | } | 
|  | } | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  |  | 
|  | if (&evt->node == &phba->ct_ev_waiters) { | 
|  | /* no event waiting struct yet - first call */ | 
|  | evt = lpfc_ct_event_new(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"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | list_add(&evt->node, &phba->ct_ev_waiters); | 
|  | lpfc_ct_event_ref(evt); | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  | } | 
|  |  | 
|  | evt->waiting = 1; | 
|  | if (wait_event_interruptible(evt->wq, | 
|  | !list_empty(&evt->events_to_see))) { | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | lpfc_ct_event_unref(evt); /* release ref */ | 
|  | lpfc_ct_event_unref(evt); /* delete */ | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  | rc = -EINTR; | 
|  | goto set_event_out; | 
|  | } | 
|  |  | 
|  | evt->wait_time_stamp = jiffies; | 
|  | evt->waiting = 0; | 
|  |  | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | list_move(evt->events_to_see.prev, &evt->events_to_get); | 
|  | lpfc_ct_event_unref(evt); /* release ref */ | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  |  | 
|  | set_event_out: | 
|  | /* set_event carries no reply payload */ | 
|  | job->reply->reply_payload_rcv_len = 0; | 
|  | /* make error code available to userspace */ | 
|  | job->reply->result = rc; | 
|  | /* complete the job back to userspace */ | 
|  | job->job_done(job); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * lpfc_bsg_get_event - process a GET_EVENT bsg vendor command | 
|  | * @job: GET_EVENT fc_bsg_job | 
|  | */ | 
|  | static int | 
|  | lpfc_bsg_get_event(struct fc_bsg_job *job) | 
|  | { | 
|  | struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; | 
|  | struct lpfc_hba *phba = vport->phba; | 
|  | struct get_ct_event *event_req; | 
|  | struct get_ct_event_reply *event_reply; | 
|  | struct lpfc_ct_event *evt; | 
|  | struct event_data *evt_dat = NULL; | 
|  | int 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"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | event_req = (struct get_ct_event *) | 
|  | job->request->rqst_data.h_vendor.vendor_cmd; | 
|  |  | 
|  | event_reply = (struct get_ct_event_reply *) | 
|  | job->reply->reply_data.vendor_reply.vendor_rsp; | 
|  |  | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | list_for_each_entry(evt, &phba->ct_ev_waiters, node) { | 
|  | if (evt->reg_id == event_req->ev_reg_id) { | 
|  | if (list_empty(&evt->events_to_get)) | 
|  | break; | 
|  | lpfc_ct_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; | 
|  | } | 
|  | } | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  |  | 
|  | if (!evt_dat) { | 
|  | job->reply->reply_payload_rcv_len = 0; | 
|  | rc = -ENOENT; | 
|  | goto error_get_event_exit; | 
|  | } | 
|  |  | 
|  | if (evt_dat->len > job->reply_payload.payload_len) { | 
|  | evt_dat->len = job->reply_payload.payload_len; | 
|  | lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, | 
|  | "2618 Truncated event data at %d " | 
|  | "bytes\n", | 
|  | job->reply_payload.payload_len); | 
|  | } | 
|  |  | 
|  | event_reply->immed_data = evt_dat->immed_dat; | 
|  |  | 
|  | if (evt_dat->len > 0) | 
|  | job->reply->reply_payload_rcv_len = | 
|  | sg_copy_from_buffer(job->reply_payload.sg_list, | 
|  | job->reply_payload.sg_cnt, | 
|  | evt_dat->data, evt_dat->len); | 
|  | else | 
|  | job->reply->reply_payload_rcv_len = 0; | 
|  | rc = 0; | 
|  |  | 
|  | if (evt_dat) | 
|  | kfree(evt_dat->data); | 
|  | kfree(evt_dat); | 
|  | mutex_lock(&phba->ct_event_mutex); | 
|  | lpfc_ct_event_unref(evt); | 
|  | mutex_unlock(&phba->ct_event_mutex); | 
|  |  | 
|  | error_get_event_exit: | 
|  | /* make error code available to userspace */ | 
|  | job->reply->result = rc; | 
|  | /* complete the job back to userspace */ | 
|  | job->job_done(job); | 
|  |  | 
|  | 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 fc_bsg_job *job) | 
|  | { | 
|  | int command = job->request->rqst_data.h_vendor.vendor_cmd[0]; | 
|  |  | 
|  | switch (command) { | 
|  | case LPFC_BSG_VENDOR_SET_CT_EVENT: | 
|  | return lpfc_bsg_set_event(job); | 
|  | break; | 
|  |  | 
|  | case LPFC_BSG_VENDOR_GET_CT_EVENT: | 
|  | return lpfc_bsg_get_event(job); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * lpfc_bsg_request - handle a bsg request from the FC transport | 
|  | * @job: fc_bsg_job to handle | 
|  | */ | 
|  | int | 
|  | lpfc_bsg_request(struct fc_bsg_job *job) | 
|  | { | 
|  | uint32_t msgcode; | 
|  | int rc = -EINVAL; | 
|  |  | 
|  | msgcode = job->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_rport_ct(job); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * lpfc_bsg_timeout - handle timeout of a bsg request from the FC transport | 
|  | * @job: fc_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 fc_bsg_job *job) | 
|  | { | 
|  | struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; | 
|  | struct lpfc_hba *phba = vport->phba; | 
|  | struct lpfc_iocbq *cmdiocb = (struct lpfc_iocbq *)job->dd_data; | 
|  | struct lpfc_sli_ring *pring = &phba->sli.ring[LPFC_ELS_RING]; | 
|  |  | 
|  | if (cmdiocb) | 
|  | lpfc_sli_issue_abort_iotag(phba, pring, cmdiocb); | 
|  |  | 
|  | return 0; | 
|  | } |