forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
5.0 KiB
194 lines
5.0 KiB
/* |
|
* Copyright 2014 Cisco Systems, Inc. All rights reserved. |
|
* |
|
* This program is free software; you may redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; version 2 of the License. |
|
* |
|
* 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/string.h> |
|
#include <linux/errno.h> |
|
#include <linux/pci.h> |
|
#include <linux/interrupt.h> |
|
|
|
#include "vnic_dev.h" |
|
#include "vnic_intr.h" |
|
#include "vnic_stats.h" |
|
#include "snic_io.h" |
|
#include "snic.h" |
|
|
|
|
|
/* |
|
* snic_isr_msix_wq : MSIx ISR for work queue. |
|
*/ |
|
|
|
static irqreturn_t |
|
snic_isr_msix_wq(int irq, void *data) |
|
{ |
|
struct snic *snic = data; |
|
unsigned long wq_work_done = 0; |
|
|
|
snic->s_stats.misc.last_isr_time = jiffies; |
|
atomic64_inc(&snic->s_stats.misc.ack_isr_cnt); |
|
|
|
wq_work_done = snic_wq_cmpl_handler(snic, -1); |
|
svnic_intr_return_credits(&snic->intr[SNIC_MSIX_WQ], |
|
wq_work_done, |
|
1 /* unmask intr */, |
|
1 /* reset intr timer */); |
|
|
|
return IRQ_HANDLED; |
|
} /* end of snic_isr_msix_wq */ |
|
|
|
static irqreturn_t |
|
snic_isr_msix_io_cmpl(int irq, void *data) |
|
{ |
|
struct snic *snic = data; |
|
unsigned long iocmpl_work_done = 0; |
|
|
|
snic->s_stats.misc.last_isr_time = jiffies; |
|
atomic64_inc(&snic->s_stats.misc.cmpl_isr_cnt); |
|
|
|
iocmpl_work_done = snic_fwcq_cmpl_handler(snic, -1); |
|
svnic_intr_return_credits(&snic->intr[SNIC_MSIX_IO_CMPL], |
|
iocmpl_work_done, |
|
1 /* unmask intr */, |
|
1 /* reset intr timer */); |
|
|
|
return IRQ_HANDLED; |
|
} /* end of snic_isr_msix_io_cmpl */ |
|
|
|
static irqreturn_t |
|
snic_isr_msix_err_notify(int irq, void *data) |
|
{ |
|
struct snic *snic = data; |
|
|
|
snic->s_stats.misc.last_isr_time = jiffies; |
|
atomic64_inc(&snic->s_stats.misc.errnotify_isr_cnt); |
|
|
|
svnic_intr_return_all_credits(&snic->intr[SNIC_MSIX_ERR_NOTIFY]); |
|
snic_log_q_error(snic); |
|
|
|
/*Handling link events */ |
|
snic_handle_link_event(snic); |
|
|
|
return IRQ_HANDLED; |
|
} /* end of snic_isr_msix_err_notify */ |
|
|
|
|
|
void |
|
snic_free_intr(struct snic *snic) |
|
{ |
|
int i; |
|
|
|
/* ONLY interrupt mode MSIX is supported */ |
|
for (i = 0; i < ARRAY_SIZE(snic->msix); i++) { |
|
if (snic->msix[i].requested) { |
|
free_irq(pci_irq_vector(snic->pdev, i), |
|
snic->msix[i].devid); |
|
} |
|
} |
|
} /* end of snic_free_intr */ |
|
|
|
int |
|
snic_request_intr(struct snic *snic) |
|
{ |
|
int ret = 0, i; |
|
enum vnic_dev_intr_mode intr_mode; |
|
|
|
intr_mode = svnic_dev_get_intr_mode(snic->vdev); |
|
SNIC_BUG_ON(intr_mode != VNIC_DEV_INTR_MODE_MSIX); |
|
|
|
/* |
|
* Currently HW supports single WQ and CQ. So passing devid as snic. |
|
* When hardware supports multiple WQs and CQs, one idea is |
|
* to pass devid as corresponding WQ or CQ ptr and retrieve snic |
|
* from queue ptr. |
|
* Except for err_notify, which is always one. |
|
*/ |
|
sprintf(snic->msix[SNIC_MSIX_WQ].devname, |
|
"%.11s-scsi-wq", |
|
snic->name); |
|
snic->msix[SNIC_MSIX_WQ].isr = snic_isr_msix_wq; |
|
snic->msix[SNIC_MSIX_WQ].devid = snic; |
|
|
|
sprintf(snic->msix[SNIC_MSIX_IO_CMPL].devname, |
|
"%.11s-io-cmpl", |
|
snic->name); |
|
snic->msix[SNIC_MSIX_IO_CMPL].isr = snic_isr_msix_io_cmpl; |
|
snic->msix[SNIC_MSIX_IO_CMPL].devid = snic; |
|
|
|
sprintf(snic->msix[SNIC_MSIX_ERR_NOTIFY].devname, |
|
"%.11s-err-notify", |
|
snic->name); |
|
snic->msix[SNIC_MSIX_ERR_NOTIFY].isr = snic_isr_msix_err_notify; |
|
snic->msix[SNIC_MSIX_ERR_NOTIFY].devid = snic; |
|
|
|
for (i = 0; i < ARRAY_SIZE(snic->msix); i++) { |
|
ret = request_irq(pci_irq_vector(snic->pdev, i), |
|
snic->msix[i].isr, |
|
0, |
|
snic->msix[i].devname, |
|
snic->msix[i].devid); |
|
if (ret) { |
|
SNIC_HOST_ERR(snic->shost, |
|
"MSI-X: request_irq(%d) failed %d\n", |
|
i, |
|
ret); |
|
snic_free_intr(snic); |
|
break; |
|
} |
|
snic->msix[i].requested = 1; |
|
} |
|
|
|
return ret; |
|
} /* end of snic_request_intr */ |
|
|
|
int |
|
snic_set_intr_mode(struct snic *snic) |
|
{ |
|
unsigned int n = ARRAY_SIZE(snic->wq); |
|
unsigned int m = SNIC_CQ_IO_CMPL_MAX; |
|
unsigned int vecs = n + m + 1; |
|
|
|
/* |
|
* We need n WQs, m CQs, and n+m+1 INTRs |
|
* (last INTR is used for WQ/CQ errors and notification area |
|
*/ |
|
BUILD_BUG_ON((ARRAY_SIZE(snic->wq) + SNIC_CQ_IO_CMPL_MAX) > |
|
ARRAY_SIZE(snic->intr)); |
|
|
|
if (snic->wq_count < n || snic->cq_count < n + m) |
|
goto fail; |
|
|
|
if (pci_alloc_irq_vectors(snic->pdev, vecs, vecs, PCI_IRQ_MSIX) < 0) |
|
goto fail; |
|
|
|
snic->wq_count = n; |
|
snic->cq_count = n + m; |
|
snic->intr_count = vecs; |
|
snic->err_intr_offset = SNIC_MSIX_ERR_NOTIFY; |
|
|
|
SNIC_ISR_DBG(snic->shost, "Using MSI-X Interrupts\n"); |
|
svnic_dev_set_intr_mode(snic->vdev, VNIC_DEV_INTR_MODE_MSIX); |
|
return 0; |
|
fail: |
|
svnic_dev_set_intr_mode(snic->vdev, VNIC_DEV_INTR_MODE_UNKNOWN); |
|
return -EINVAL; |
|
} /* end of snic_set_intr_mode */ |
|
|
|
void |
|
snic_clear_intr_mode(struct snic *snic) |
|
{ |
|
pci_free_irq_vectors(snic->pdev); |
|
svnic_dev_set_intr_mode(snic->vdev, VNIC_DEV_INTR_MODE_INTX); |
|
}
|
|
|