Adding VIOC device driver. Interrupt handler. Signed-off-by: Misha Tomushev <[EMAIL PROTECTED]>
diff -uprN linux-2.6.17/drivers/net/vioc/vioc_irq.c linux-2.6.17.vioc/drivers/net/vioc/vioc_irq.c --- linux-2.6.17/drivers/net/vioc/vioc_irq.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.17.vioc/drivers/net/vioc/vioc_irq.c 2006-10-04 10:37:56.000000000 -0700 @@ -0,0 +1,538 @@ +/* + * Fabric7 Systems Virtual IO Controller Driver + * Copyright (C) 2003-2005 Fabric7 Systems. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * http://www.fabric7.com/ + * + * Maintainers: + * [EMAIL PROTECTED] + * + * + */ +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/compiler.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/if_vlan.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/pci.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/byteorder.h> +#include <asm/uaccess.h> +#include <asm/apic.h> + +#include "f7/vnic_hw_registers.h" +#include "f7/vnic_defs.h" +#include "vioc_vnic.h" + +#define VIOC_INTERRUPTS_CNT 19 /* 16 Rx + 1 Tx + 1 BMC + 1 Error */ +#define VIOC_INTERRUPTS_CNT_PIN_IRQ 4 /* 2 Rx + 1 Tx + 1 BMC */ + +#define VIOC_SLVAR(x) x spinlock_t vioc_driver_lock = SPIN_LOCK_UNLOCKED +#define VIOC_CLI spin_lock_irq(&vioc_driver_lock) +#define VIOC_STI spin_unlock_irq(&vioc_driver_lock) +#define IRQRETURN return IRQ_HANDLED +#define TX_IRQ_IDX 16 +#define BMC_IRQ_IDX 17 +#define ERR_IRQ_IDX 18 +#define HANDLER_TASKLET 1 +#define HANDLER_DIRECT 2 +#define HANDLER_TASKQ 3 +#define VIOC_RX0_PCI_FUNC 0 +#define VIOC_TX_PCI_FUNC 1 +#define VIOC_BMC_PCI_FUNC 2 +#define VIOC_RX1_PCI_FUNC 3 +#define VIOC_IRQ_NONE (u16) -1 +#define VIOC_ID_NONE -1 +#define VIOC_IVEC_NONE -1 +#define VIOC_INTR_NONE -1 + + +struct vioc_msix_entry { + u16 vector; + u16 entry; +}; + +struct vioc_intreq { + char name[VIOC_NAME_LEN]; + void (*intrFuncp) (void *); + void *intrFuncparm; + irqreturn_t(*hthandler) (int, void *, struct pt_regs *); + unsigned int irq; + unsigned int vec; + unsigned int intr_base; + unsigned int intr_offset; + unsigned int timeout_value; + unsigned int pkt_counter; + unsigned int rxc_mask; + struct work_struct taskq; + struct tasklet_struct tasklet; +}; + +struct viocdev_intreq { + int vioc_id; + struct pci_dev *pci_dev; + void *vioc_virt; + unsigned long long vioc_phy; + void *ioapic_virt; + unsigned long long ioapic_phy; + struct vioc_intreq intreq[VIOC_INTERRUPTS_CNT]; + struct vioc_msix_entry irqs[VIOC_INTERRUPTS_CNT]; +}; + +/* GLOBAL VIOC Interrupt table/structure */ +struct viocdev_intreq vioc_interrupts[VIOC_MAX_VIOCS]; + +VIOC_SLVAR(); + +static irqreturn_t taskq_handler(int i, void *p, struct pt_regs *r) +{ + int intr_id = VIOC_IRQ_PARAM_INTR_ID(p); + int vioc_id = VIOC_IRQ_PARAM_VIOC_ID(p); + + schedule_work(&vioc_interrupts[vioc_id].intreq[intr_id].taskq); + IRQRETURN; +} + +static irqreturn_t tasklet_handler(int i, void *p, struct pt_regs *r) +{ + int intr_id = VIOC_IRQ_PARAM_INTR_ID(p); + int vioc_id = VIOC_IRQ_PARAM_VIOC_ID(p); + + tasklet_schedule(&vioc_interrupts[vioc_id].intreq[intr_id].tasklet); + IRQRETURN; +} + +static irqreturn_t direct_handler(int i, void *p, struct pt_regs *r) +{ + int intr_id = VIOC_IRQ_PARAM_INTR_ID(p); + int vioc_id = VIOC_IRQ_PARAM_VIOC_ID(p); + + vioc_interrupts[vioc_id].intreq[intr_id]. + intrFuncp(vioc_interrupts[vioc_id].intreq[intr_id].intrFuncparm); + IRQRETURN; +} + +static int vioc_enable_msix(u32 viocdev_idx) +{ + struct vioc_device *viocdev = vioc_viocdev(viocdev_idx); + int ret; + +#if defined(CONFIG_MSIX_MOD) + ret = pci_enable_msix(viocdev->pdev, + (struct msix_entry *) + &vioc_interrupts[viocdev_idx].irqs, + VIOC_INTERRUPTS_CNT); + if (ret == 0) { + dev_err(&viocdev->pdev->dev, "MSI-X OK\n"); + return VIOC_INTERRUPTS_CNT; + } else { + dev_err(&viocdev->pdev->dev, + "Enabling MSIX failed (%d) VIOC %d, use PIN-IRQ\n", ret, + viocdev_idx); + pci_disable_msix(viocdev->pdev); + ret = pci_request_regions(viocdev->pdev, VIOC_DRV_MODULE_NAME); + if (ret != 0) { + dev_err(&viocdev->pdev->dev, "vioc%d: Cannot obtain PCI resources\n", + viocdev_idx); + return 0; + } + return VIOC_INTERRUPTS_CNT_PIN_IRQ; + } +#else + ret = pci_request_regions(viocdev->pdev, VIOC_DRV_MODULE_NAME); + if (ret != 0) { + dev_err(&viocdev->pdev->dev, "vioc%d: Cannot obtain PCI resources\n", + viocdev_idx); + return 0; + } + return VIOC_INTERRUPTS_CNT_PIN_IRQ; +#endif +} + +static void vioc_irq_remove(int viocdev_idx, int irq) +{ + int intr_id; + + if (viocdev_idx >= VIOC_MAX_VIOCS) + return; + + for (intr_id = 0; intr_id < VIOC_INTERRUPTS_CNT; intr_id++) { + if (vioc_interrupts[viocdev_idx].intreq[intr_id].irq == irq) { + if (vioc_interrupts[viocdev_idx].intreq[intr_id].irq != + VIOC_IRQ_NONE) { + free_irq(vioc_interrupts[viocdev_idx]. + intreq[intr_id].irq, + vioc_interrupts[viocdev_idx]. + intreq[intr_id].intrFuncparm); + } + vioc_interrupts[viocdev_idx].intreq[intr_id].irq = + VIOC_IRQ_NONE; + vioc_interrupts[viocdev_idx].irqs[intr_id].vector = + VIOC_IRQ_NONE; + } + } +} + +void vioc_free_irqs(u32 viocdev_idx) +{ + u32 i; + + for (i = 0; i < VIOC_INTERRUPTS_CNT; i++) { + if (vioc_interrupts[viocdev_idx].irqs[i].vector != + VIOC_IRQ_NONE) { + vioc_irq_remove(viocdev_idx, + vioc_interrupts[viocdev_idx].irqs[i]. + vector); + } + } +} + +void vioc_irq_exit(void) +{ + int vioc_id; + + for (vioc_id = 0; vioc_id < VIOC_MAX_VIOCS; vioc_id++) + vioc_free_irqs(vioc_id); +} + +int vioc_irq_init(void) +{ + int intr_id, vioc_id; + + /* Zero out whole vioc_interrupts array */ + memset(&vioc_interrupts, 0, sizeof(vioc_interrupts)); + + for (vioc_id = 0; vioc_id < VIOC_MAX_VIOCS; vioc_id++) { + vioc_interrupts[vioc_id].vioc_id = VIOC_ID_NONE; + for (intr_id = 0; intr_id < VIOC_INTERRUPTS_CNT; intr_id++) { + vioc_interrupts[vioc_id].intreq[intr_id].irq = + VIOC_IRQ_NONE; + vioc_interrupts[vioc_id].irqs[intr_id].vector = + VIOC_IRQ_NONE; + vioc_interrupts[vioc_id].irqs[intr_id].entry = intr_id; + } + } + return 0; +} + +int get_pci_pin_irq(struct pci_dev *dev_in, int func) +{ + struct pci_dev *dev = NULL; + unsigned int slot, fn, devfn; + + slot = PCI_SLOT(dev_in->devfn); + fn = PCI_FUNC(dev_in->devfn); + devfn = dev_in->devfn; + + devfn = PCI_DEVFN(slot, func); + /* Find pci_dev structure of the requested function */ + dev = pci_find_slot(dev_in->bus->number, devfn); + if (dev) { + return dev->irq; + } else { + return VIOC_IRQ_NONE; + } +} + +static int vioc_irq_install(struct pci_dev *vioc_pci_dev, + void (*routine) (void *), + int vioc_id, + int intr_id, + int irq, + int intr_handler_type, + int intr_param, char *intr_name) +{ + int ret; + void *intr_func_param = (void *) + VIOC_IRQ_PARAM_SET(vioc_id, intr_id, intr_param); + + /* + * Find IRQ of requested interrupt: For now, search the + * vioc_interrupts[] table that was initialized with proper + * IRQs during viocdev_htirq_init() In the final product, the + * IRQ will be obtained from the pci_dev structure of the VIOC + * device. + */ + + if (vioc_id >= VIOC_MAX_VIOCS) + return -ENODEV; + + if (intr_id >= VIOC_INTERRUPTS_CNT) { + dev_err(&vioc_pci_dev->dev, + "%s: INTR ID (%d) out of range for Interrupt IRQ %d, name %s\n", + __FUNCTION__, intr_id, irq, intr_name); + return -EINVAL; + } + vioc_interrupts[vioc_id].vioc_id = vioc_id; + + if (vioc_interrupts[vioc_id].intreq[intr_id].irq != VIOC_IRQ_NONE) { + free_irq(vioc_interrupts[vioc_id]. + intreq[intr_id].irq, + vioc_interrupts[vioc_id].intreq[intr_id].intrFuncparm); + vioc_interrupts[vioc_id].intreq[intr_id].irq = VIOC_IRQ_NONE; + } + + vioc_set_intr_func_param(vioc_id, intr_id, intr_param); + INIT_WORK(&vioc_interrupts[vioc_id].intreq[intr_id].taskq, routine, + intr_func_param); + + vioc_interrupts[vioc_id].intreq[intr_id].irq = irq; + vioc_interrupts[vioc_id].intreq[intr_id].name[VIOC_NAME_LEN - 1] = '\0'; + + vioc_interrupts[vioc_id].intreq[intr_id].timeout_value = 100; + vioc_interrupts[vioc_id].intreq[intr_id].pkt_counter = 1; + vioc_interrupts[vioc_id].intreq[intr_id].vec = VIOC_IVEC_NONE; + vioc_interrupts[vioc_id].intreq[intr_id].intrFuncp = routine; + + /* Init work_struct used to schedule task from INTR handler code */ + + /* Init tasklet_struct used to run "tasklet" from INTR handler code */ + vioc_interrupts[vioc_id].intreq[intr_id].tasklet.next = NULL; + vioc_interrupts[vioc_id].intreq[intr_id].tasklet.state = 0; + atomic_set(&vioc_interrupts[vioc_id].intreq[intr_id].tasklet.count, 0); + vioc_interrupts[vioc_id].intreq[intr_id].tasklet.func = + (void (*)(unsigned long))routine; + + snprintf(&vioc_interrupts[vioc_id].intreq[intr_id].name[0], + VIOC_NAME_LEN, "%s_%02x", intr_name, vioc_id); + vioc_interrupts[vioc_id].intreq[intr_id].name[VIOC_NAME_LEN - 1] = '\0'; + + if (intr_handler_type == HANDLER_TASKLET) + vioc_interrupts[vioc_id].intreq[intr_id].hthandler = + tasklet_handler; + else if (intr_handler_type == HANDLER_TASKQ) + vioc_interrupts[vioc_id].intreq[intr_id].hthandler = + taskq_handler; + else if (intr_handler_type == HANDLER_DIRECT) + vioc_interrupts[vioc_id].intreq[intr_id].hthandler = + direct_handler; + else { + dev_err(&vioc_pci_dev->dev, + "%s: Interrupt handler type for name %s unknown\n", + __FUNCTION__, intr_name); + return -EINVAL; + } + + ret = request_irq(vioc_interrupts[vioc_id].intreq[intr_id].irq, + vioc_interrupts[vioc_id].intreq[intr_id].hthandler, + SA_INTERRUPT, + vioc_interrupts[vioc_id].intreq[intr_id].name, + vioc_interrupts[vioc_id].intreq[intr_id]. + intrFuncparm); + if (ret) { + dev_err(&vioc_pci_dev->dev, "%s: request_irq() -> %d\n", __FUNCTION__, ret); + return ret; + } + + return 0; +} + +int vioc_set_intr_func_param(int viocdev_idx, int intr_idx, int intr_param) +{ + struct vioc_device *viocdev = vioc_viocdev(viocdev_idx); + + void *intr_func_param = (void *) + VIOC_IRQ_PARAM_SET(viocdev_idx, intr_idx, intr_param); + + if (viocdev_idx >= VIOC_MAX_VIOCS) { + dev_err(&viocdev->pdev->dev, "%s: VIOC ID (%d) is out of range\n", + __FUNCTION__, viocdev_idx); + return -ENODEV; + } + + if (intr_idx >= VIOC_INTERRUPTS_CNT) { + dev_err(&viocdev->pdev->dev, "%s: INTR ID (%d) is out of range\n", + __FUNCTION__, intr_idx); + return -EINVAL; + } + + vioc_interrupts[viocdev_idx].intreq[intr_idx].intrFuncparm = + intr_func_param; + vioc_interrupts[viocdev_idx].intreq[intr_idx].taskq.data = + intr_func_param; + vioc_interrupts[viocdev_idx].intreq[intr_idx].tasklet.data = + (unsigned long)intr_func_param; + return 0; +} + +/* + * Function returns number of Rx IRQs. + * When PIN IRQ is used, 2 Rx IRQs are supported. + * With MSI-X 16 Rx IRQs. + */ + +int vioc_request_irqs(u32 viocdev_idx) +{ + struct vioc_device *viocdev = vioc_viocdev(viocdev_idx); + int ret; + int total_num_irqs; + int intr_idx; + char name_buf[64]; + + /* Check for MSI-X, install either 2 or 16 Rx IRQs */ + total_num_irqs = vioc_enable_msix(viocdev_idx); + viocdev->num_irqs = total_num_irqs; + + switch (total_num_irqs) { + + case VIOC_INTERRUPTS_CNT_PIN_IRQ: + vioc_interrupts[viocdev_idx].irqs[2].vector = + get_pci_pin_irq(viocdev->pdev, VIOC_TX_PCI_FUNC); + vioc_interrupts[viocdev_idx].irqs[3].vector = + get_pci_pin_irq(viocdev->pdev, VIOC_BMC_PCI_FUNC); + + intr_idx = 0; + vioc_interrupts[viocdev_idx].irqs[intr_idx].vector = + get_pci_pin_irq(viocdev->pdev, VIOC_RX0_PCI_FUNC); + sprintf(name_buf, "rx%02d_intr", intr_idx); + ret = vioc_irq_install(viocdev->pdev, + vioc_rxc_interrupt, + viocdev_idx, + intr_idx, + vioc_interrupts[viocdev_idx]. + irqs[intr_idx].vector, HANDLER_DIRECT, + intr_idx, name_buf); + if (ret) { + dev_err(&viocdev->pdev->dev, "vioc%d: RX IRQ %02d not installed\n", + viocdev_idx, intr_idx); + return 0; + } + + intr_idx = 1; + vioc_interrupts[viocdev_idx].irqs[intr_idx].vector = + get_pci_pin_irq(viocdev->pdev, VIOC_RX1_PCI_FUNC); + sprintf(name_buf, "rx%02d_intr", intr_idx); + ret = vioc_irq_install(viocdev->pdev, + vioc_rxc_interrupt, + viocdev_idx, + intr_idx, + vioc_interrupts[viocdev_idx]. + irqs[intr_idx].vector, HANDLER_DIRECT, + intr_idx, name_buf); + if (ret) { + dev_err(&viocdev->pdev->dev, "vioc%d: RX IRQ %02d not installed\n", + viocdev_idx, intr_idx); + return 0; + } + + intr_idx = TX_IRQ_IDX; + vioc_interrupts[viocdev_idx].irqs[intr_idx].vector = + get_pci_pin_irq(viocdev->pdev, VIOC_TX_PCI_FUNC); + ret = vioc_irq_install(viocdev->pdev, + vioc_tx_interrupt, + viocdev_idx, + intr_idx, + vioc_interrupts[viocdev_idx]. + irqs[intr_idx].vector, HANDLER_TASKLET, + intr_idx, "tx_intr"); + if (ret) { + dev_err(&viocdev->pdev->dev, "vioc%d: TX IRQ not installed\n", + viocdev_idx); + return 0; + } + + intr_idx = BMC_IRQ_IDX; + vioc_interrupts[viocdev_idx].irqs[intr_idx].vector = + get_pci_pin_irq(viocdev->pdev, VIOC_BMC_PCI_FUNC); + ret = vioc_irq_install(viocdev->pdev, + vioc_bmc_interrupt, + viocdev_idx, + intr_idx, + vioc_interrupts[viocdev_idx]. + irqs[intr_idx].vector, HANDLER_TASKQ, + intr_idx, "bmc_intr"); + if (ret) { + dev_err(&viocdev->pdev->dev, "vioc%d: BMC IRQ not installed\n", + viocdev_idx); + return 0; + } + + return 2; + + case VIOC_INTERRUPTS_CNT: + + for (intr_idx = 0; intr_idx < 16; intr_idx++) { + sprintf(name_buf, "rx%02d_intr", intr_idx); + ret = vioc_irq_install(viocdev->pdev, + vioc_rxc_interrupt, + viocdev_idx, + intr_idx, + vioc_interrupts[viocdev_idx]. + irqs[intr_idx].vector, + HANDLER_DIRECT, intr_idx, + name_buf); + if (ret) { + dev_err(&viocdev->pdev->dev, + "vioc%d: RX IRQ %02d not installed\n", + viocdev_idx, intr_idx); + return 0; + } + } + + intr_idx = TX_IRQ_IDX; + ret = vioc_irq_install(viocdev->pdev, + vioc_tx_interrupt, + viocdev_idx, + intr_idx, + vioc_interrupts[viocdev_idx]. + irqs[intr_idx].vector, HANDLER_TASKLET, + intr_idx, "tx_intr"); + if (ret) { + dev_err(&viocdev->pdev->dev, "vioc%d: TX IRQ not installed\n", + viocdev_idx); + return 0; + } + + intr_idx = BMC_IRQ_IDX; + ret = vioc_irq_install(viocdev->pdev, + vioc_bmc_interrupt, + viocdev_idx, + intr_idx, + vioc_interrupts[viocdev_idx]. + irqs[intr_idx].vector, HANDLER_TASKQ, + intr_idx, "bmc_intr"); + if (ret) { + dev_err(&viocdev->pdev->dev, "vioc%d: BMC IRQ not installed\n", + viocdev_idx); + return 0; + } + + return 16; + + default: + + return 0; + } + + return 0; +} + -- Misha Tomushev [EMAIL PROTECTED] - To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html