diff options
Diffstat (limited to 'drivers/scsi/lpfc/lpfc_bsg.c')
-rw-r--r-- | drivers/scsi/lpfc/lpfc_bsg.c | 904 |
1 files changed, 904 insertions, 0 deletions
diff --git a/drivers/scsi/lpfc/lpfc_bsg.c b/drivers/scsi/lpfc/lpfc_bsg.c new file mode 100644 index 00000000000..da6bf5aac9d --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_bsg.c @@ -0,0 +1,904 @@ +/******************************************************************* + * 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; +} |