2013-09-06 07:41:41 +08:00
|
|
|
/*
|
|
|
|
* Intel MIC Platform Software Stack (MPSS)
|
|
|
|
*
|
|
|
|
* Copyright(c) 2013 Intel Corporation.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License, 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.
|
|
|
|
*
|
|
|
|
* The full GNU General Public License is included in this distribution in
|
|
|
|
* the file called "COPYING".
|
|
|
|
*
|
|
|
|
* Intel MIC Host driver.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
|
2013-09-28 00:49:42 +08:00
|
|
|
#include "../common/mic_dev.h"
|
2013-09-06 07:41:41 +08:00
|
|
|
#include "mic_device.h"
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
static irqreturn_t mic_thread_fn(int irq, void *dev)
|
2013-09-06 07:41:41 +08:00
|
|
|
{
|
2014-07-12 05:04:22 +08:00
|
|
|
struct mic_device *mdev = dev;
|
|
|
|
struct mic_intr_info *intr_info = mdev->intr_info;
|
|
|
|
struct mic_irq_info *irq_info = &mdev->irq_info;
|
2013-09-06 07:41:41 +08:00
|
|
|
struct mic_intr_cb *intr_cb;
|
|
|
|
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
2014-07-12 05:04:22 +08:00
|
|
|
struct pci_dev, dev);
|
|
|
|
int i;
|
2013-09-06 07:41:41 +08:00
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_lock(&irq_info->mic_thread_lock);
|
|
|
|
for (i = intr_info->intr_start_idx[MIC_INTR_DB];
|
|
|
|
i < intr_info->intr_len[MIC_INTR_DB]; i++)
|
|
|
|
if (test_and_clear_bit(i, &irq_info->mask)) {
|
|
|
|
list_for_each_entry(intr_cb, &irq_info->cb_list[i],
|
|
|
|
list)
|
|
|
|
if (intr_cb->thread_fn)
|
|
|
|
intr_cb->thread_fn(pdev->irq,
|
|
|
|
intr_cb->data);
|
|
|
|
}
|
|
|
|
spin_unlock(&irq_info->mic_thread_lock);
|
|
|
|
return IRQ_HANDLED;
|
2013-09-06 07:41:41 +08:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* mic_interrupt - Generic interrupt handler for
|
|
|
|
* MSI and INTx based interrupts.
|
|
|
|
*/
|
|
|
|
static irqreturn_t mic_interrupt(int irq, void *dev)
|
|
|
|
{
|
|
|
|
struct mic_device *mdev = dev;
|
2014-07-12 05:04:22 +08:00
|
|
|
struct mic_intr_info *intr_info = mdev->intr_info;
|
|
|
|
struct mic_irq_info *irq_info = &mdev->irq_info;
|
|
|
|
struct mic_intr_cb *intr_cb;
|
|
|
|
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
|
|
|
struct pci_dev, dev);
|
2013-09-06 07:41:41 +08:00
|
|
|
u32 mask;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mask = mdev->ops->ack_interrupt(mdev);
|
|
|
|
if (!mask)
|
|
|
|
return IRQ_NONE;
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_lock(&irq_info->mic_intr_lock);
|
|
|
|
for (i = intr_info->intr_start_idx[MIC_INTR_DB];
|
|
|
|
i < intr_info->intr_len[MIC_INTR_DB]; i++)
|
|
|
|
if (mask & BIT(i)) {
|
|
|
|
list_for_each_entry(intr_cb, &irq_info->cb_list[i],
|
|
|
|
list)
|
|
|
|
if (intr_cb->handler)
|
|
|
|
intr_cb->handler(pdev->irq,
|
|
|
|
intr_cb->data);
|
|
|
|
set_bit(i, &irq_info->mask);
|
|
|
|
}
|
|
|
|
spin_unlock(&irq_info->mic_intr_lock);
|
|
|
|
return IRQ_WAKE_THREAD;
|
2013-09-06 07:41:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Return the interrupt offset from the index. Index is 0 based. */
|
|
|
|
static u16 mic_map_src_to_offset(struct mic_device *mdev,
|
2013-09-28 00:49:53 +08:00
|
|
|
int intr_src, enum mic_intr_type type)
|
|
|
|
{
|
2013-09-06 07:41:41 +08:00
|
|
|
if (type >= MIC_NUM_INTR_TYPES)
|
|
|
|
return MIC_NUM_OFFSETS;
|
|
|
|
if (intr_src >= mdev->intr_info->intr_len[type])
|
|
|
|
return MIC_NUM_OFFSETS;
|
|
|
|
|
|
|
|
return mdev->intr_info->intr_start_idx[type] + intr_src;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return next available msix_entry. */
|
|
|
|
static struct msix_entry *mic_get_available_vector(struct mic_device *mdev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct mic_irq_info *info = &mdev->irq_info;
|
|
|
|
|
|
|
|
for (i = 0; i < info->num_vectors; i++)
|
|
|
|
if (!info->mic_msi_map[i])
|
|
|
|
return &info->msix_entries[i];
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_register_intr_callback - Register a callback handler for the
|
|
|
|
* given source id.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to the mic_device instance
|
|
|
|
* @idx: The source id to be registered.
|
2014-07-12 05:04:22 +08:00
|
|
|
* @handler: The function to be called when the source id receives
|
2013-09-06 07:41:41 +08:00
|
|
|
* the interrupt.
|
2014-07-12 05:04:22 +08:00
|
|
|
* @thread_fn: thread fn. corresponding to the handler
|
2013-09-06 07:41:41 +08:00
|
|
|
* @data: Private data of the requester.
|
|
|
|
* Return the callback structure that was registered or an
|
|
|
|
* appropriate error on failure.
|
|
|
|
*/
|
|
|
|
static struct mic_intr_cb *mic_register_intr_callback(struct mic_device *mdev,
|
2014-07-12 05:04:22 +08:00
|
|
|
u8 idx, irq_handler_t handler, irq_handler_t thread_fn,
|
2013-09-06 07:41:41 +08:00
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct mic_intr_cb *intr_cb;
|
|
|
|
unsigned long flags;
|
|
|
|
int rc;
|
2013-09-28 00:49:53 +08:00
|
|
|
intr_cb = kmalloc(sizeof(*intr_cb), GFP_KERNEL);
|
2013-09-06 07:41:41 +08:00
|
|
|
|
|
|
|
if (!intr_cb)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
intr_cb->handler = handler;
|
|
|
|
intr_cb->thread_fn = thread_fn;
|
2013-09-06 07:41:41 +08:00
|
|
|
intr_cb->data = data;
|
|
|
|
intr_cb->cb_id = ida_simple_get(&mdev->irq_info.cb_ida,
|
|
|
|
0, 0, GFP_KERNEL);
|
|
|
|
if (intr_cb->cb_id < 0) {
|
|
|
|
rc = intr_cb->cb_id;
|
|
|
|
goto ida_fail;
|
|
|
|
}
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_lock(&mdev->irq_info.mic_thread_lock);
|
2013-09-06 07:41:41 +08:00
|
|
|
spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags);
|
|
|
|
list_add_tail(&intr_cb->list, &mdev->irq_info.cb_list[idx]);
|
|
|
|
spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags);
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
2013-09-06 07:41:41 +08:00
|
|
|
|
|
|
|
return intr_cb;
|
|
|
|
ida_fail:
|
|
|
|
kfree(intr_cb);
|
|
|
|
return ERR_PTR(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_unregister_intr_callback - Unregister the callback handler
|
|
|
|
* identified by its callback id.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to the mic_device instance
|
|
|
|
* @idx: The callback structure id to be unregistered.
|
|
|
|
* Return the source id that was unregistered or MIC_NUM_OFFSETS if no
|
|
|
|
* such callback handler was found.
|
|
|
|
*/
|
|
|
|
static u8 mic_unregister_intr_callback(struct mic_device *mdev, u32 idx)
|
|
|
|
{
|
|
|
|
struct list_head *pos, *tmp;
|
|
|
|
struct mic_intr_cb *intr_cb;
|
|
|
|
unsigned long flags;
|
|
|
|
int i;
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_lock(&mdev->irq_info.mic_thread_lock);
|
|
|
|
spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags);
|
2013-09-06 07:41:41 +08:00
|
|
|
for (i = 0; i < MIC_NUM_OFFSETS; i++) {
|
|
|
|
list_for_each_safe(pos, tmp, &mdev->irq_info.cb_list[i]) {
|
|
|
|
intr_cb = list_entry(pos, struct mic_intr_cb, list);
|
|
|
|
if (intr_cb->cb_id == idx) {
|
|
|
|
list_del(pos);
|
|
|
|
ida_simple_remove(&mdev->irq_info.cb_ida,
|
2013-09-28 00:49:53 +08:00
|
|
|
intr_cb->cb_id);
|
2013-09-06 07:41:41 +08:00
|
|
|
kfree(intr_cb);
|
|
|
|
spin_unlock_irqrestore(
|
|
|
|
&mdev->irq_info.mic_intr_lock, flags);
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
2013-09-06 07:41:41 +08:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags);
|
|
|
|
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
2013-09-06 07:41:41 +08:00
|
|
|
return MIC_NUM_OFFSETS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_setup_msix - Initializes MSIx interrupts.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
|
|
|
*/
|
|
|
|
static int mic_setup_msix(struct mic_device *mdev, struct pci_dev *pdev)
|
|
|
|
{
|
|
|
|
int rc, i;
|
2013-09-28 00:49:53 +08:00
|
|
|
int entry_size = sizeof(*mdev->irq_info.msix_entries);
|
2013-09-06 07:41:41 +08:00
|
|
|
|
2013-09-28 00:49:53 +08:00
|
|
|
mdev->irq_info.msix_entries = kmalloc_array(MIC_MIN_MSIX,
|
|
|
|
entry_size, GFP_KERNEL);
|
2013-09-06 07:41:41 +08:00
|
|
|
if (!mdev->irq_info.msix_entries) {
|
|
|
|
rc = -ENOMEM;
|
|
|
|
goto err_nomem1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < MIC_MIN_MSIX; i++)
|
|
|
|
mdev->irq_info.msix_entries[i].entry = i;
|
|
|
|
|
2014-02-22 00:22:59 +08:00
|
|
|
rc = pci_enable_msix_exact(pdev, mdev->irq_info.msix_entries,
|
2013-09-06 07:41:41 +08:00
|
|
|
MIC_MIN_MSIX);
|
|
|
|
if (rc) {
|
|
|
|
dev_dbg(&pdev->dev, "Error enabling MSIx. rc = %d\n", rc);
|
|
|
|
goto err_enable_msix;
|
|
|
|
}
|
|
|
|
|
|
|
|
mdev->irq_info.num_vectors = MIC_MIN_MSIX;
|
|
|
|
mdev->irq_info.mic_msi_map = kzalloc((sizeof(u32) *
|
|
|
|
mdev->irq_info.num_vectors), GFP_KERNEL);
|
|
|
|
|
|
|
|
if (!mdev->irq_info.mic_msi_map) {
|
|
|
|
rc = -ENOMEM;
|
|
|
|
goto err_nomem2;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(mdev->sdev->parent,
|
|
|
|
"%d MSIx irqs setup\n", mdev->irq_info.num_vectors);
|
|
|
|
return 0;
|
|
|
|
err_nomem2:
|
|
|
|
pci_disable_msix(pdev);
|
|
|
|
err_enable_msix:
|
|
|
|
kfree(mdev->irq_info.msix_entries);
|
|
|
|
err_nomem1:
|
|
|
|
mdev->irq_info.num_vectors = 0;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_setup_callbacks - Initialize data structures needed
|
|
|
|
* to handle callbacks.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
|
|
|
*/
|
|
|
|
static int mic_setup_callbacks(struct mic_device *mdev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2013-09-28 00:49:53 +08:00
|
|
|
mdev->irq_info.cb_list = kmalloc_array(MIC_NUM_OFFSETS,
|
|
|
|
sizeof(*mdev->irq_info.cb_list),
|
|
|
|
GFP_KERNEL);
|
2013-09-06 07:41:41 +08:00
|
|
|
if (!mdev->irq_info.cb_list)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
for (i = 0; i < MIC_NUM_OFFSETS; i++)
|
|
|
|
INIT_LIST_HEAD(&mdev->irq_info.cb_list[i]);
|
|
|
|
ida_init(&mdev->irq_info.cb_ida);
|
|
|
|
spin_lock_init(&mdev->irq_info.mic_intr_lock);
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_lock_init(&mdev->irq_info.mic_thread_lock);
|
2013-09-06 07:41:41 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_release_callbacks - Uninitialize data structures needed
|
|
|
|
* to handle callbacks.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
|
|
|
*/
|
|
|
|
static void mic_release_callbacks(struct mic_device *mdev)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
struct list_head *pos, *tmp;
|
|
|
|
struct mic_intr_cb *intr_cb;
|
|
|
|
int i;
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_lock(&mdev->irq_info.mic_thread_lock);
|
|
|
|
spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags);
|
2013-09-06 07:41:41 +08:00
|
|
|
for (i = 0; i < MIC_NUM_OFFSETS; i++) {
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
if (list_empty(&mdev->irq_info.cb_list[i]))
|
2013-09-06 07:41:41 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
list_for_each_safe(pos, tmp, &mdev->irq_info.cb_list[i]) {
|
|
|
|
intr_cb = list_entry(pos, struct mic_intr_cb, list);
|
|
|
|
list_del(pos);
|
|
|
|
ida_simple_remove(&mdev->irq_info.cb_ida,
|
2013-09-28 00:49:53 +08:00
|
|
|
intr_cb->cb_id);
|
2013-09-06 07:41:41 +08:00
|
|
|
kfree(intr_cb);
|
|
|
|
}
|
|
|
|
}
|
2014-07-12 05:04:22 +08:00
|
|
|
spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags);
|
|
|
|
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
2013-09-06 07:41:41 +08:00
|
|
|
ida_destroy(&mdev->irq_info.cb_ida);
|
|
|
|
kfree(mdev->irq_info.cb_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_setup_msi - Initializes MSI interrupts.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
|
|
|
* @pdev: PCI device structure
|
|
|
|
*
|
|
|
|
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
|
|
|
*/
|
|
|
|
static int mic_setup_msi(struct mic_device *mdev, struct pci_dev *pdev)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = pci_enable_msi(pdev);
|
|
|
|
if (rc) {
|
|
|
|
dev_dbg(&pdev->dev, "Error enabling MSI. rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
mdev->irq_info.num_vectors = 1;
|
|
|
|
mdev->irq_info.mic_msi_map = kzalloc((sizeof(u32) *
|
|
|
|
mdev->irq_info.num_vectors), GFP_KERNEL);
|
|
|
|
|
|
|
|
if (!mdev->irq_info.mic_msi_map) {
|
|
|
|
rc = -ENOMEM;
|
|
|
|
goto err_nomem1;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = mic_setup_callbacks(mdev);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(&pdev->dev, "Error setting up callbacks\n");
|
|
|
|
goto err_nomem2;
|
|
|
|
}
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
rc = request_threaded_irq(pdev->irq, mic_interrupt, mic_thread_fn,
|
|
|
|
0, "mic-msi", mdev);
|
2013-09-06 07:41:41 +08:00
|
|
|
if (rc) {
|
|
|
|
dev_err(&pdev->dev, "Error allocating MSI interrupt\n");
|
|
|
|
goto err_irq_req_fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(&pdev->dev, "%d MSI irqs setup\n", mdev->irq_info.num_vectors);
|
|
|
|
return 0;
|
|
|
|
err_irq_req_fail:
|
|
|
|
mic_release_callbacks(mdev);
|
|
|
|
err_nomem2:
|
|
|
|
kfree(mdev->irq_info.mic_msi_map);
|
|
|
|
err_nomem1:
|
|
|
|
pci_disable_msi(pdev);
|
|
|
|
mdev->irq_info.num_vectors = 0;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_setup_intx - Initializes legacy interrupts.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
|
|
|
* @pdev: PCI device structure
|
|
|
|
*
|
|
|
|
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
|
|
|
*/
|
|
|
|
static int mic_setup_intx(struct mic_device *mdev, struct pci_dev *pdev)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* Enable intx */
|
|
|
|
pci_intx(pdev, 1);
|
|
|
|
rc = mic_setup_callbacks(mdev);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(&pdev->dev, "Error setting up callbacks\n");
|
|
|
|
goto err_nomem;
|
|
|
|
}
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
rc = request_threaded_irq(pdev->irq, mic_interrupt, mic_thread_fn,
|
|
|
|
IRQF_SHARED, "mic-intx", mdev);
|
2013-09-06 07:41:41 +08:00
|
|
|
if (rc)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
dev_dbg(&pdev->dev, "intx irq setup\n");
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
mic_release_callbacks(mdev);
|
|
|
|
err_nomem:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_next_db - Retrieve the next doorbell interrupt source id.
|
|
|
|
* The id is picked sequentially from the available pool of
|
|
|
|
* doorlbell ids.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to the mic_device instance.
|
|
|
|
*
|
|
|
|
* Returns the next doorbell interrupt source.
|
|
|
|
*/
|
|
|
|
int mic_next_db(struct mic_device *mdev)
|
|
|
|
{
|
|
|
|
int next_db;
|
|
|
|
|
|
|
|
next_db = mdev->irq_info.next_avail_src %
|
|
|
|
mdev->intr_info->intr_len[MIC_INTR_DB];
|
|
|
|
mdev->irq_info.next_avail_src++;
|
|
|
|
return next_db;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define COOKIE_ID_SHIFT 16
|
|
|
|
#define GET_ENTRY(cookie) ((cookie) & 0xFFFF)
|
|
|
|
#define GET_OFFSET(cookie) ((cookie) >> COOKIE_ID_SHIFT)
|
|
|
|
#define MK_COOKIE(x, y) ((x) | (y) << COOKIE_ID_SHIFT)
|
|
|
|
|
|
|
|
/**
|
2014-07-12 05:04:22 +08:00
|
|
|
* mic_request_threaded_irq - request an irq. mic_mutex needs
|
2013-09-06 07:41:41 +08:00
|
|
|
* to be held before calling this function.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
2014-07-12 05:04:22 +08:00
|
|
|
* @handler: The callback function that handles the interrupt.
|
2013-09-06 07:41:41 +08:00
|
|
|
* The function needs to call ack_interrupts
|
|
|
|
* (mdev->ops->ack_interrupt(mdev)) when handling the interrupts.
|
2014-07-12 05:04:22 +08:00
|
|
|
* @thread_fn: thread fn required by request_threaded_irq.
|
2013-09-06 07:41:41 +08:00
|
|
|
* @name: The ASCII name of the callee requesting the irq.
|
|
|
|
* @data: private data that is returned back when calling the
|
|
|
|
* function handler.
|
|
|
|
* @intr_src: The source id of the requester. Its the doorbell id
|
|
|
|
* for Doorbell interrupts and DMA channel id for DMA interrupts.
|
|
|
|
* @type: The type of interrupt. Values defined in mic_intr_type
|
|
|
|
*
|
|
|
|
* returns: The cookie that is transparent to the caller. Passed
|
|
|
|
* back when calling mic_free_irq. An appropriate error code
|
|
|
|
* is returned on failure. Caller needs to use IS_ERR(return_val)
|
|
|
|
* to check for failure and PTR_ERR(return_val) to obtained the
|
|
|
|
* error code.
|
|
|
|
*
|
|
|
|
*/
|
2014-07-12 05:04:22 +08:00
|
|
|
struct mic_irq *
|
|
|
|
mic_request_threaded_irq(struct mic_device *mdev,
|
|
|
|
irq_handler_t handler, irq_handler_t thread_fn,
|
|
|
|
const char *name, void *data, int intr_src,
|
|
|
|
enum mic_intr_type type)
|
2013-09-06 07:41:41 +08:00
|
|
|
{
|
|
|
|
u16 offset;
|
|
|
|
int rc = 0;
|
|
|
|
struct msix_entry *msix = NULL;
|
|
|
|
unsigned long cookie = 0;
|
|
|
|
u16 entry;
|
|
|
|
struct mic_intr_cb *intr_cb;
|
|
|
|
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
|
|
|
struct pci_dev, dev);
|
|
|
|
|
|
|
|
offset = mic_map_src_to_offset(mdev, intr_src, type);
|
|
|
|
if (offset >= MIC_NUM_OFFSETS) {
|
|
|
|
dev_err(mdev->sdev->parent,
|
2013-09-28 00:49:53 +08:00
|
|
|
"Error mapping index %d to a valid source id.\n",
|
|
|
|
intr_src);
|
2013-09-06 07:41:41 +08:00
|
|
|
rc = -EINVAL;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mdev->irq_info.num_vectors > 1) {
|
|
|
|
msix = mic_get_available_vector(mdev);
|
|
|
|
if (!msix) {
|
|
|
|
dev_err(mdev->sdev->parent,
|
2013-09-28 00:49:53 +08:00
|
|
|
"No MSIx vectors available for use.\n");
|
2013-09-06 07:41:41 +08:00
|
|
|
rc = -ENOSPC;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2014-07-12 05:04:22 +08:00
|
|
|
rc = request_threaded_irq(msix->vector, handler, thread_fn,
|
|
|
|
0, name, data);
|
2013-09-06 07:41:41 +08:00
|
|
|
if (rc) {
|
|
|
|
dev_dbg(mdev->sdev->parent,
|
|
|
|
"request irq failed rc = %d\n", rc);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
entry = msix->entry;
|
|
|
|
mdev->irq_info.mic_msi_map[entry] |= BIT(offset);
|
|
|
|
mdev->intr_ops->program_msi_to_src_map(mdev,
|
|
|
|
entry, offset, true);
|
|
|
|
cookie = MK_COOKIE(entry, offset);
|
|
|
|
dev_dbg(mdev->sdev->parent, "irq: %d assigned for src: %d\n",
|
|
|
|
msix->vector, intr_src);
|
|
|
|
} else {
|
2014-07-12 05:04:22 +08:00
|
|
|
intr_cb = mic_register_intr_callback(mdev, offset, handler,
|
|
|
|
thread_fn, data);
|
2013-09-06 07:41:41 +08:00
|
|
|
if (IS_ERR(intr_cb)) {
|
|
|
|
dev_err(mdev->sdev->parent,
|
2013-09-28 00:49:53 +08:00
|
|
|
"No available callback entries for use\n");
|
2013-09-06 07:41:41 +08:00
|
|
|
rc = PTR_ERR(intr_cb);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = 0;
|
|
|
|
if (pci_dev_msi_enabled(pdev)) {
|
|
|
|
mdev->irq_info.mic_msi_map[entry] |= (1 << offset);
|
|
|
|
mdev->intr_ops->program_msi_to_src_map(mdev,
|
|
|
|
entry, offset, true);
|
|
|
|
}
|
|
|
|
cookie = MK_COOKIE(entry, intr_cb->cb_id);
|
|
|
|
dev_dbg(mdev->sdev->parent, "callback %d registered for src: %d\n",
|
|
|
|
intr_cb->cb_id, intr_src);
|
|
|
|
}
|
|
|
|
return (struct mic_irq *)cookie;
|
|
|
|
err:
|
|
|
|
return ERR_PTR(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_free_irq - free irq. mic_mutex
|
|
|
|
* needs to be held before calling this function.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
2014-07-12 05:04:22 +08:00
|
|
|
* @cookie: cookie obtained during a successful call to mic_request_threaded_irq
|
2013-09-06 07:41:41 +08:00
|
|
|
* @data: private data specified by the calling function during the
|
2014-07-12 05:04:22 +08:00
|
|
|
* mic_request_threaded_irq
|
2013-09-06 07:41:41 +08:00
|
|
|
*
|
|
|
|
* returns: none.
|
|
|
|
*/
|
|
|
|
void mic_free_irq(struct mic_device *mdev,
|
|
|
|
struct mic_irq *cookie, void *data)
|
|
|
|
{
|
|
|
|
u32 offset;
|
|
|
|
u32 entry;
|
|
|
|
u8 src_id;
|
|
|
|
unsigned int irq;
|
|
|
|
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
|
|
|
struct pci_dev, dev);
|
|
|
|
|
|
|
|
entry = GET_ENTRY((unsigned long)cookie);
|
|
|
|
offset = GET_OFFSET((unsigned long)cookie);
|
|
|
|
if (mdev->irq_info.num_vectors > 1) {
|
|
|
|
if (entry >= mdev->irq_info.num_vectors) {
|
|
|
|
dev_warn(mdev->sdev->parent,
|
2013-09-28 00:49:53 +08:00
|
|
|
"entry %d should be < num_irq %d\n",
|
2013-09-06 07:41:41 +08:00
|
|
|
entry, mdev->irq_info.num_vectors);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
irq = mdev->irq_info.msix_entries[entry].vector;
|
|
|
|
free_irq(irq, data);
|
|
|
|
mdev->irq_info.mic_msi_map[entry] &= ~(BIT(offset));
|
|
|
|
mdev->intr_ops->program_msi_to_src_map(mdev,
|
|
|
|
entry, offset, false);
|
|
|
|
|
|
|
|
dev_dbg(mdev->sdev->parent, "irq: %d freed\n", irq);
|
|
|
|
} else {
|
|
|
|
irq = pdev->irq;
|
|
|
|
src_id = mic_unregister_intr_callback(mdev, offset);
|
|
|
|
if (src_id >= MIC_NUM_OFFSETS) {
|
|
|
|
dev_warn(mdev->sdev->parent, "Error unregistering callback\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (pci_dev_msi_enabled(pdev)) {
|
|
|
|
mdev->irq_info.mic_msi_map[entry] &= ~(BIT(src_id));
|
|
|
|
mdev->intr_ops->program_msi_to_src_map(mdev,
|
|
|
|
entry, src_id, false);
|
|
|
|
}
|
|
|
|
dev_dbg(mdev->sdev->parent, "callback %d unregistered for src: %d\n",
|
|
|
|
offset, src_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_setup_interrupts - Initializes interrupts.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
|
|
|
* @pdev: PCI device structure
|
|
|
|
*
|
|
|
|
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
|
|
|
*/
|
|
|
|
int mic_setup_interrupts(struct mic_device *mdev, struct pci_dev *pdev)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = mic_setup_msix(mdev, pdev);
|
|
|
|
if (!rc)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
rc = mic_setup_msi(mdev, pdev);
|
|
|
|
if (!rc)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
rc = mic_setup_intx(mdev, pdev);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(mdev->sdev->parent, "no usable interrupts\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
mdev->intr_ops->enable_interrupts(mdev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_free_interrupts - Frees interrupts setup by mic_setup_interrupts
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance
|
|
|
|
* @pdev: PCI device structure
|
|
|
|
*
|
|
|
|
* returns none.
|
|
|
|
*/
|
|
|
|
void mic_free_interrupts(struct mic_device *mdev, struct pci_dev *pdev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mdev->intr_ops->disable_interrupts(mdev);
|
|
|
|
if (mdev->irq_info.num_vectors > 1) {
|
|
|
|
for (i = 0; i < mdev->irq_info.num_vectors; i++) {
|
|
|
|
if (mdev->irq_info.mic_msi_map[i])
|
|
|
|
dev_warn(&pdev->dev, "irq %d may still be in use.\n",
|
2013-09-28 00:49:53 +08:00
|
|
|
mdev->irq_info.msix_entries[i].vector);
|
2013-09-06 07:41:41 +08:00
|
|
|
}
|
|
|
|
kfree(mdev->irq_info.mic_msi_map);
|
|
|
|
kfree(mdev->irq_info.msix_entries);
|
|
|
|
pci_disable_msix(pdev);
|
|
|
|
} else {
|
|
|
|
if (pci_dev_msi_enabled(pdev)) {
|
|
|
|
free_irq(pdev->irq, mdev);
|
|
|
|
kfree(mdev->irq_info.mic_msi_map);
|
|
|
|
pci_disable_msi(pdev);
|
|
|
|
} else {
|
|
|
|
free_irq(pdev->irq, mdev);
|
|
|
|
}
|
|
|
|
mic_release_callbacks(mdev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* mic_intr_restore - Restore MIC interrupt registers.
|
|
|
|
*
|
|
|
|
* @mdev: pointer to mic_device instance.
|
|
|
|
*
|
|
|
|
* Restore the interrupt registers to values previously
|
|
|
|
* stored in the SW data structures. mic_mutex needs to
|
|
|
|
* be held before calling this function.
|
|
|
|
*
|
|
|
|
* returns None.
|
|
|
|
*/
|
|
|
|
void mic_intr_restore(struct mic_device *mdev)
|
|
|
|
{
|
|
|
|
int entry, offset;
|
|
|
|
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
|
|
|
struct pci_dev, dev);
|
|
|
|
|
|
|
|
if (!pci_dev_msi_enabled(pdev))
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (entry = 0; entry < mdev->irq_info.num_vectors; entry++) {
|
|
|
|
for (offset = 0; offset < MIC_NUM_OFFSETS; offset++) {
|
|
|
|
if (mdev->irq_info.mic_msi_map[entry] & BIT(offset))
|
|
|
|
mdev->intr_ops->program_msi_to_src_map(mdev,
|
|
|
|
entry, offset, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|