/*
* Copyright (c) 2005-2009 Brocade Communications Systems, Inc.
* All rights reserved
* www.brocade.com
*
* Linux driver for Brocade Fibre Channel Host Bus Adapter.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License (GPL) Version 2 as
* published by the Free Software Foundation
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include "bfad_drv.h"
#include "bfad_trcmod.h"
BFA_TRC_FILE(LDRV, INTR);
/**
* bfa_isr BFA driver interrupt functions
*/
static int msix_disable_cb;
static int msix_disable_ct;
module_param(msix_disable_cb, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(msix_disable_cb, "Disable MSIX for Brocade-415/425/815/825"
" cards, default=0, Range[false:0|true:1]");
module_param(msix_disable_ct, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(msix_disable_ct, "Disable MSIX for Brocade-1010/1020/804"
" cards, default=0, Range[false:0|true:1]");
/**
* Line based interrupt handler.
*/
static irqreturn_t
bfad_intx(int irq, void *dev_id)
{
struct bfad_s *bfad = dev_id;
struct list_head doneq;
unsigned long flags;
bfa_boolean_t rc;
spin_lock_irqsave(&bfad->bfad_lock, flags);
rc = bfa_intx(&bfad->bfa);
if (!rc) {
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
return IRQ_NONE;
}
bfa_comp_deq(&bfad->bfa, &doneq);
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
if (!list_empty(&doneq)) {
bfa_comp_process(&bfad->bfa, &doneq);
spin_lock_irqsave(&bfad->bfad_lock, flags);
bfa_comp_free(&bfad->bfa, &doneq);
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
bfa_trc_fp(bfad, irq);
}
return IRQ_HANDLED;
}
static irqreturn_t
bfad_msix(int irq, void *dev_id)
{
struct bfad_msix_s *vec = dev_id;
struct bfad_s *bfad = vec->bfad;
struct list_head doneq;
unsigned long flags;
spin_lock_irqsave(&bfad->bfad_lock, flags);
bfa_msix(&bfad->bfa, vec->msix.entry);
bfa_comp_deq(&bfad->bfa, &doneq);
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
if (!list_empty(&doneq)) {
bfa_comp_process(&bfad->bfa, &doneq);
spin_lock_irqsave(&bfad->bfad_lock, flags);
bfa_comp_free(&bfad->bfa, &doneq);
spin_unlock_irqrestore(&bfad->bfad_lock, flags);
}
return IRQ_HANDLED;
}
/**
* Initialize the MSIX entry table.
*/
static void
bfad_init_msix_entry(struct bfad_s *bfad, struct msix_entry *msix_entries,
int mask, int max_bit)
{
int i;
int match = 0x00000001;
for (i = 0, bfad->nvec = 0; i < MAX_MSIX_ENTRY; i++) {
if (mask & match) {
bfad->msix_tab[bfad->nvec].msix.entry = i;
bfad->msix_tab[bfad->nvec].bfad = bfad;
msix_entries[bfad->nvec].entry = i;
bfad->nvec++;
}
match <<= 1;
}
}
int
bfad_install_msix_handler(struct bfad_s *bfad)
{
int i, error = 0;
for (i = 0; i < bfad->nvec; i++) {
error = request_irq(bfad->msix_tab[i].msix.vector,
(irq_handler_t) bfad_msix, 0,
BFAD_DRIVER_NAME, &bfad->msix_tab[i]);
bfa_trc(bfad, i);
bfa_trc(bfad, bfad->msix_tab[i].msix.vector);
if (error) {
int j;
for (j = 0; j < i; j++)
free_irq(bfad->msix_tab[j].msix.vector,
&bfad->msix_tab[j]);
return 1;
}
}
return 0;
}
/**
* Setup MSIX based interrupt.
*/
int
bfad_setup_intr(struct bfad_s *bfad)
{
int error = 0;
u32 mask = 0, i, num_bit = 0, max_bit = 0;
struct msix_entry msix_entries[MAX_MSIX_ENTRY];
struct pci_dev *pdev = bfad->pcidev;
/* Call BFA to get the msix map for this PCI function. */
bfa_msix_getvecs(&bfad->bfa, &mask, &num_bit, &max_bit);
/* Set up the msix entry table */
bfad_init_msix_entry(bfad, msix_entries, mask, max_bit);
if ((bfa_asic_id_ct(pdev->device) && !msix_disable_ct) ||
(!bfa_asic_id_ct(pdev->device) && !msix_disable_cb)) {
error = pci_enable_msix(bfad->pcidev, msix_entries, bfad->nvec);
if (error) {
/*
* Only error number of vector is available.
* We don't have a mechanism to map multiple
* interrupts into one vector, so even if we
* can try to request less vectors, we don't
* know how to associate interrupt events to
* vectors. Linux doesn't dupicate vectors
* in the MSIX table for this case.
*/
printk(KERN_WARNING "bfad%d: "
"pci_enable_msix failed (%d),"
" use line based.\n", bfad->inst_no, error);
goto line_based;
}
/* Save the vectors */
for (i = 0; i < bfad->nvec; i++) {
bfa_trc(bfad, msix_entries[i].vector);
bfad->msix_tab[i].msix.vector = msix_entries[i].vector;
}
bfa_msix_init(&bfad->bfa, bfad->nvec);
bfad->bfad_flags |= BFAD_MSIX_ON;
return error;
}
line_based:
error = 0;
if (request_irq
(bfad->pcidev->irq, (irq_handler_t) bfad_intx, BFAD_IRQ_FLAGS,
BFAD_DRIVER_NAME, bfad) != 0) {
/* Enable interrupt handler failed */
return 1;
}
return error;
}
void
bfad_remove_intr(struct bfad_s *bfad)
{
int i;
if (bfad->bfad_flags & BFAD_MSIX_ON) {
for (i = 0; i < bfad->nvec; i++)
free_irq(bfad->msix_tab[i].msix.vector,
&bfad->msix_tab[i]);
pci_disable_msix(bfad->pcidev);
bfad->bfad_flags &= ~BFAD_MSIX_ON;
} else {
free_irq(bfad->pcidev->irq, bfad);
}
}