 6b73352134
			
		
	
	
	6b73352134
	
	
	
		
			
			As result of deprecation of MSI-X/MSI enablement functions pci_enable_msix() and pci_enable_msi_block() all drivers using these two interfaces need to be updated to use the new pci_enable_msi_range() or pci_enable_msi_exact() and pci_enable_msix_range() or pci_enable_msix_exact() interfaces. Signed-off-by: Alexander Gordeev <agordeev@redhat.com> Reviewed-by: Tomas Henzl <thenzl@redhat.com> Signed-off-by: Christoph Hellwig <hch@lst.de>
		
			
				
	
	
		
			618 lines
		
	
	
	
		
			15 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			618 lines
		
	
	
	
		
			15 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * This file is part of the Chelsio FCoE driver for Linux.
 | |
|  *
 | |
|  * Copyright (c) 2008-2012 Chelsio Communications, Inc. All rights reserved.
 | |
|  *
 | |
|  * This software is available to you under a choice of one of two
 | |
|  * licenses.  You may choose to be licensed under the terms of the GNU
 | |
|  * General Public License (GPL) Version 2, available from the file
 | |
|  * COPYING in the main directory of this source tree, or the
 | |
|  * OpenIB.org BSD license below:
 | |
|  *
 | |
|  *     Redistribution and use in source and binary forms, with or
 | |
|  *     without modification, are permitted provided that the following
 | |
|  *     conditions are met:
 | |
|  *
 | |
|  *      - Redistributions of source code must retain the above
 | |
|  *        copyright notice, this list of conditions and the following
 | |
|  *        disclaimer.
 | |
|  *
 | |
|  *      - Redistributions in binary form must reproduce the above
 | |
|  *        copyright notice, this list of conditions and the following
 | |
|  *        disclaimer in the documentation and/or other materials
 | |
|  *        provided with the distribution.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 | |
|  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | |
|  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 | |
|  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 | |
|  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 | |
|  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 | |
|  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | |
|  * SOFTWARE.
 | |
|  */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/pci.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/cpumask.h>
 | |
| #include <linux/string.h>
 | |
| 
 | |
| #include "csio_init.h"
 | |
| #include "csio_hw.h"
 | |
| 
 | |
| static irqreturn_t
 | |
| csio_nondata_isr(int irq, void *dev_id)
 | |
