| /******************************************************************* |
| * 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 <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_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_RCTL_DD_UNSOL_CTL; |
| cmd->un.genreq64.w5.hcsw.Type = FC_TYPE_CT; |
| 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; |
| } |