/******************************************************************* * This file is part of the Emulex Linux Device Driver for * * Fibre Channel Host Bus Adapters. * * Copyright (C) 2009-2012 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 #include #include #include #include #include #include #include #include #include #include #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; /* job waiting for this event to finish */ struct fc_bsg_job *set_job; }; struct lpfc_bsg_iocb { struct lpfc_iocbq *cmdiocbq; struct lpfc_iocbq *rspiocbq; struct lpfc_dmabuf *bmp; struct lpfc_nodelist *ndlp; /* job waiting for this iocb to finish */ struct fc_bsg_job *set_job; }; 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 */ /* job waiting for this mbox command to finish */ struct fc_bsg_job *set_job; }; #define MENLO_DID 0x0000FC0E struct lpfc_bsg_menlo { struct lpfc_iocbq *cmdiocbq; struct lpfc_iocbq *rspiocbq; struct lpfc_dmabuf *bmp; /* job waiting for this iocb to finish */ struct fc_bsg_job *set_job; }; #define TYPE_EVT 1 #define TYPE_IOCB 2 #define TYPE_MBOX 3 #define TYPE_MENLO 4 struct bsg_job_data { uint32_t type; union { struct lpfc_bsg_event *evt; struct lpfc_bsg_iocb iocb; struct lpfc_bsg_mbox mbox; struct lpfc_bsg_menlo menlo; } 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; }; /** * 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 fc_bsg_job *job; IOCB_t *rsp; struct lpfc_dmabuf *bmp; struct lpfc_nodelist *ndlp; struct lpfc_bsg_iocb *iocb; unsigned long flags; int rc = 0; spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = cmdiocbq->context2; if (!dd_data) { spin_unlock_irqrestore(&phba->ct_ev_lock, flags); lpfc_sli_release_iocbq(phba, cmdiocbq); return; } iocb = &dd_data->context_un.iocb; job = iocb->set_job; job->dd_data = NULL; /* so timeout handler does not reply */ bmp = iocb->bmp; rsp = &rspiocbq->iocb; ndlp = cmdiocbq->context1; 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 (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; } } else rc = -EACCES; } else job->reply->reply_payload_rcv_len = rsp->un.genreq64.bdl.bdeSize; lpfc_mbuf_free(phba, bmp->virt, bmp->phys); lpfc_sli_release_iocbq(phba, cmdiocbq); lpfc_nlp_put(ndlp); kfree(bmp); kfree(dd_data); /* make error code available to userspace */ job->reply->result = rc; /* complete the job back to userspace */ job->job_done(job); spin_unlock_irqrestore(&phba->ct_ev_lock, flags); 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 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; struct ulp_bde64 *bpl = NULL; uint32_t timeout; struct lpfc_iocbq *cmdiocbq = NULL; IOCB_t *cmd; struct lpfc_dmabuf *bmp = NULL; int request_nseg; int reply_nseg; struct scatterlist *sgel = NULL; int numbde; dma_addr_t busaddr; struct bsg_job_data *dd_data; uint32_t creg_val; int rc = 0; int iocb_stat; /* in case no data is transferred */ job->reply->reply_payload_rcv_len = 0; /* 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; } if (!lpfc_nlp_get(ndlp)) { rc = -ENODEV; goto no_ndlp; } bmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); if (!bmp) { rc = -ENOMEM; goto free_ndlp; } if (ndlp->nlp_flag & NLP_ELS_SND_MASK) { rc = -ENODEV; goto free_bmp; } cmdiocbq = lpfc_sli_get_iocbq(phba); if (!cmdiocbq) { rc = -ENOMEM; goto free_bmp; } cmd = &cmdiocbq->iocb; bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); if (!bmp->virt) { rc = -ENOMEM; goto free_cmdiocbq; } 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; if (phba->sli_rev == LPFC_SLI_REV4) cmd->ulpContext = phba->sli4_hba.rpi_ids[ndlp->nlp_rpi]; cmd->ulpOwner = OWN_CHIP; cmdiocbq->vport = phba->pport; cmdiocbq->context3 = bmp; cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; timeout = phba->fc_ratov * 2; cmd->ulpTimeout = timeout; cmdiocbq->iocb_cmpl = lpfc_bsg_send_mgmt_cmd_cmp; cmdiocbq->context1 = ndlp; cmdiocbq->context2 = dd_data; dd_data->type = TYPE_IOCB; dd_data->context_un.iocb.cmdiocbq = cmdiocbq; dd_data->context_un.iocb.set_job = job; dd_data->context_un.iocb.bmp = bmp; if (phba->cfg_poll & DISABLE_FCP_RING_INT) { if (lpfc_readl(phba->HCregaddr, &creg_val)) { rc = -EIO ; goto free_cmdiocbq; } creg_val |= (HC_R0INT_ENA << LPFC_FCP_RING); writel(creg_val, phba->HCregaddr); readl(phba->HCregaddr); /* flush */ } iocb_stat = lpfc_sli_issue_iocb(phba, LPFC_ELS_RING, cmdiocbq, 0); if (iocb_stat == IOCB_SUCCESS) return 0; /* done for now */ else if (iocb_stat == IOCB_BUSY) rc = -EAGAIN; else rc = -EIO; /* iocb failed so cleanup */ 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); lpfc_mbuf_free(phba, bmp->virt, bmp->phys); free_cmdiocbq: lpfc_sli_release_iocbq(phba, cmdiocbq); free_bmp: kfree(bmp); free_ndlp: lpfc_nlp_put(ndlp); no_ndlp: kfree(dd_data); no_dd_data: /* make error code available to userspace */ job->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 fc_bsg_job *job; IOCB_t *rsp; struct lpfc_nodelist *ndlp; struct lpfc_dmabuf *pbuflist = NULL; struct fc_bsg_ctels_reply *els_reply; uint8_t *rjt_data; unsigned long flags; int rc = 0; spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = cmdiocbq->context1; /* normal completion and timeout crossed paths, already done */ if (!dd_data) { spin_unlock_irqrestore(&phba->ct_ev_lock, flags); return; } cmdiocbq->iocb_flag |= LPFC_IO_WAKE; if (cmdiocbq->context2 && rspiocbq) memcpy(&((struct lpfc_iocbq *)cmdiocbq->context2)->iocb, &rspiocbq->iocb, sizeof(IOCB_t)); job = dd_data->context_un.iocb.set_job; cmdiocbq = dd_data->context_un.iocb.cmdiocbq; rspiocbq = dd_data->context_un.iocb.rspiocbq; rsp = &rspiocbq->iocb; ndlp = dd_data->context_un.iocb.ndlp; 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 (job->reply->result == -EAGAIN) rc = -EAGAIN; else if (rsp->ulpStatus == IOSTAT_SUCCESS) job->reply->reply_payload_rcv_len = rsp->un.elsreq64.bdl.bdeSize; else if (rsp->ulpStatus == IOSTAT_LS_RJT) { job->reply->reply_payload_rcv_len = sizeof(struct fc_bsg_ctels_reply); /* LS_RJT data returned in word 4 */ rjt_data = (uint8_t *)&rsp->un.ulpWord[4]; els_reply = &job->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 rc = -EIO; pbuflist = (struct lpfc_dmabuf *) cmdiocbq->context3; lpfc_mbuf_free(phba, pbuflist->virt, pbuflist->phys); lpfc_sli_release_iocbq(phba, rspiocbq); lpfc_sli_release_iocbq(phba, cmdiocbq); lpfc_nlp_put(ndlp); kfree(dd_data); /* make error code available to userspace */ job->reply->result = rc; job->dd_data = NULL; /* complete the job back to userspace */ job->job_done(job); spin_unlock_irqrestore(&phba->ct_ev_lock, flags); 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 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 request_nseg; int reply_nseg; struct scatterlist *sgel = NULL; int numbde; dma_addr_t busaddr; struct bsg_job_data *dd_data; uint32_t creg_val; int rc = 0; /* in case no data is transferred */ job->reply->reply_payload_rcv_len = 0; /* 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; } if (!lpfc_nlp_get(ndlp)) { rc = -ENODEV; goto free_dd_data; } 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 free_dd_data; } rsp = &rspiocbq->iocb; rpi = ndlp->nlp_rpi; cmdiocbq = lpfc_prep_els_iocb(vport, 1, cmdsize, 0, ndlp, ndlp->nlp_DID, elscmd); if (!cmdiocbq) { rc = -EIO; goto free_rspiocbq; } /* prep els iocb set context1 to the ndlp, context2 to the command * dmabuf, context3 holds the data dmabuf */ 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); if (phba->sli_rev == LPFC_SLI_REV4) cmdiocbq->iocb.ulpContext = phba->sli4_hba.rpi_ids[rpi]; else cmdiocbq->iocb.ulpContext = rpi; cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; cmdiocbq->context1 = NULL; cmdiocbq->context2 = NULL; cmdiocbq->iocb_cmpl = lpfc_bsg_rport_els_cmp; cmdiocbq->context1 = dd_data; cmdiocbq->context_un.ndlp = ndlp; cmdiocbq->context2 = rspiocbq; dd_data->type = TYPE_IOCB; dd_data->context_un.iocb.cmdiocbq = cmdiocbq; dd_data->context_un.iocb.rspiocbq = rspiocbq; dd_data->context_un.iocb.set_job = job; dd_data->context_un.iocb.bmp = NULL; dd_data->context_un.iocb.ndlp = ndlp; 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); lpfc_nlp_put(ndlp); if (rc == IOCB_SUCCESS) return 0; /* done for now */ else if (rc == IOCB_BUSY) rc = -EAGAIN; else rc = -EIO; linkdown_err: 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); lpfc_mbuf_free(phba, pbuflist->virt, pbuflist->phys); lpfc_sli_release_iocbq(phba, cmdiocbq); free_rspiocbq: lpfc_sli_release_iocbq(phba, rspiocbq); free_dd_data: kfree(dd_data); no_dd_data: /* make error code available to userspace */ job->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); } /** * 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; 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 * @phba: * @pring: * @piocbq: * * 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; uint32_t cmd; uint32_t len; struct lpfc_dmabuf *dmabuf = NULL; struct lpfc_bsg_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; struct fc_bsg_job *job = NULL; unsigned long flags; int size = 0; 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->link_state == LPFC_HBA_ERROR || (!(phba->sli.sli_flag & LPFC_SLI_ACTIVE))) 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); } if (dmabuf == NULL) goto error_ct_unsol_exit; 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); 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); 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 == 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->context2; bdeBuf2 = iocbq->context3; } for (i = 0; i < iocbq->iocb.ulpBdeCount; i++) { 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); 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_post_buffer(phba, pring, 1); } break; default: if (!(phba->sli3_options & LPFC_SLI3_HBQ_ENABLED)) lpfc_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) % 64; /* Provide warning for over-run of the ct_ctx array */ if (phba->ct_ctx[evt_dat->immed_dat].flags & 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 = piocbq->iocb.ulpContext; phba->ct_ctx[evt_dat->immed_dat].oxid = piocbq->iocb.unsli3.rcvsli3.ox_id; phba->ct_ctx[evt_dat->immed_dat].SID = piocbq->iocb.un.rcvels.remoteID; phba->ct_ctx[evt_dat->immed_dat].flags = UNSOL_VALID; } else evt_dat->immed_dat = piocbq->iocb.ulpContext; 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); lpfc_bsg_event_unref(evt); job = evt->set_job; evt->set_job = NULL; if (job) { job->reply->reply_payload_rcv_len = size; /* make error code available to userspace */ job->reply->result = 0; job->dd_data = NULL; /* complete the job back to userspace */ spin_unlock_irqrestore(&phba->ct_ev_lock, flags); job->job_done(job); 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_hba_set_event - process a SET_EVENT bsg vendor command * @job: SET_EVENT fc_bsg_job **/ static int lpfc_bsg_hba_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_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; } 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; } event_req = (struct set_ct_event *) job->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; break; } } spin_unlock_irqrestore(&phba->ct_ev_lock, flags); if (&evt->node == &phba->ct_ev_waiters) { /* no event waiting struct yet - first call */ 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; } 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->type = TYPE_EVT; dd_data->context_un.evt = evt; evt->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: if (dd_data != NULL) 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 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_bsg_event *evt; 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 *) job->request->rqst_data.h_vendor.vendor_cmd; event_reply = (struct get_ct_event_reply *) job->reply->reply_data.vendor_reply.vendor_rsp; 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) { 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) { job->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) job->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 job->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; job->reply->result = 0; job->job_done(job); return 0; job_error: job->dd_data = NULL; job->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 fc_bsg_job *job; IOCB_t *rsp; struct lpfc_dmabuf *bmp; struct lpfc_nodelist *ndlp; unsigned long flags; int rc = 0; spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = cmdiocbq->context2; /* normal completion and timeout crossed paths, already done */ if (!dd_data) { spin_unlock_irqrestore(&phba->ct_ev_lock, flags); return; } job = dd_data->context_un.iocb.set_job; bmp = dd_data->context_un.iocb.bmp; rsp = &rspiocbq->iocb; ndlp = dd_data->context_un.iocb.ndlp; pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, job->request_payload.sg_cnt, DMA_TO_DEVICE); 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; } } else rc = -EACCES; } else job->reply->reply_payload_rcv_len = rsp->un.genreq64.bdl.bdeSize; lpfc_mbuf_free(phba, bmp->virt, bmp->phys); lpfc_sli_release_iocbq(phba, cmdiocbq); lpfc_nlp_put(ndlp); kfree(bmp); kfree(dd_data); /* make error code available to userspace */ job->reply->result = rc; job->dd_data = NULL; /* complete the job back to userspace */ job->job_done(job); spin_unlock_irqrestore(&phba->ct_ev_lock, flags); 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. * @bmp: Pointer to a dma buffer descriptor. * @num_entry: Number of enties in the bde. **/ static int lpfc_issue_ct_rsp(struct lpfc_hba *phba, struct fc_bsg_job *job, uint32_t tag, struct lpfc_dmabuf *bmp, int num_entry) { IOCB_t *icmd; struct lpfc_iocbq *ctiocb = NULL; int rc = 0; struct lpfc_nodelist *ndlp = NULL; struct bsg_job_data *dd_data; uint32_t creg_val; /* 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; } icmd = &ctiocb->iocb; icmd->un.xseq64.bdl.ulpIoTag32 = 0; icmd->un.xseq64.bdl.addrHigh = putPaddrHigh(bmp->phys); icmd->un.xseq64.bdl.addrLow = putPaddrLow(bmp->phys); icmd->un.xseq64.bdl.bdeFlags = BUFF_TYPE_BLP_64; icmd->un.xseq64.bdl.bdeSize = (num_entry * sizeof(struct ulp_bde64)); icmd->un.xseq64.w5.hcsw.Fctl = (LS | LA); icmd->un.xseq64.w5.hcsw.Dfctl = 0; icmd->un.xseq64.w5.hcsw.Rctl = FC_RCTL_DD_SOL_CTL; icmd->un.xseq64.w5.hcsw.Type = FC_TYPE_CT; /* Fill in rest of iocb */ icmd->ulpCommand = CMD_XMIT_SEQUENCE64_CX; icmd->ulpBdeCount = 1; icmd->ulpLe = 1; icmd->ulpClass = CLASS3; if (phba->sli_rev == LPFC_SLI_REV4) { /* Do not issue unsol response if oxid not marked as valid */ if (!(phba->ct_ctx[tag].flags & UNSOL_VALID)) { rc = IOCB_ERROR; goto issue_ct_rsp_exit; } icmd->ulpContext = phba->ct_ctx[tag].rxid; icmd->unsli3.rcvsli3.ox_id = phba->ct_ctx[tag].oxid; 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", icmd->ulpContext, phba->ct_ctx[tag].SID); rc = IOCB_ERROR; goto issue_ct_rsp_exit; } /* Check if the ndlp is active */ if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) { rc = -IOCB_ERROR; goto issue_ct_rsp_exit; } /* get a refernece count so the ndlp doesn't go away while * we respond */ if (!lpfc_nlp_get(ndlp)) { rc = -IOCB_ERROR; goto issue_ct_rsp_exit; } icmd->un.ulpWord[3] = phba->sli4_hba.rpi_ids[ndlp->nlp_rpi]; /* The exchange is done, mark the entry as invalid */ phba->ct_ctx[tag].flags &= ~UNSOL_VALID; } else icmd->ulpContext = (ushort) tag; icmd->ulpTimeout = phba->fc_ratov * 2; /* Xmit CT response on exchange */ lpfc_printf_log(phba, KERN_INFO, LOG_ELS, "2722 Xmit CT response on exchange x%x Data: x%x x%x x%x\n", icmd->ulpContext, icmd->ulpIoTag, tag, phba->link_state); ctiocb->iocb_cmpl = NULL; ctiocb->iocb_flag |= LPFC_IO_LIBDFC; ctiocb->vport = phba->pport; ctiocb->context3 = bmp; ctiocb->iocb_cmpl = lpfc_issue_ct_rsp_cmp; ctiocb->context2 = dd_data; ctiocb->context1 = ndlp; dd_data->type = TYPE_IOCB; dd_data->context_un.iocb.cmdiocbq = ctiocb; dd_data->context_un.iocb.rspiocbq = NULL; dd_data->context_un.iocb.set_job = job; dd_data->context_un.iocb.bmp = bmp; dd_data->context_un.iocb.ndlp = ndlp; 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) return 0; /* done for now */ 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 fc_bsg_job *job) { struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; struct lpfc_hba *phba = vport->phba; struct send_mgmt_resp *mgmt_resp = (struct send_mgmt_resp *) job->request->rqst_data.h_vendor.vendor_cmd; struct ulp_bde64 *bpl; struct lpfc_dmabuf *bmp = NULL; struct scatterlist *sgel = NULL; int request_nseg; int numbde; dma_addr_t busaddr; 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 */ job->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; 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++; } rc = lpfc_issue_ct_rsp(phba, job, tag, bmp, request_nseg); if (rc == IOCB_SUCCESS) return 0; /* done for now */ /* TBD need to handle a timeout */ pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, job->request_payload.sg_cnt, DMA_TO_DEVICE); rc = -EACCES; lpfc_mbuf_free(phba, bmp->virt, bmp->phys); send_mgmt_rsp_free_bmp: kfree(bmp); send_mgmt_rsp_exit: /* make error code available to userspace */ job->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_sli_ring *pring; int i = 0; psli = &phba->sli; if (!psli) return -ENODEV; pring = &psli->ring[LPFC_FCP_RING]; if (!pring) 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); } while (pring->txcmplq_cnt) { if (i++ > 500) /* wait up to 5 seconds */ break; msleep(10); } 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 fc_bsg_job *job) { 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 */ job->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 *) job->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 */ job->reply->result = rc; /* complete the job back to userspace if no error */ if (rc == 0) job->job_done(job); 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_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_internal_loopback - set sli4 internal loopback diagnostic * @phba: Pointer to HBA context object. * * This function is responsible for issuing a sli4 mailbox command for setting * up internal loopback diagnostic. */ static int lpfc_sli4_bsg_set_internal_loopback(struct lpfc_hba *phba) { 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, phba->sli4_hba.lnk_info.lnk_no); 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, LPFC_DIAG_LOOPBACK_TYPE_INTERNAL); 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) { int rc; 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; } rc = lpfc_issue_reg_vfi(phba->pport); return rc; } /** * 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 fc_bsg_job *job) { struct diag_mode_set *loopback_mode; uint32_t link_flags, timeout; int i, rc = 0; /* no data to return just the return code */ job->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_error; } rc = lpfc_bsg_diag_mode_enter(phba); if (rc) goto job_error; /* 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_error; /* bring the link to diagnostic mode */ lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, "3129 Bring link to diagnostic state.\n"); loopback_mode = (struct diag_mode_set *) job->request->rqst_data.h_vendor.vendor_cmd; link_flags = loopback_mode->type; timeout = loopback_mode->timeout * 100; 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); if (link_flags == INTERNAL_LOOP_BACK) rc = lpfc_sli4_bsg_set_internal_loopback(phba); else if (link_flags == EXTERNAL_LOOP_BACK) rc = lpfc_hba_init_link_fc_topology(phba, FLAGS_TOPOLOGY_MODE_PT_PT, MBX_NOWAIT); else { 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_error: /* make error code available to userspace */ job->reply->result = rc; /* complete the job back to userspace if no error */ if (rc == 0) job->job_done(job); 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 fc_bsg_job *job) { struct Scsi_Host *shost; struct lpfc_vport *vport; struct lpfc_hba *phba; int rc; shost = job->shost; if (!shost) return -ENODEV; vport = (struct lpfc_vport *)job->shost->hostdata; 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 fc_bsg_job *job) { 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 = job->shost; if (!shost) return -ENODEV; vport = (struct lpfc_vport *)job->shost->hostdata; 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 *) job->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) { rc = -ETIMEDOUT; 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 */ job->reply->result = rc; /* complete the job back to userspace if no error */ if (rc == 0) job->job_done(job); 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 fc_bsg_job *job) { 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; uint32_t timeout; 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 = 0; shost = job->shost; if (!shost) { rc = -ENODEV; goto job_error; } vport = (struct lpfc_vport *)job->shost->hostdata; if (!vport) { rc = -ENODEV; goto job_error; } phba = vport->phba; if (!phba) { rc = -ENODEV; goto job_error; } if (phba->sli_rev < LPFC_SLI_REV4) { rc = -ENODEV; goto job_error; } if (bf_get(lpfc_sli_intf_if_type, &phba->sli4_hba.sli_intf) != LPFC_SLI_INTF_IF_TYPE_2) { rc = -ENODEV; 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 *) job->request->rqst_data.h_vendor.vendor_cmd; timeout = link_diag_test_cmd->timeout * 100; 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 *) job->reply->reply_data.vendor_reply.vendor_rsp; if (job->reply_len < sizeof(struct fc_bsg_request) + sizeof(struct diag_status)) { 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(struct fc_bsg_request) + sizeof(struct diag_status)), 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: rc = 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 */ job->reply->result = rc; /* complete the job back to userspace if no error */ if (rc == 0) job->job_done(job); 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); 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->context1; mbox->context1 = NULL; mbox->context2 = 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, phb