| {
 | |
| 	struct csio_hw *hw = (struct csio_hw *) dev_id;
 | |
| 	int rv;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (unlikely(!hw))
 | |
| 		return IRQ_NONE;
 | |
| 
 | |
| 	if (unlikely(pci_channel_offline(hw->pdev))) {
 | |
| 		CSIO_INC_STATS(hw, n_pcich_offline);
 | |
| 		return IRQ_NONE;
 | |
| 	}
 | |
| 
 | |
| 	spin_lock_irqsave(&hw->lock, flags);
 | |
| 	csio_hw_slow_intr_handler(hw);
 | |
| 	rv = csio_mb_isr_handler(hw);
 | |
| 
 | |
| 	if (rv == 0 && !(hw->flags & CSIO_HWF_FWEVT_PENDING)) {
 | |
| 		hw->flags |= CSIO_HWF_FWEVT_PENDING;
 | |
| 		spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 		schedule_work(&hw->evtq_work);
 | |
| 		return IRQ_HANDLED;
 | |
| 	}
 | |
| 	spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * csio_fwevt_handler - Common FW event handler routine.
 | |
|  * @hw: HW module.
 | |
|  *
 | |
|  * This is the ISR for FW events. It is shared b/w MSIX
 | |
|  * and INTx handlers.
 | |
|  */
 | |
| static void
 | |
| csio_fwevt_handler(struct csio_hw *hw)
 | |
| {
 | |
| 	int rv;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	rv = csio_fwevtq_handler(hw);
 | |
| 
 | |
| 	spin_lock_irqsave(&hw->lock, flags);
 | |
| 	if (rv == 0 && !(hw->flags & CSIO_HWF_FWEVT_PENDING)) {
 | |
| 		hw->flags |= CSIO_HWF_FWEVT_PENDING;
 | |
| 		spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 		schedule_work(&hw->evtq_work);
 | |
| 		return;
 | |
| 	}
 | |
| 	spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 
 | |
| } /* csio_fwevt_handler */
 | |
| 
 | |
| /*
 | |
|  * csio_fwevt_isr() - FW events MSIX ISR
 | |
|  * @irq:
 | |
|  * @dev_id:
 | |
|  *
 | |
|  * Process WRs on the FW event queue.
 | |
|  *
 | |
|  */
 | |
| static irqreturn_t
 | |
| csio_fwevt_isr(int irq, void *dev_id)
 | |
| {
 | |
| 	struct csio_hw *hw = (struct csio_hw *) dev_id;
 | |
| 
 | |
| 	if (unlikely(!hw))
 | |
| 		return IRQ_NONE;
 | |
| 
 | |
| 	if (unlikely(pci_channel_offline(hw->pdev))) {
 | |
| 		CSIO_INC_STATS(hw, n_pcich_offline);
 | |
| 		return IRQ_NONE;
 | |
| 	}
 | |
| 
 | |
| 	csio_fwevt_handler(hw);
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * csio_fwevt_isr() - INTx wrapper for handling FW events.
 | |
|  * @irq:
 | |
|  * @dev_id:
 | |
|  */
 | |
| void
 | |
| csio_fwevt_intx_handler(struct csio_hw *hw, void *wr, uint32_t len,
 | |
| 			   struct csio_fl_dma_buf *flb, void *priv)
 | |
| {
 | |
| 	csio_fwevt_handler(hw);
 | |
| } /* csio_fwevt_intx_handler */
 | |
| 
 | |
| /*
 | |
|  * csio_process_scsi_cmpl - Process a SCSI WR completion.
 | |
|  * @hw: HW module.
 | |
|  * @wr: The completed WR from the ingress queue.
 | |
|  * @len: Length of the WR.
 | |
|  * @flb: Freelist buffer array.
 | |
|  *
 | |
|  */
 | |
| static void
 | |
| csio_process_scsi_cmpl(struct csio_hw *hw, void *wr, uint32_t len,
 | |
| 			struct csio_fl_dma_buf *flb, void *cbfn_q)
 | |
| {
 | |
| 	struct csio_ioreq *ioreq;
 | |
| 	uint8_t *scsiwr;
 | |
| 	uint8_t subop;
 | |
| 	void *cmnd;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	ioreq = csio_scsi_cmpl_handler(hw, wr, len, flb, NULL, &scsiwr);
 | |
| 	if (likely(ioreq)) {
 | |
| 		if (unlikely(*scsiwr == FW_SCSI_ABRT_CLS_WR)) {
 | |
| 			subop = FW_SCSI_ABRT_CLS_WR_SUB_OPCODE_GET(
 | |
| 					((struct fw_scsi_abrt_cls_wr *)
 | |
| 					    scsiwr)->sub_opcode_to_chk_all_io);
 | |
| 
 | |
| 			csio_dbg(hw, "%s cmpl recvd ioreq:%p status:%d\n",
 | |
| 				    subop ? "Close" : "Abort",
 | |
| 				    ioreq, ioreq->wr_status);
 | |
| 
 | |
| 			spin_lock_irqsave(&hw->lock, flags);
 | |
| 			if (subop)
 | |
| 				csio_scsi_closed(ioreq,
 | |
| 						 (struct list_head *)cbfn_q);
 | |
| 			else
 | |
| 				csio_scsi_aborted(ioreq,
 | |
| 						  (struct list_head *)cbfn_q);
 | |
| 			/*
 | |
| 			 * We call scsi_done for I/Os that driver thinks aborts
 | |
| 			 * have timed out. If there is a race caused by FW
 | |
| 			 * completing abort at the exact same time that the
 | |
| 			 * driver has deteced the abort timeout, the following
 | |
| 			 * check prevents calling of scsi_done twice for the
 | |
| 			 * same command: once from the eh_abort_handler, another
 | |
| 			 * from csio_scsi_isr_handler(). This also avoids the
 | |
| 			 * need to check if csio_scsi_cmnd(req) is NULL in the
 | |
| 			 * fast path.
 | |
| 			 */
 | |
| 			cmnd = csio_scsi_cmnd(ioreq);
 | |
| 			if (unlikely(cmnd == NULL))
 | |
| 				list_del_init(&ioreq->sm.sm_list);
 | |
| 
 | |
| 			spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 
 | |
| 			if (unlikely(cmnd == NULL))
 | |
| 				csio_put_scsi_ioreq_lock(hw,
 | |
| 						csio_hw_to_scsim(hw), ioreq);
 | |
| 		} else {
 | |
| 			spin_lock_irqsave(&hw->lock, flags);
 | |
| 			csio_scsi_completed(ioreq, (struct list_head *)cbfn_q);
 | |
| 			spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * csio_scsi_isr_handler() - Common SCSI ISR handler.
 | |
|  * @iq: Ingress queue pointer.
 | |
|  *
 | |
|  * Processes SCSI completions on the SCSI IQ indicated by scm->iq_idx
 | |
|  * by calling csio_wr_process_iq_idx. If there are completions on the
 | |
|  * isr_cbfn_q, yank them out into a local queue and call their io_cbfns.
 | |
|  * Once done, add these completions onto the freelist.
 | |
|  * This routine is shared b/w MSIX and INTx.
 | |
|  */
 | |
| static inline irqreturn_t
 | |
| csio_scsi_isr_handler(struct csio_q *iq)
 | |
| {
 | |
| 	struct csio_hw *hw = (struct csio_hw *)iq->owner;
 | |
| 	LIST_HEAD(cbfn_q);
 | |
| 	struct list_head *tmp;
 | |
| 	struct csio_scsim *scm;
 | |
| 	struct csio_ioreq *ioreq;
 | |
| 	int isr_completions = 0;
 | |
| 
 | |
| 	scm = csio_hw_to_scsim(hw);
 | |
| 
 | |
| 	if (unlikely(csio_wr_process_iq(hw, iq, csio_process_scsi_cmpl,
 | |
| 					&cbfn_q) != 0))
 | |
| 		return IRQ_NONE;
 | |
| 
 | |
| 	/* Call back the completion routines */
 | |
| 	list_for_each(tmp, &cbfn_q) {
 | |
| 		ioreq = (struct csio_ioreq *)tmp;
 | |
| 		isr_completions++;
 | |
| 		ioreq->io_cbfn(hw, ioreq);
 | |
| 		/* Release ddp buffer if used for this req */
 | |
| 		if (unlikely(ioreq->dcopy))
 | |
| 			csio_put_scsi_ddp_list_lock(hw, scm, &ioreq->gen_list,
 | |
| 						    ioreq->nsge);
 | |
| 	}
 | |
| 
 | |
| 	if (isr_completions) {
 | |
| 		/* Return the ioreqs back to ioreq->freelist */
 | |
| 		csio_put_scsi_ioreq_list_lock(hw, scm, &cbfn_q,
 | |
| 					      isr_completions);
 | |
| 	}
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * csio_scsi_isr() - SCSI MSIX handler
 | |
|  * @irq:
 | |
|  * @dev_id:
 | |
|  *
 | |
|  * This is the top level SCSI MSIX handler. Calls csio_scsi_isr_handler()
 | |
|  * for handling SCSI completions.
 | |
|  */
 | |
| static irqreturn_t
 | |
| csio_scsi_isr(int irq, void *dev_id)
 | |
| {
 | |
| 	struct csio_q *iq = (struct csio_q *) dev_id;
 | |
| 	struct csio_hw *hw;
 | |
| 
 | |
| 	if (unlikely(!iq))
 | |
| 		return IRQ_NONE;
 | |
| 
 | |
| 	hw = (struct csio_hw *)iq->owner;
 | |
| 
 | |
| 	if (unlikely(pci_channel_offline(hw->pdev))) {
 | |
| 		CSIO_INC_STATS(hw, n_pcich_offline);
 | |
| 		return IRQ_NONE;
 | |
| 	}
 | |
| 
 | |
| 	csio_scsi_isr_handler(iq);
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * csio_scsi_intx_handler() - SCSI INTx handler
 | |
|  * @irq:
 | |
|  * @dev_id:
 | |
|  *
 | |
|  * This is the top level SCSI INTx handler. Calls csio_scsi_isr_handler()
 | |
|  * for handling SCSI completions.
 | |
|  */
 | |
| void
 | |
| csio_scsi_intx_handler(struct csio_hw *hw, void *wr, uint32_t len,
 | |
| 			struct csio_fl_dma_buf *flb, void *priv)
 | |
| {
 | |
| 	struct csio_q *iq = priv;
 | |
| 
 | |
| 	csio_scsi_isr_handler(iq);
 | |
| 
 | |
| } /* csio_scsi_intx_handler */
 | |
| 
 | |
| /*
 | |
|  * csio_fcoe_isr() - INTx/MSI interrupt service routine for FCoE.
 | |
|  * @irq:
 | |
|  * @dev_id:
 | |
|  *
 | |
|  *
 | |
|  */
 | |
| static irqreturn_t
 | |
| csio_fcoe_isr(int irq, void *dev_id)
 | |
| {
 | |
| 	struct csio_hw *hw = (struct csio_hw *) dev_id;
 | |
| 	struct csio_q *intx_q = NULL;
 | |
| 	int rv;
 | |
| 	irqreturn_t ret = IRQ_NONE;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (unlikely(!hw))
 | |
| 		return IRQ_NONE;
 | |
| 
 | |
| 	if (unlikely(pci_channel_offline(hw->pdev))) {
 | |
| 		CSIO_INC_STATS(hw, n_pcich_offline);
 | |
| 		return IRQ_NONE;
 | |
| 	}
 | |
| 
 | |
| 	/* Disable the interrupt for this PCI function. */
 | |
| 	if (hw->intr_mode == CSIO_IM_INTX)
 | |
| 		csio_wr_reg32(hw, 0, MYPF_REG(PCIE_PF_CLI));
 | |
| 
 | |
| 	/*
 | |
| 	 * The read in the following function will flush the
 | |
| 	 * above write.
 | |
| 	 */
 | |
| 	if (csio_hw_slow_intr_handler(hw))
 | |
| 		ret = IRQ_HANDLED;
 | |
| 
 | |
| 	/* Get the INTx Forward interrupt IQ. */
 | |
| 	intx_q = csio_get_q(hw, hw->intr_iq_idx);
 | |
| 
 | |
| 	CSIO_DB_ASSERT(intx_q);
 | |
| 
 | |
| 	/* IQ handler is not possible for intx_q, hence pass in NULL */
 | |
| 	if (likely(csio_wr_process_iq(hw, intx_q, NULL, NULL) == 0))
 | |
| 		ret = IRQ_HANDLED;
 | |
| 
 | |
| 	spin_lock_irqsave(&hw->lock, flags);
 | |
| 	rv = csio_mb_isr_handler(hw);
 | |
| 	if (rv == 0 && !(hw->flags & CSIO_HWF_FWEVT_PENDING)) {
 | |
| 		hw->flags |= CSIO_HWF_FWEVT_PENDING;
 | |
| 		spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 		schedule_work(&hw->evtq_work);
 | |
| 		return IRQ_HANDLED;
 | |
| 	}
 | |
| 	spin_unlock_irqrestore(&hw->lock, flags);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void
 | |
| csio_add_msix_desc(struct csio_hw *hw)
 | |
| {
 | |
| 	int i;
 | |
| 	struct csio_msix_entries *entryp = &hw->msix_entries[0];
 | |
| 	int k = CSIO_EXTRA_VECS;
 | |
| 	int len = sizeof(entryp->desc) - 1;
 | |
| 	int cnt = hw->num_sqsets + k;
 | |
| 
 | |
| 	/* Non-data vector */
 | |
| 	memset(entryp->desc, 0, len + 1);
 | |
| 	snprintf(entryp->desc, len, "csio-%02x:%02x:%x-nondata",
 | |
| 		 CSIO_PCI_BUS(hw), CSIO_PCI_DEV(hw), CSIO_PCI_FUNC(hw));
 | |
| 
 | |
| 	entryp++;
 | |
| 	memset(entryp->desc, 0, len + 1);
 | |
| 	snprintf(entryp->desc, len, "csio-%02x:%02x:%x-fwevt",
 | |
| 		 CSIO_PCI_BUS(hw), CSIO_PCI_DEV(hw), CSIO_PCI_FUNC(hw));
 | |
| 	entryp++;
 | |
| 
 | |
| 	/* Name SCSI vecs */
 | |
| 	for (i = k; i < cnt; i++, entryp++) {
 | |
| 		memset(entryp->desc, 0, len + 1);
 | |
| 		snprintf(entryp->desc, len, "csio-%02x:%02x:%x-scsi%d",
 | |
| 			 CSIO_PCI_BUS(hw), CSIO_PCI_DEV(hw),
 | |
| 			 CSIO_PCI_FUNC(hw), i - CSIO_EXTRA_VECS);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int
 | |
| csio_request_irqs(struct csio_hw *hw)
 | |
| {
 | |
| 	int rv, i, j, k = 0;
 | |
| 	struct csio_msix_entries *entryp = &hw->msix_entries[0];
 | |
| 	struct csio_scsi_cpu_info *info;
 | |
| 
 | |
| 	if (hw->intr_mode != CSIO_IM_MSIX) {
 | |
| 		rv = request_irq(hw->pdev->irq, csio_fcoe_isr,
 | |
| 					(hw->intr_mode == CSIO_IM_MSI) ?
 | |
| 							0 : IRQF_SHARED,
 | |
| 					KBUILD_MODNAME, hw);
 | |
| 		if (rv) {
 | |
| 			if (hw->intr_mode == CSIO_IM_MSI)
 | |
| 				pci_disable_msi(hw->pdev);
 | |
| 			csio_err(hw, "Failed to allocate interrupt line.\n");
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	/* Add the MSIX vector descriptions */
 | |
| 	csio_add_msix_desc(hw);
 | |
| 
 | |
| 	rv = request_irq(entryp[k].vector, csio_nondata_isr, 0,
 | |
| 			 entryp[k].desc, hw);
 | |
| 	if (rv) {
 | |
| 		csio_err(hw, "IRQ request failed for vec %d err:%d\n",
 | |
| 			 entryp[k].vector, rv);
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	entryp[k++].dev_id = (void *)hw;
 | |
| 
 | |
| 	rv = request_irq(entryp[k].vector, csio_fwevt_isr, 0,
 | |
| 			 entryp[k].desc, hw);
 | |
| 	if (rv) {
 | |
| 		csio_err(hw, "IRQ request failed for vec %d err:%d\n",
 | |
| 			 entryp[k].vector, rv);
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	entryp[k++].dev_id = (void *)hw;
 | |
| 
 | |
| 	/* Allocate IRQs for SCSI */
 | |
| 	for (i = 0; i < hw->num_pports; i++) {
 | |
| 		info = &hw->scsi_cpu_info[i];
 | |
| 		for (j = 0; j < info->max_cpus; j++, k++) {
 | |
| 			struct csio_scsi_qset *sqset = &hw->sqset[i][j];
 | |
| 			struct csio_q *q = hw->wrm.q_arr[sqset->iq_idx];
 | |
| 
 | |
| 			rv = request_irq(entryp[k].vector, csio_scsi_isr, 0,
 | |
| 					 entryp[k].desc, q);
 | |
| 			if (rv) {
 | |
| 				csio_err(hw,
 | |
| 				       "IRQ request failed for vec %d err:%d\n",
 | |
| 				       entryp[k].vector, rv);
 | |
| 				goto err;
 | |
| 			}
 | |
| 
 | |
| 			entryp[k].dev_id = (void *)q;
 | |
| 
 | |
| 		} /* for all scsi cpus */
 | |
| 	} /* for all ports */
 | |
| 
 | |
| out:
 | |
| 	hw->flags |= CSIO_HWF_HOST_INTR_ENABLED;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err:
 | |
| 	for (i = 0; i < k; i++) {
 | |
| 		entryp = &hw->msix_entries[i];
 | |
| 		free_irq(entryp->vector, entryp->dev_id);
 | |
| 	}
 | |
| 	pci_disable_msix(hw->pdev);
 | |
| 
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static void
 | |
| csio_disable_msix(struct csio_hw *hw, bool free)
 | |
| {
 | |
| 	int i;
 | |
| 	struct csio_msix_entries *entryp;
 | |
| 	int cnt = hw->num_sqsets + CSIO_EXTRA_VECS;
 | |
| 
 | |
| 	if (free) {
 | |
| 		for (i = 0; i < cnt; i++) {
 | |
| 			entryp = &hw->msix_entries[i];
 | |
| 			free_irq(entryp->vector, entryp->dev_id);
 | |
| 		}
 | |
| 	}
 | |
| 	pci_disable_msix(hw->pdev);
 | |
| }
 | |
| 
 | |
| /* Reduce per-port max possible CPUs */
 | |
| static void
 | |
| csio_reduce_sqsets(struct csio_hw *hw, int cnt)
 | |
| {
 | |
| 	int i;
 | |
| 	struct csio_scsi_cpu_info *info;
 | |
| 
 | |
| 	while (cnt < hw->num_sqsets) {
 | |
| 		for (i = 0; i < hw->num_pports; i++) {
 | |
| 			info = &hw->scsi_cpu_info[i];
 | |
| 			if (info->max_cpus > 1) {
 | |
| 				info->max_cpus--;
 | |
| 				hw->num_sqsets--;
 | |
| 				if (hw->num_sqsets <= cnt)
 | |
| 					break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	csio_dbg(hw, "Reduced sqsets to %d\n", hw->num_sqsets);
 | |
| }
 | |
| 
 | |
| static int
 | |
| csio_enable_msix(struct csio_hw *hw)
 | |
| {
 | |
| 	int i, j, k, n, min, cnt;
 | |
| 	struct csio_msix_entries *entryp;
 | |
| 	struct msix_entry *entries;
 | |
| 	int extra = CSIO_EXTRA_VECS;
 | |
| 	struct csio_scsi_cpu_info *info;
 | |
| 
 | |
| 	min = hw->num_pports + extra;
 | |
| 	cnt = hw->num_sqsets + extra;
 | |
| 
 | |
| 	/* Max vectors required based on #niqs configured in fw */
 | |
| 	if (hw->flags & CSIO_HWF_USING_SOFT_PARAMS || !csio_is_hw_master(hw))
 | |
| 		cnt = min_t(uint8_t, hw->cfg_niq, cnt);
 | |
| 
 | |
| 	entries = kzalloc(sizeof(struct msix_entry) * cnt, GFP_KERNEL);
 | |
| 	if (!entries)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	for (i = 0; i < cnt; i++)
 | |
| 		entries[i].entry = (uint16_t)i;
 | |
| 
 | |
| 	csio_dbg(hw, "FW supp #niq:%d, trying %d msix's\n", hw->cfg_niq, cnt);
 | |
| 
 | |
| 	cnt = pci_enable_msix_range(hw->pdev, entries, min, cnt);
 | |
| 	if (cnt < 0) {
 | |
| 		kfree(entries);
 | |
| 		return cnt;
 | |
| 	}
 | |
| 
 | |
| 	if (cnt < (hw->num_sqsets + extra)) {
 | |
| 		csio_dbg(hw, "Reducing sqsets to %d\n", cnt - extra);
 | |
| 		csio_reduce_sqsets(hw, cnt - extra);
 | |
| 	}
 | |
| 
 | |
| 	/* Save off vectors */
 | |
| 	for (i = 0; i < cnt; i++) {
 | |
| 		entryp = &hw->msix_entries[i];
 | |
| 		entryp->vector = entries[i].vector;
 | |
| 	}
 | |
| 
 | |
| 	/* Distribute vectors */
 | |
| 	k = 0;
 | |
| 	csio_set_nondata_intr_idx(hw, entries[k].entry);
 | |
| 	csio_set_mb_intr_idx(csio_hw_to_mbm(hw), entries[k++].entry);
 | |
| 	csio_set_fwevt_intr_idx(hw, entries[k++].entry);
 | |
| 
 | |
| 	for (i = 0; i < hw->num_pports; i++) {
 | |
| 		info = &hw->scsi_cpu_info[i];
 | |
| 
 | |
| 		for (j = 0; j < hw->num_scsi_msix_cpus; j++) {
 | |
| 			n = (j % info->max_cpus) +  k;
 | |
| 			hw->sqset[i][j].intr_idx = entries[n].entry;
 | |
| 		}
 | |
| 
 | |
| 		k += info->max_cpus;
 | |
| 	}
 | |
| 
 | |
| 	kfree(entries);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void
 | |
| csio_intr_enable(struct csio_hw *hw)
 | |
| {
 | |
| 	hw->intr_mode = CSIO_IM_NONE;
 | |
| 	hw->flags &= ~CSIO_HWF_HOST_INTR_ENABLED;
 | |
| 
 | |
| 	/* Try MSIX, then MSI or fall back to INTx */
 | |
| 	if ((csio_msi == 2) && !csio_enable_msix(hw))
 | |
| 		hw->intr_mode = CSIO_IM_MSIX;
 | |
| 	else {
 | |
| 		/* Max iqs required based on #niqs configured in fw */
 | |
| 		if (hw->flags & CSIO_HWF_USING_SOFT_PARAMS ||
 | |
| 			!csio_is_hw_master(hw)) {
 | |
| 			int extra = CSIO_EXTRA_MSI_IQS;
 | |
| 
 | |
| 			if (hw->cfg_niq < (hw->num_sqsets + extra)) {
 | |
| 				csio_dbg(hw, "Reducing sqsets to %d\n",
 | |
| 					 hw->cfg_niq - extra);
 | |
| 				csio_reduce_sqsets(hw, hw->cfg_niq - extra);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if ((csio_msi == 1) && !pci_enable_msi(hw->pdev))
 | |
| 			hw->intr_mode = CSIO_IM_MSI;
 | |
| 		else
 | |
| 			hw->intr_mode = CSIO_IM_INTX;
 | |
| 	}
 | |
| 
 | |
| 	csio_dbg(hw, "Using %s interrupt mode.\n",
 | |
| 		(hw->intr_mode == CSIO_IM_MSIX) ? "MSIX" :
 | |
| 		((hw->intr_mode == CSIO_IM_MSI) ? "MSI" : "INTx"));
 | |
| }
 | |
| 
 | |
| void
 | |
| csio_intr_disable(struct csio_hw *hw, bool free)
 | |
| {
 | |
| 	csio_hw_intr_disable(hw);
 | |
| 
 | |
| 	switch (hw->intr_mode) {
 | |
| 	case CSIO_IM_MSIX:
 | |
| 		csio_disable_msix(hw, free);
 | |
| 		break;
 | |
| 	case CSIO_IM_MSI:
 | |
| 		if (free)
 | |
| 			free_irq(hw->pdev->irq, hw);
 | |
| 		pci_disable_msi(hw->pdev);
 | |
| 		break;
 | |
| 	case CSIO_IM_INTX:
 | |
| 		if (free)
 | |
| 			free_irq(hw->pdev->irq, hw);
 | |
| 		break;
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| 	hw->intr_mode = CSIO_IM_NONE;
 | |
| 	hw->flags &= ~CSIO_HWF_HOST_INTR_ENABLED;
 | |
| }
 |