mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
Summary: Support to hook mods interrupts for tegra mods devices was broken due to gpu-mods changes that went uncaught in sanity. This change modifies the kernel to allow the tegra code paths to work again. This change also adds support to map irq numbers to gpio input pins to expand functionality in the irq framework. Change-Id: Iee65f9bcaf09453c1740cd5cf7f64b76c87e00c3 Signed-off-by: Ellis Roberts <ellisr@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/2259509 Reviewed-by: Automatic_Commit_Validation_User GVS: Gerrit_Virtual_Submit Reviewed-by: Chris Dragan <kdragan@nvidia.com> Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
1579 lines
38 KiB
C
1579 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* mods_irq.c - This file is part of NVIDIA MODS kernel driver.
|
|
*
|
|
* Copyright (c) 2008-2020, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* NVIDIA MODS kernel driver 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.
|
|
*
|
|
* NVIDIA MODS kernel driver 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 NVIDIA MODS kernel driver.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "mods_internal.h"
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pci_regs.h>
|
|
#if defined(MODS_TEGRA) && defined(CONFIG_OF) && defined(CONFIG_OF_IRQ)
|
|
#include <linux/of.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/io.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#endif
|
|
|
|
#define PCI_VENDOR_ID_NVIDIA 0x10de
|
|
#define INDEX_IRQSTAT(irq) (irq / BITS_NUM)
|
|
#define POS_IRQSTAT(irq) (irq & (BITS_NUM - 1))
|
|
|
|
/* MSI */
|
|
#define PCI_MSI_MASK_BIT 16
|
|
#define MSI_CONTROL_REG(base) (base + PCI_MSI_FLAGS)
|
|
#define IS_64BIT_ADDRESS(control) (!!(control & PCI_MSI_FLAGS_64BIT))
|
|
#define MSI_DATA_REG(base, is64bit) \
|
|
((is64bit == 1) ? base + PCI_MSI_DATA_64 : base + PCI_MSI_DATA_32)
|
|
#define TOP_TKE_TKEIE_WDT_MASK(i) (1 << (16 + 4 * (i)))
|
|
#define TOP_TKE_TKEIE(i) (0x100 + 4 * (i))
|
|
|
|
/*********************
|
|
* PRIVATE FUNCTIONS *
|
|
*********************/
|
|
static struct mods_priv mp;
|
|
|
|
struct mutex *mods_get_irq_mutex(void)
|
|
{
|
|
return &mp.mtx;
|
|
}
|
|
|
|
#ifdef CONFIG_PCI
|
|
int mods_enable_device(struct mods_client *client,
|
|
struct pci_dev *dev,
|
|
struct en_dev_entry **dev_entry)
|
|
{
|
|
int err = OK;
|
|
struct en_dev_entry *dpriv = client->enabled_devices;
|
|
|
|
BUG_ON(!mutex_is_locked(&mp.mtx));
|
|
|
|
dpriv = pci_get_drvdata(dev);
|
|
if (unlikely(dpriv)) {
|
|
if (dpriv->client_id == client->client_id) {
|
|
if (dev_entry)
|
|
*dev_entry = dpriv;
|
|
return OK;
|
|
}
|
|
|
|
mods_error_printk("invalid client %u for device %04x:%02x:%02x.%x\n",
|
|
client->client_id,
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn));
|
|
return -EBUSY;
|
|
}
|
|
|
|
dpriv = kzalloc(sizeof(*dpriv), GFP_KERNEL | __GFP_NORETRY);
|
|
if (unlikely(!dpriv))
|
|
return -ENOMEM;
|
|
atomic_inc(&client->num_allocs);
|
|
|
|
err = pci_enable_device(dev);
|
|
if (unlikely(err)) {
|
|
mods_error_printk("failed to enable device %04x:%02x:%02x.%x\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn));
|
|
kfree(dpriv);
|
|
atomic_dec(&client->num_allocs);
|
|
return err;
|
|
}
|
|
|
|
dpriv->client_id = client->client_id;
|
|
dpriv->dev = pci_dev_get(dev);
|
|
dpriv->next = client->enabled_devices;
|
|
client->enabled_devices = dpriv;
|
|
pci_set_drvdata(dev, dpriv);
|
|
|
|
if (dev_entry)
|
|
*dev_entry = dpriv;
|
|
return OK;
|
|
}
|
|
|
|
void mods_disable_device(struct pci_dev *dev)
|
|
{
|
|
struct en_dev_entry *dpriv = pci_get_drvdata(dev);
|
|
|
|
BUG_ON(!mutex_is_locked(&mp.mtx));
|
|
|
|
if (dpriv) {
|
|
pci_set_drvdata(dev, NULL);
|
|
pci_dev_put(dev);
|
|
}
|
|
|
|
pci_disable_device(dev);
|
|
}
|
|
#endif
|
|
|
|
static unsigned int get_cur_time(void)
|
|
{
|
|
/* This is not very precise, sched_clock() would be better */
|
|
return jiffies_to_usecs(jiffies);
|
|
}
|
|
|
|
static inline int mods_check_interrupt(struct dev_irq_map *t)
|
|
{
|
|
int ii = 0;
|
|
int valid = 0;
|
|
|
|
/* For MSI - we always treat it as pending (must rearm later). */
|
|
/* For non-GPU devices - we can't tell. */
|
|
if (t->mask_info_cnt == 0)
|
|
return true;
|
|
|
|
for (ii = 0; ii < t->mask_info_cnt; ii++) {
|
|
if (!t->mask_info[ii].dev_irq_state ||
|
|
!t->mask_info[ii].dev_irq_mask_reg)
|
|
continue;
|
|
|
|
/* GPU device */
|
|
if (t->mask_info[ii].mask_type == MODS_MASK_TYPE_IRQ_DISABLE64)
|
|
valid |= ((*(u64 *)t->mask_info[ii].dev_irq_state &&
|
|
*(u64 *)t->mask_info[ii].dev_irq_mask_reg) != 0);
|
|
else
|
|
valid |= ((*t->mask_info[ii].dev_irq_state &&
|
|
*t->mask_info[ii].dev_irq_mask_reg) != 0);
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
static void mods_disable_interrupts(struct dev_irq_map *t)
|
|
{
|
|
u32 ii = 0;
|
|
|
|
for (ii = 0; ii < t->mask_info_cnt; ii++) {
|
|
if (t->mask_info[ii].dev_irq_disable_reg &&
|
|
t->mask_info[ii].mask_type == MODS_MASK_TYPE_IRQ_DISABLE64) {
|
|
if (t->mask_info[ii].irq_and_mask == 0)
|
|
*(u64 *)t->mask_info[ii].dev_irq_disable_reg =
|
|
t->mask_info[ii].irq_or_mask;
|
|
else
|
|
*(u64 *)t->mask_info[ii].dev_irq_disable_reg =
|
|
(*(u64 *)t->mask_info[ii].dev_irq_mask_reg &
|
|
t->mask_info[ii].irq_and_mask) |
|
|
t->mask_info[ii].irq_or_mask;
|
|
} else if (t->mask_info[ii].dev_irq_disable_reg) {
|
|
if (t->mask_info[ii].irq_and_mask == 0) {
|
|
*t->mask_info[ii].dev_irq_disable_reg =
|
|
t->mask_info[ii].irq_or_mask;
|
|
} else {
|
|
*t->mask_info[ii].dev_irq_disable_reg =
|
|
(*t->mask_info[ii].dev_irq_mask_reg &
|
|
t->mask_info[ii].irq_and_mask) |
|
|
t->mask_info[ii].irq_or_mask;
|
|
}
|
|
}
|
|
}
|
|
if ((ii == 0) && t->type == MODS_IRQ_TYPE_CPU) {
|
|
mods_debug_printk(DEBUG_ISR, "IRQ_DISABLE_NOSYNC ");
|
|
disable_irq_nosync(t->apic_irq);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_PCI
|
|
static const char *mods_irq_type_name(u8 irq_type)
|
|
{
|
|
switch (irq_type) {
|
|
case MODS_IRQ_TYPE_INT:
|
|
return "INTx";
|
|
case MODS_IRQ_TYPE_MSI:
|
|
return "MSI";
|
|
case MODS_IRQ_TYPE_CPU:
|
|
return "CPU";
|
|
case MODS_IRQ_TYPE_MSIX:
|
|
return "MSI-X";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static struct mods_client *client_from_id(u8 client_id)
|
|
{
|
|
return &mp.clients[client_id - 1];
|
|
}
|
|
|
|
static void wake_up_client(struct dev_irq_map *t)
|
|
{
|
|
struct mods_client *client = client_from_id(t->client_id);
|
|
|
|
if (client)
|
|
wake_up_interruptible(&client->interrupt_event);
|
|
}
|
|
|
|
static int rec_irq_done(struct dev_irq_map *t,
|
|
unsigned int irq_time)
|
|
{
|
|
struct irq_q_info *q;
|
|
|
|
/* Get interrupt queue */
|
|
q = &client_from_id(t->client_id)->irq_queue;
|
|
|
|
/* Don't do anything if the IRQ has already been recorded */
|
|
if (q->head != q->tail) {
|
|
unsigned int i;
|
|
|
|
for (i = q->head; i != q->tail; i++) {
|
|
struct irq_q_data *pd
|
|
= q->data+(i & (MODS_MAX_IRQS - 1));
|
|
|
|
if ((pd->irq == t->apic_irq) &&
|
|
(!t->dev || (pd->dev == t->dev)))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Print an error if the queue is full */
|
|
/* This is deadly! */
|
|
if (q->tail - q->head == MODS_MAX_IRQS) {
|
|
mods_error_printk("IRQ queue is full\n");
|
|
return false;
|
|
}
|
|
|
|
/* Record the device which generated the IRQ in the queue */
|
|
q->data[q->tail & (MODS_MAX_IRQS - 1)].dev = t->dev;
|
|
q->data[q->tail & (MODS_MAX_IRQS - 1)].irq = t->apic_irq;
|
|
q->data[q->tail & (MODS_MAX_IRQS - 1)].irq_index = t->entry;
|
|
q->data[q->tail & (MODS_MAX_IRQS - 1)].time = irq_time;
|
|
q->tail++;
|
|
|
|
#ifdef CONFIG_PCI
|
|
if (t->dev) {
|
|
mods_debug_printk(DEBUG_ISR_DETAILED,
|
|
"%04x:%02x:%02x.%x %s IRQ 0x%x time=%uus\n",
|
|
pci_domain_nr(t->dev->bus),
|
|
t->dev->bus->number,
|
|
PCI_SLOT(t->dev->devfn),
|
|
PCI_FUNC(t->dev->devfn),
|
|
mods_irq_type_name(t->type),
|
|
t->apic_irq,
|
|
irq_time);
|
|
} else
|
|
#endif
|
|
mods_debug_printk(DEBUG_ISR_DETAILED,
|
|
"CPU IRQ 0x%x, time=%uus\n",
|
|
t->apic_irq,
|
|
irq_time);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* mods_irq_handle - interrupt function */
|
|
static irqreturn_t mods_irq_handle(int irq, void *data)
|
|
{
|
|
struct dev_irq_map *t = (struct dev_irq_map *)data;
|
|
int serviced = false;
|
|
|
|
if (unlikely(!t))
|
|
mods_error_printk("received irq %d, but no context for it\n",
|
|
irq);
|
|
else if (unlikely(t->apic_irq != irq))
|
|
mods_error_printk("received irq %d which doesn't match registered irq %d\n",
|
|
irq, t->apic_irq);
|
|
else {
|
|
unsigned long flags = 0;
|
|
int recorded = false;
|
|
unsigned int irq_time = get_cur_time();
|
|
struct mods_client *client = client_from_id(t->client_id);
|
|
|
|
spin_lock_irqsave(&client->irq_lock, flags);
|
|
|
|
/* Check if the interrupt is still pending (shared INTA) */
|
|
if (mods_check_interrupt(t)) {
|
|
|
|
/* Disable interrupts on this device to avoid interrupt
|
|
* storm
|
|
*/
|
|
mods_disable_interrupts(t);
|
|
|
|
/* Record IRQ for MODS and wake MODS up */
|
|
recorded = rec_irq_done(t, irq_time);
|
|
|
|
serviced = true;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&client->irq_lock, flags);
|
|
|
|
if (recorded)
|
|
wake_up_client(t);
|
|
}
|
|
|
|
return IRQ_RETVAL(serviced);
|
|
}
|
|
|
|
static int mods_lookup_cpu_irq(u8 client_id, unsigned int irq)
|
|
{
|
|
u8 client_idx;
|
|
int ret = IRQ_NOT_FOUND;
|
|
|
|
LOG_ENT();
|
|
|
|
for (client_idx = 1; client_idx <= MODS_MAX_CLIENTS; client_idx++) {
|
|
struct dev_irq_map *t = NULL;
|
|
struct dev_irq_map *next = NULL;
|
|
|
|
if (!test_bit(client_idx - 1, &mp.client_flags))
|
|
continue;
|
|
|
|
list_for_each_entry_safe(t,
|
|
next,
|
|
&client_from_id(client_idx)->irq_list,
|
|
list) {
|
|
|
|
if (t->apic_irq == irq) {
|
|
if (client_id == 0) {
|
|
ret = IRQ_FOUND;
|
|
} else {
|
|
ret = (client_id == client_idx)
|
|
? IRQ_FOUND : IRQ_NOT_FOUND;
|
|
}
|
|
|
|
/* Break out of the outer loop */
|
|
client_idx = MODS_MAX_CLIENTS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG_EXT();
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PCI
|
|
static int is_nvidia_gpu(struct pci_dev *dev)
|
|
{
|
|
unsigned short class_code, vendor_id;
|
|
|
|
pci_read_config_word(dev, PCI_CLASS_DEVICE, &class_code);
|
|
pci_read_config_word(dev, PCI_VENDOR_ID, &vendor_id);
|
|
if (((class_code == PCI_CLASS_DISPLAY_VGA) ||
|
|
(class_code == PCI_CLASS_DISPLAY_3D)) && (vendor_id == 0x10DE)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCI
|
|
static void setup_mask_info(struct dev_irq_map *newmap,
|
|
struct MODS_REGISTER_IRQ_4 *p,
|
|
struct pci_dev *dev)
|
|
{
|
|
/* account for legacy adapters */
|
|
char *bar = newmap->dev_irq_aperture;
|
|
u32 ii = 0;
|
|
|
|
if ((p->mask_info_cnt == 0) && is_nvidia_gpu(dev)) {
|
|
newmap->mask_info_cnt = 1;
|
|
newmap->mask_info[0].dev_irq_mask_reg = (u32 *)(bar+0x140);
|
|
newmap->mask_info[0].dev_irq_disable_reg = (u32 *)(bar+0x140);
|
|
newmap->mask_info[0].dev_irq_state = (u32 *)(bar+0x100);
|
|
newmap->mask_info[0].irq_and_mask = 0;
|
|
newmap->mask_info[0].irq_or_mask = 0;
|
|
return;
|
|
}
|
|
/* setup for new adapters */
|
|
newmap->mask_info_cnt = p->mask_info_cnt;
|
|
for (ii = 0; ii < p->mask_info_cnt; ii++) {
|
|
newmap->mask_info[ii].dev_irq_state =
|
|
(u32 *)(bar + p->mask_info[ii].irq_pending_offset);
|
|
newmap->mask_info[ii].dev_irq_mask_reg =
|
|
(u32 *)(bar + p->mask_info[ii].irq_enabled_offset);
|
|
newmap->mask_info[ii].dev_irq_disable_reg =
|
|
(u32 *)(bar + p->mask_info[ii].irq_disable_offset);
|
|
newmap->mask_info[ii].irq_and_mask = p->mask_info[ii].and_mask;
|
|
newmap->mask_info[ii].irq_or_mask = p->mask_info[ii].or_mask;
|
|
newmap->mask_info[ii].mask_type = p->mask_info[ii].mask_type;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int add_irq_map(struct mods_client *client,
|
|
struct pci_dev *dev,
|
|
struct MODS_REGISTER_IRQ_4 *p,
|
|
u32 irq,
|
|
u32 entry)
|
|
{
|
|
u32 irq_type = MODS_IRQ_TYPE_FROM_FLAGS(p->irq_flags);
|
|
struct dev_irq_map *newmap = NULL;
|
|
u64 irq_flags = MODS_IRQ_FLAG_FROM_FLAGS(p->irq_flags);
|
|
u64 valid_mask = IRQF_TRIGGER_NONE;
|
|
|
|
LOG_ENT();
|
|
|
|
/* Get the flags based on the interrupt type*/
|
|
switch (irq_type) {
|
|
case MODS_IRQ_TYPE_INT:
|
|
irq_flags = IRQF_SHARED;
|
|
break;
|
|
|
|
case MODS_IRQ_TYPE_CPU:
|
|
valid_mask = IRQF_TRIGGER_RISING |
|
|
IRQF_TRIGGER_FALLING |
|
|
IRQF_SHARED;
|
|
|
|
/* Either use a valid flag bit or no flags */
|
|
if (irq_flags & ~valid_mask) {
|
|
mods_error_printk("invalid Device Interrupt flag %llx\n",
|
|
(long long) irq_flags);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
irq_flags = IRQF_TRIGGER_NONE;
|
|
break;
|
|
}
|
|
|
|
/* Allocate memory for the new entry */
|
|
newmap = kzalloc(sizeof(*newmap), GFP_KERNEL | __GFP_NORETRY);
|
|
if (unlikely(!newmap)) {
|
|
LOG_EXT();
|
|
return -ENOMEM;
|
|
}
|
|
atomic_inc(&client->num_allocs);
|
|
|
|
/* Fill out the new entry */
|
|
newmap->apic_irq = irq;
|
|
newmap->dev = dev;
|
|
newmap->client_id = client->client_id;
|
|
newmap->dev_irq_aperture = 0;
|
|
newmap->mask_info_cnt = 0;
|
|
newmap->type = irq_type;
|
|
newmap->entry = entry;
|
|
|
|
/* Enable IRQ for this device in the kernel */
|
|
if (request_irq(
|
|
irq,
|
|
&mods_irq_handle,
|
|
irq_flags,
|
|
"nvidia mods",
|
|
newmap)) {
|
|
mods_error_printk("unable to enable IRQ 0x%x with flags %llx\n",
|
|
irq,
|
|
(long long) irq_flags);
|
|
kfree(newmap);
|
|
atomic_dec(&client->num_allocs);
|
|
LOG_EXT();
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Add the new entry to the list of all registered interrupts */
|
|
list_add(&newmap->list, &client->irq_list);
|
|
|
|
#ifdef CONFIG_PCI
|
|
/* Map BAR0 to be able to disable interrupts */
|
|
if ((irq_type == MODS_IRQ_TYPE_INT) &&
|
|
(p->aperture_addr != 0) &&
|
|
(p->aperture_size != 0)) {
|
|
char *bar = ioremap_nocache(p->aperture_addr, p->aperture_size);
|
|
|
|
if (!bar) {
|
|
mods_debug_printk(DEBUG_ISR,
|
|
"failed to remap aperture: 0x%llx size=0x%x\n",
|
|
p->aperture_addr, p->aperture_size);
|
|
LOG_EXT();
|
|
return -EPERM;
|
|
}
|
|
|
|
newmap->dev_irq_aperture = bar;
|
|
setup_mask_info(newmap, p, dev);
|
|
}
|
|
|
|
if (dev)
|
|
pci_dev_get(dev);
|
|
#endif
|
|
|
|
/* Print out successful registration string */
|
|
if (irq_type == MODS_IRQ_TYPE_CPU)
|
|
mods_debug_printk(DEBUG_ISR, "registered CPU IRQ 0x%x with flags %llx\n",
|
|
irq,
|
|
(long long) irq_flags);
|
|
#ifdef CONFIG_PCI
|
|
else if ((irq_type == MODS_IRQ_TYPE_INT) ||
|
|
(irq_type == MODS_IRQ_TYPE_MSI) ||
|
|
(irq_type == MODS_IRQ_TYPE_MSIX)) {
|
|
mods_debug_printk(DEBUG_ISR,
|
|
"%04x:%02x:%02x.%x registered %s IRQ 0x%x\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn),
|
|
mods_irq_type_name(irq_type),
|
|
irq);
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_PCI_MSI
|
|
else if (irq_type == MODS_IRQ_TYPE_MSI) {
|
|
u16 control;
|
|
u16 data;
|
|
int cap_pos = pci_find_capability(dev, PCI_CAP_ID_MSI);
|
|
|
|
pci_read_config_word(dev, MSI_CONTROL_REG(cap_pos), &control);
|
|
if (IS_64BIT_ADDRESS(control))
|
|
pci_read_config_word(dev,
|
|
MSI_DATA_REG(cap_pos, 1),
|
|
&data);
|
|
else
|
|
pci_read_config_word(dev,
|
|
MSI_DATA_REG(cap_pos, 0),
|
|
&data);
|
|
mods_debug_printk(DEBUG_ISR,
|
|
"%04x:%02x:%02x.%x registered MSI IRQ 0x%x data:0x%02x\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn),
|
|
irq,
|
|
data);
|
|
} else if (irq_type == MODS_IRQ_TYPE_MSIX) {
|
|
mods_debug_printk(DEBUG_ISR,
|
|
"%04x:%02x:%02x.%x registered MSI-X IRQ 0x%x\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn),
|
|
irq);
|
|
}
|
|
#endif
|
|
|
|
LOG_EXT();
|
|
return OK;
|
|
}
|
|
|
|
static void mods_free_map(struct dev_irq_map *del)
|
|
{
|
|
unsigned long flags = 0;
|
|
struct mods_client *client = client_from_id(del->client_id);
|
|
|
|
LOG_ENT();
|
|
|
|
/* Disable interrupts on the device */
|
|
spin_lock_irqsave(&client->irq_lock, flags);
|
|
mods_disable_interrupts(del);
|
|
spin_unlock_irqrestore(&client->irq_lock, flags);
|
|
|
|
/* Unhook interrupts in the kernel */
|
|
free_irq(del->apic_irq, del);
|
|
|
|
/* Unmap aperture used for masking irqs */
|
|
if (del->dev_irq_aperture)
|
|
iounmap(del->dev_irq_aperture);
|
|
|
|
#ifdef CONFIG_PCI
|
|
pci_dev_put(del->dev);
|
|
#endif
|
|
|
|
/* Free memory */
|
|
kfree(del);
|
|
atomic_dec(&client->num_allocs);
|
|
|
|
LOG_EXT();
|
|
}
|
|
|
|
void mods_init_irq(void)
|
|
{
|
|
LOG_ENT();
|
|
|
|
memset(&mp, 0, sizeof(mp));
|
|
|
|
mutex_init(&mp.mtx);
|
|
|
|
LOG_EXT();
|
|
}
|
|
|
|
void mods_cleanup_irq(void)
|
|
{
|
|
int i;
|
|
|
|
LOG_ENT();
|
|
for (i = 0; i < MODS_MAX_CLIENTS; i++) {
|
|
if (mp.client_flags & (1 << i))
|
|
mods_free_client(i + 1);
|
|
}
|
|
LOG_EXT();
|
|
}
|
|
|
|
int mods_irq_event_check(u8 client_id)
|
|
{
|
|
struct irq_q_info *q = &client_from_id(client_id)->irq_queue;
|
|
unsigned int pos = (1 << (client_id - 1));
|
|
|
|
if (!(mp.client_flags & pos))
|
|
return POLLERR; /* irq has quit */
|
|
|
|
if (q->head != q->tail)
|
|
return POLLIN; /* irq generated */
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct mods_client *mods_alloc_client(void)
|
|
{
|
|
u8 idx;
|
|
u8 max_clients = 1;
|
|
|
|
LOG_ENT();
|
|
|
|
if (mods_get_multi_instance() ||
|
|
(mods_get_access_token() != MODS_ACCESS_TOKEN_NONE))
|
|
max_clients = MODS_MAX_CLIENTS;
|
|
|
|
for (idx = 1; idx <= max_clients; idx++) {
|
|
if (!test_and_set_bit(idx - 1, &mp.client_flags)) {
|
|
struct mods_client *client = client_from_id(idx);
|
|
|
|
mods_debug_printk(DEBUG_IOCTL,
|
|
"open client %u (bit mask 0x%lx)\n",
|
|
(unsigned int)idx,
|
|
mp.client_flags);
|
|
|
|
memset(client, 0, sizeof(*client));
|
|
client->client_id = idx;
|
|
client->access_token = MODS_ACCESS_TOKEN_NONE;
|
|
mutex_init(&client->mtx);
|
|
spin_lock_init(&client->irq_lock);
|
|
init_waitqueue_head(&client->interrupt_event);
|
|
INIT_LIST_HEAD(&client->irq_list);
|
|
INIT_LIST_HEAD(&client->mem_alloc_list);
|
|
INIT_LIST_HEAD(&client->mem_map_list);
|
|
INIT_LIST_HEAD(&client->free_mem_list);
|
|
#if defined(CONFIG_PPC64)
|
|
INIT_LIST_HEAD(&client->ppc_tce_bypass_list);
|
|
INIT_LIST_HEAD(&client->nvlink_sysmem_trained_list);
|
|
#endif
|
|
|
|
LOG_EXT();
|
|
return client;
|
|
}
|
|
}
|
|
|
|
LOG_EXT();
|
|
return NULL;
|
|
}
|
|
|
|
static int mods_free_irqs(u8 client_id, struct pci_dev *dev)
|
|
{
|
|
#ifdef CONFIG_PCI
|
|
struct mods_client *client = client_from_id(client_id);
|
|
struct dev_irq_map *del = NULL;
|
|
struct dev_irq_map *next;
|
|
struct en_dev_entry *dpriv;
|
|
unsigned int irq_type;
|
|
|
|
LOG_ENT();
|
|
|
|
if (unlikely(mutex_lock_interruptible(&mp.mtx))) {
|
|
LOG_EXT();
|
|
return -EINTR;
|
|
}
|
|
|
|
dpriv = pci_get_drvdata(dev);
|
|
|
|
if (!dpriv) {
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return OK;
|
|
}
|
|
|
|
if (dpriv->client_id != client->client_id) {
|
|
mods_error_printk("invalid client %u for device %04x:%02x:%02x.%x\n",
|
|
client->client_id,
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn));
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
|
|
mods_debug_printk(DEBUG_ISR_DETAILED,
|
|
"(dev=%04x:%02x:%02x.%x) irq_flags=0x%x nvecs=%d\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn),
|
|
dpriv->irq_flags,
|
|
dpriv->nvecs);
|
|
|
|
/* Delete device interrupts from the list */
|
|
list_for_each_entry_safe(del, next, &client->irq_list, list) {
|
|
if (dev == del->dev) {
|
|
u8 type = del->type;
|
|
|
|
list_del(&del->list);
|
|
mods_debug_printk(DEBUG_ISR,
|
|
"%04x:%02x:%02x.%x unregistered %s IRQ 0x%x\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn),
|
|
mods_irq_type_name(type),
|
|
del->apic_irq);
|
|
mods_free_map(del);
|
|
|
|
BUG_ON(type !=
|
|
MODS_IRQ_TYPE_FROM_FLAGS(dpriv->irq_flags));
|
|
if (type != MODS_IRQ_TYPE_MSIX)
|
|
break;
|
|
}
|
|
}
|
|
|
|
mods_debug_printk(DEBUG_ISR_DETAILED, "before disable\n");
|
|
#ifdef CONFIG_PCI_MSI
|
|
irq_type = MODS_IRQ_TYPE_FROM_FLAGS(dpriv->irq_flags);
|
|
|
|
if (irq_type == MODS_IRQ_TYPE_MSIX) {
|
|
pci_disable_msix(dev);
|
|
kfree(dpriv->msix_entries);
|
|
if (dpriv->msix_entries)
|
|
atomic_dec(&client->num_allocs);
|
|
dpriv->msix_entries = 0;
|
|
} else if (irq_type == MODS_IRQ_TYPE_MSI) {
|
|
pci_disable_msi(dev);
|
|
}
|
|
#endif
|
|
|
|
dpriv->nvecs = 0;
|
|
mods_debug_printk(DEBUG_ISR_DETAILED, "irqs freed\n");
|
|
#endif
|
|
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return 0;
|
|
}
|
|
|
|
void mods_free_client_interrupts(struct mods_client *client)
|
|
{
|
|
struct en_dev_entry *dpriv = client->enabled_devices;
|
|
|
|
LOG_ENT();
|
|
|
|
/* Release all interrupts */
|
|
while (dpriv) {
|
|
mods_free_irqs(client->client_id, dpriv->dev);
|
|
dpriv = dpriv->next;
|
|
}
|
|
|
|
LOG_EXT();
|
|
}
|
|
|
|
void mods_free_client(u8 client_id)
|
|
{
|
|
struct mods_client *client = client_from_id(client_id);
|
|
|
|
LOG_ENT();
|
|
|
|
memset(client, 0, sizeof(*client));
|
|
|
|
/* Indicate the client_id is free */
|
|
clear_bit(client_id - 1, &mp.client_flags);
|
|
|
|
mods_debug_printk(DEBUG_IOCTL, "closed client %u\n",
|
|
(unsigned int)client_id);
|
|
LOG_EXT();
|
|
}
|
|
|
|
#ifdef CONFIG_PCI
|
|
static int mods_allocate_irqs(struct mods_client *client,
|
|
struct pci_dev *dev,
|
|
u32 nvecs,
|
|
u32 flags)
|
|
{
|
|
struct en_dev_entry *dpriv;
|
|
unsigned int irq_type = MODS_IRQ_TYPE_FROM_FLAGS(flags);
|
|
int err = OK;
|
|
|
|
LOG_ENT();
|
|
|
|
mods_debug_printk(DEBUG_ISR_DETAILED,
|
|
"(dev=%04x:%02x:%02x.%x, flags=0x%x, nvecs=%d)\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn),
|
|
flags,
|
|
nvecs);
|
|
|
|
/* Determine if the device supports requested interrupt type */
|
|
if (irq_type == MODS_IRQ_TYPE_MSI) {
|
|
#ifdef CONFIG_PCI_MSI
|
|
if (pci_find_capability(dev, PCI_CAP_ID_MSI) == 0) {
|
|
mods_error_printk("dev %04x:%02x:%02x.%x does not support MSI\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn));
|
|
LOG_EXT();
|
|
return -ENOENT;
|
|
}
|
|
#else
|
|
mods_error_printk("the kernel does not support MSI!\n");
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
#endif
|
|
} else if (irq_type == MODS_IRQ_TYPE_MSIX) {
|
|
#ifdef CONFIG_PCI_MSI
|
|
if (pci_find_capability(dev, PCI_CAP_ID_MSIX) == 0) {
|
|
mods_error_printk("dev %04x:%02x:%02x.%x does not support MSI-X\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn));
|
|
LOG_EXT();
|
|
return -ENOENT;
|
|
}
|
|
#else
|
|
mods_error_printk("the kernel does not support MSI-X!\n");
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
/* Enable device on the PCI bus */
|
|
err = mods_enable_device(client, dev, &dpriv);
|
|
if (err) {
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
|
|
if (irq_type == MODS_IRQ_TYPE_INT) {
|
|
/* use legacy irq */
|
|
if (nvecs != 1) {
|
|
mods_error_printk("INTA: only 1 INTA vector supported, requested %u!\n",
|
|
nvecs);
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
dpriv->nvecs = 1;
|
|
}
|
|
/* Enable MSI */
|
|
#ifdef CONFIG_PCI_MSI
|
|
else if (irq_type == MODS_IRQ_TYPE_MSI) {
|
|
if (nvecs != 1) {
|
|
mods_error_printk("MSI: only 1 MSI vector supported, requested %u!\n",
|
|
nvecs);
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
err = pci_enable_msi(dev);
|
|
if (err) {
|
|
mods_error_printk("unable to enable MSI on dev %04x:%02x:%02x.%x\n",
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn));
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
dpriv->nvecs = 1;
|
|
} else if (irq_type == MODS_IRQ_TYPE_MSIX) {
|
|
struct msix_entry *entries;
|
|
int i = 0, cnt = 1;
|
|
|
|
entries = kcalloc(nvecs, sizeof(struct msix_entry),
|
|
GFP_KERNEL | __GFP_NORETRY);
|
|
|
|
if (!entries) {
|
|
mods_error_printk("could not allocate %d MSI-X entries!\n",
|
|
nvecs);
|
|
LOG_EXT();
|
|
return -ENOMEM;
|
|
}
|
|
atomic_inc(&client->num_allocs);
|
|
|
|
for (i = 0; i < nvecs; i++)
|
|
entries[i].entry = (uint16_t)i;
|
|
|
|
#ifdef MODS_HAS_MSIX_RANGE
|
|
cnt = pci_enable_msix_range(dev, entries, nvecs, nvecs);
|
|
|
|
if (cnt < 0) {
|
|
/* returns number of interrupts allocated
|
|
* < 0 indicates a failure.
|
|
*/
|
|
mods_error_printk(
|
|
"could not allocate the requested number of MSI-X vectors=%d return=%d!\n",
|
|
nvecs, cnt);
|
|
kfree(entries);
|
|
atomic_dec(&client->num_allocs);
|
|
LOG_EXT();
|
|
return cnt;
|
|
}
|
|
#else
|
|
cnt = pci_enable_msix(dev, entries, nvecs);
|
|
|
|
if (cnt) {
|
|
/* A return of < 0 indicates a failure.
|
|
* A return of > 0 indicates that driver request is
|
|
* exceeding the number of irqs or MSI-X
|
|
* vectors available
|
|
*/
|
|
mods_error_printk(
|
|
"could not allocate the requested number of MSI-X vectors=%d return=%d!\n",
|
|
nvecs, cnt);
|
|
kfree(entries);
|
|
atomic_dec(&client->num_allocs);
|
|
LOG_EXT();
|
|
if (cnt > 0)
|
|
cnt = -ENOSPC;
|
|
return cnt;
|
|
}
|
|
#endif
|
|
|
|
mods_debug_printk(DEBUG_ISR,
|
|
"allocated %d irq's of type %s(%d)\n",
|
|
nvecs, mods_irq_type_name(irq_type), irq_type);
|
|
|
|
for (i = 0; i < nvecs; i++)
|
|
mods_debug_printk(DEBUG_ISR, "vec %d %x\n",
|
|
entries[i].entry, entries[i].vector);
|
|
|
|
dpriv->nvecs = nvecs;
|
|
dpriv->msix_entries = entries;
|
|
}
|
|
#endif
|
|
else {
|
|
mods_error_printk("unsupported irq_type %d dev: %04x:%02x:%02x.%x\n",
|
|
irq_type,
|
|
pci_domain_nr(dev->bus),
|
|
dev->bus->number,
|
|
PCI_SLOT(dev->devfn),
|
|
PCI_FUNC(dev->devfn));
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
|
|
dpriv->client_id = client->client_id;
|
|
dpriv->irq_flags = flags;
|
|
LOG_EXT();
|
|
return OK;
|
|
}
|
|
|
|
static int mods_register_pci_irq(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_4 *p)
|
|
{
|
|
int err = OK;
|
|
unsigned int irq_type = MODS_IRQ_TYPE_FROM_FLAGS(p->irq_flags);
|
|
struct pci_dev *dev;
|
|
struct en_dev_entry *dpriv;
|
|
int i;
|
|
|
|
LOG_ENT();
|
|
|
|
if (unlikely(!p->irq_count)) {
|
|
mods_error_printk("no irq's requested!\n");
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Get the PCI device structure for the specified device from kernel */
|
|
err = mods_find_pci_dev(client, &p->dev, &dev);
|
|
if (unlikely(err)) {
|
|
if (err == -ENODEV)
|
|
mods_error_printk("PCI device %04x:%02x:%02x.%x not found\n",
|
|
p->dev.domain,
|
|
p->dev.bus,
|
|
p->dev.device,
|
|
p->dev.function);
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
|
|
if (unlikely(mutex_lock_interruptible(&mp.mtx))) {
|
|
pci_dev_put(dev);
|
|
LOG_EXT();
|
|
return -EINTR;
|
|
}
|
|
|
|
dpriv = pci_get_drvdata(dev);
|
|
if (dpriv) {
|
|
if (dpriv->client_id != client->client_id) {
|
|
mods_error_printk("dev %04x:%02x:%02x.%x already owned by client %u\n",
|
|
p->dev.domain,
|
|
p->dev.bus,
|
|
p->dev.device,
|
|
p->dev.function,
|
|
dpriv->client_id);
|
|
mutex_unlock(&mp.mtx);
|
|
pci_dev_put(dev);
|
|
LOG_EXT();
|
|
return -EBUSY;
|
|
}
|
|
if (dpriv->nvecs) {
|
|
mods_error_printk("interrupt for dev %04x:%02x:%02x.%x already registered\n",
|
|
p->dev.domain,
|
|
p->dev.bus,
|
|
p->dev.device,
|
|
p->dev.function);
|
|
mutex_unlock(&mp.mtx);
|
|
pci_dev_put(dev);
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
err = mods_allocate_irqs(client, dev, p->irq_count,
|
|
p->irq_flags);
|
|
if (err) {
|
|
mods_error_printk("could not allocate irqs for irq_type %d\n",
|
|
irq_type);
|
|
mutex_unlock(&mp.mtx);
|
|
pci_dev_put(dev);
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
|
|
dpriv = pci_get_drvdata(dev);
|
|
|
|
for (i = 0; i < p->irq_count; i++) {
|
|
u32 irq = ((irq_type == MODS_IRQ_TYPE_INT) ||
|
|
(irq_type == MODS_IRQ_TYPE_MSI)) ? dev->irq :
|
|
dpriv->msix_entries[i].vector;
|
|
|
|
err = add_irq_map(client, dev, p, irq, i);
|
|
if (unlikely(err)) {
|
|
#ifdef CONFIG_PCI_MSI
|
|
if (irq_type == MODS_IRQ_TYPE_MSI)
|
|
pci_disable_msi(dev);
|
|
else if (irq_type == MODS_IRQ_TYPE_MSIX)
|
|
pci_disable_msix(dev);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&mp.mtx);
|
|
pci_dev_put(dev);
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_PCI */
|
|
|
|
static int mods_register_cpu_irq(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_4 *p)
|
|
{
|
|
u32 irq = p->dev.bus;
|
|
int err;
|
|
|
|
LOG_ENT();
|
|
|
|
if (unlikely(mutex_lock_interruptible(&mp.mtx))) {
|
|
LOG_EXT();
|
|
return -EINTR;
|
|
}
|
|
|
|
/* Determine if the interrupt is already hooked */
|
|
if (mods_lookup_cpu_irq(0, irq) == IRQ_FOUND) {
|
|
mods_error_printk("CPU IRQ 0x%x has already been registered\n",
|
|
irq);
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Register interrupt */
|
|
err = add_irq_map(client, 0, p, irq, 0);
|
|
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_PCI
|
|
static int mods_unregister_pci_irq(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_2 *p)
|
|
{
|
|
struct pci_dev *dev;
|
|
int err = OK;
|
|
|
|
LOG_ENT();
|
|
|
|
/* Get the PCI device structure for the specified device from kernel */
|
|
err = mods_find_pci_dev(client, &p->dev, &dev);
|
|
if (unlikely(err)) {
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
|
|
err = mods_free_irqs(client->client_id, dev);
|
|
|
|
pci_dev_put(dev);
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
static int mods_unregister_cpu_irq(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_2 *p)
|
|
{
|
|
struct dev_irq_map *del = NULL;
|
|
struct dev_irq_map *next;
|
|
unsigned int irq;
|
|
|
|
LOG_ENT();
|
|
|
|
irq = p->dev.bus;
|
|
|
|
if (unlikely(mutex_lock_interruptible(&mp.mtx))) {
|
|
LOG_EXT();
|
|
return -EINTR;
|
|
}
|
|
|
|
/* Determine if the interrupt is already hooked by this client */
|
|
if (mods_lookup_cpu_irq(client->client_id, irq) == IRQ_NOT_FOUND) {
|
|
mods_error_printk(
|
|
"IRQ 0x%x not hooked, can't unhook\n",
|
|
irq);
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Delete device interrupt from the list */
|
|
list_for_each_entry_safe(del, next, &client->irq_list, list) {
|
|
if ((irq == del->apic_irq) && (del->dev == 0)) {
|
|
if (del->type != p->type) {
|
|
mods_error_printk("wrong IRQ type passed\n");
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return -EINVAL;
|
|
}
|
|
list_del(&del->list);
|
|
mods_debug_printk(DEBUG_ISR,
|
|
"unregistered CPU IRQ 0x%x\n",
|
|
irq);
|
|
mods_free_map(del);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&mp.mtx);
|
|
LOG_EXT();
|
|
return OK;
|
|
}
|
|
|
|
/*************************
|
|
* ESCAPE CALL FUNCTIONS *
|
|
*************************/
|
|
|
|
int esc_mods_register_irq_4(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_4 *p)
|
|
{
|
|
u32 irq_type = MODS_IRQ_TYPE_FROM_FLAGS(p->irq_flags);
|
|
|
|
if (irq_type == MODS_IRQ_TYPE_CPU)
|
|
return mods_register_cpu_irq(client, p);
|
|
#ifdef CONFIG_PCI
|
|
return mods_register_pci_irq(client, p);
|
|
#else
|
|
mods_error_printk("PCI not available\n");
|
|
return -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
int esc_mods_register_irq_3(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_3 *p)
|
|
{
|
|
struct MODS_REGISTER_IRQ_4 irq_data = { {0} };
|
|
u32 ii = 0;
|
|
|
|
irq_data.dev = p->dev;
|
|
irq_data.aperture_addr = p->aperture_addr;
|
|
irq_data.aperture_size = p->aperture_size;
|
|
irq_data.mask_info_cnt = p->mask_info_cnt;
|
|
for (ii = 0; ii < p->mask_info_cnt; ii++) {
|
|
irq_data.mask_info[ii].mask_type = p->mask_info[ii].mask_type;
|
|
irq_data.mask_info[ii].irq_pending_offset =
|
|
p->mask_info[ii].irq_pending_offset;
|
|
irq_data.mask_info[ii].irq_enabled_offset =
|
|
p->mask_info[ii].irq_enabled_offset;
|
|
irq_data.mask_info[ii].irq_enable_offset =
|
|
p->mask_info[ii].irq_enable_offset;
|
|
irq_data.mask_info[ii].irq_disable_offset =
|
|
p->mask_info[ii].irq_disable_offset;
|
|
irq_data.mask_info[ii].and_mask =
|
|
p->mask_info[ii].and_mask;
|
|
irq_data.mask_info[ii].or_mask =
|
|
p->mask_info[ii].or_mask;
|
|
}
|
|
irq_data.irq_count = 1;
|
|
irq_data.irq_flags = p->irq_type;
|
|
|
|
return esc_mods_register_irq_4(client, &irq_data);
|
|
}
|
|
|
|
int esc_mods_register_irq_2(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_2 *p)
|
|
{
|
|
struct MODS_REGISTER_IRQ_4 irq_data = { {0} };
|
|
|
|
irq_data.dev = p->dev;
|
|
irq_data.irq_count = 1;
|
|
irq_data.irq_flags = p->type;
|
|
|
|
#ifdef CONFIG_PCI
|
|
{
|
|
/* Get the PCI device structure */
|
|
struct pci_dev *dev;
|
|
int err;
|
|
|
|
err = mods_find_pci_dev(client, &p->dev, &dev);
|
|
if (unlikely(err))
|
|
return err;
|
|
|
|
irq_data.aperture_addr = pci_resource_start(dev, 0);
|
|
irq_data.aperture_size = pci_resource_len(dev, 0);
|
|
|
|
pci_dev_put(dev);
|
|
}
|
|
#endif
|
|
|
|
return esc_mods_register_irq_4(client, &irq_data);
|
|
}
|
|
|
|
int esc_mods_register_irq(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ *p)
|
|
{
|
|
struct MODS_REGISTER_IRQ_2 register_irq = { {0} };
|
|
|
|
register_irq.dev.domain = 0;
|
|
register_irq.dev.bus = p->dev.bus;
|
|
register_irq.dev.device = p->dev.device;
|
|
register_irq.dev.function = p->dev.function;
|
|
register_irq.type = p->type;
|
|
|
|
return esc_mods_register_irq_2(client, ®ister_irq);
|
|
}
|
|
|
|
int esc_mods_unregister_irq_2(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_2 *p)
|
|
{
|
|
if (p->type == MODS_IRQ_TYPE_CPU)
|
|
return mods_unregister_cpu_irq(client, p);
|
|
#ifdef CONFIG_PCI
|
|
return mods_unregister_pci_irq(client, p);
|
|
#else
|
|
return -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
int esc_mods_unregister_irq(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ *p)
|
|
{
|
|
struct MODS_REGISTER_IRQ_2 register_irq = { {0} };
|
|
|
|
register_irq.dev.domain = 0;
|
|
register_irq.dev.bus = p->dev.bus;
|
|
register_irq.dev.device = p->dev.device;
|
|
register_irq.dev.function = p->dev.function;
|
|
register_irq.type = p->type;
|
|
|
|
return esc_mods_unregister_irq_2(client, ®ister_irq);
|
|
}
|
|
|
|
int esc_mods_query_irq_3(struct mods_client *client,
|
|
struct MODS_QUERY_IRQ_3 *p)
|
|
{
|
|
struct irq_q_info *q = NULL;
|
|
unsigned int i = 0;
|
|
unsigned long flags = 0;
|
|
unsigned int cur_time = get_cur_time();
|
|
|
|
LOG_ENT();
|
|
|
|
/* Clear return array */
|
|
memset(p->irq_list, 0xFF, sizeof(p->irq_list));
|
|
|
|
/* Lock IRQ queue */
|
|
spin_lock_irqsave(&client->irq_lock, flags);
|
|
|
|
/* Fill in return array with IRQ information */
|
|
q = &client->irq_queue;
|
|
for (i = 0;
|
|
(q->head != q->tail) && (i < MODS_MAX_IRQS);
|
|
q->head++, i++) {
|
|
|
|
unsigned int index = q->head & (MODS_MAX_IRQS - 1);
|
|
struct pci_dev *dev = q->data[index].dev;
|
|
|
|
if (dev) {
|
|
p->irq_list[i].dev.domain = pci_domain_nr(dev->bus);
|
|
p->irq_list[i].dev.bus = dev->bus->number;
|
|
p->irq_list[i].dev.device = PCI_SLOT(dev->devfn);
|
|
p->irq_list[i].dev.function = PCI_FUNC(dev->devfn);
|
|
} else {
|
|
p->irq_list[i].dev.domain = 0;
|
|
p->irq_list[i].dev.bus = q->data[index].irq;
|
|
p->irq_list[i].dev.device = 0xFFU;
|
|
p->irq_list[i].dev.function = 0xFFU;
|
|
}
|
|
p->irq_list[i].irq_index = q->data[index].irq_index;
|
|
p->irq_list[i].delay = cur_time - q->data[index].time;
|
|
|
|
/* Print info about IRQ status returned */
|
|
if (dev) {
|
|
mods_debug_printk(DEBUG_ISR_DETAILED,
|
|
"retrieved IRQ index=%d dev %04x:%02x:%02x.%x, time=%uus, delay=%uus\n",
|
|
p->irq_list[i].irq_index,
|
|
p->irq_list[i].dev.domain,
|
|
p->irq_list[i].dev.bus,
|
|
p->irq_list[i].dev.device,
|
|
p->irq_list[i].dev.function,
|
|
q->data[index].time,
|
|
p->irq_list[i].delay);
|
|
} else {
|
|
mods_debug_printk(DEBUG_ISR_DETAILED,
|
|
"retrieved IRQ 0x%x, time=%uus, delay=%uus\n",
|
|
(unsigned int)p->irq_list[i].dev.bus,
|
|
q->data[index].time,
|
|
p->irq_list[i].delay);
|
|
}
|
|
}
|
|
|
|
/* Indicate if there are more IRQs pending */
|
|
if (q->head != q->tail)
|
|
p->more = 1;
|
|
|
|
/* Unlock IRQ queue */
|
|
spin_unlock_irqrestore(&client->irq_lock, flags);
|
|
|
|
LOG_EXT();
|
|
return OK;
|
|
}
|
|
|
|
int esc_mods_query_irq_2(struct mods_client *client,
|
|
struct MODS_QUERY_IRQ_2 *p)
|
|
{
|
|
int retval, i;
|
|
struct MODS_QUERY_IRQ_3 query_irq = { { { { 0 } } } };
|
|
|
|
retval = esc_mods_query_irq_3(client, &query_irq);
|
|
if (retval)
|
|
return retval;
|
|
|
|
for (i = 0; i < MODS_MAX_IRQS; i++) {
|
|
p->irq_list[i].dev = query_irq.irq_list[i].dev;
|
|
p->irq_list[i].delay = query_irq.irq_list[i].delay;
|
|
}
|
|
p->more = query_irq.more;
|
|
return OK;
|
|
}
|
|
|
|
int esc_mods_query_irq(struct mods_client *client,
|
|
struct MODS_QUERY_IRQ *p)
|
|
{
|
|
int retval, i;
|
|
struct MODS_QUERY_IRQ_3 query_irq = { { { { 0 } } } };
|
|
|
|
retval = esc_mods_query_irq_3(client, &query_irq);
|
|
if (retval)
|
|
return retval;
|
|
|
|
for (i = 0; i < MODS_MAX_IRQS; i++) {
|
|
p->irq_list[i].dev.bus = query_irq.irq_list[i].dev.bus;
|
|
p->irq_list[i].dev.device = query_irq.irq_list[i].dev.device;
|
|
p->irq_list[i].dev.function
|
|
= query_irq.irq_list[i].dev.function;
|
|
p->irq_list[i].delay = query_irq.irq_list[i].delay;
|
|
}
|
|
p->more = query_irq.more;
|
|
return OK;
|
|
}
|
|
|
|
int esc_mods_irq_handled_2(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ_2 *p)
|
|
{
|
|
unsigned long flags = 0;
|
|
u32 irq = p->dev.bus;
|
|
struct dev_irq_map *t = NULL;
|
|
struct dev_irq_map *next = NULL;
|
|
int err = -EINVAL;
|
|
|
|
if (p->type != MODS_IRQ_TYPE_CPU)
|
|
return -EINVAL;
|
|
|
|
LOG_ENT();
|
|
|
|
/* Print info */
|
|
mods_debug_printk(DEBUG_ISR_DETAILED,
|
|
"mark CPU IRQ 0x%x handled\n", irq);
|
|
|
|
/* Lock IRQ queue */
|
|
spin_lock_irqsave(&client->irq_lock, flags);
|
|
|
|
list_for_each_entry_safe(t, next, &client->irq_list, list) {
|
|
if (t->apic_irq == irq) {
|
|
if (t->type != p->type) {
|
|
mods_error_printk(
|
|
"IRQ type doesn't match registered IRQ\n");
|
|
} else {
|
|
enable_irq(irq);
|
|
err = OK;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Unlock IRQ queue */
|
|
spin_unlock_irqrestore(&client->irq_lock, flags);
|
|
|
|
LOG_EXT();
|
|
return err;
|
|
}
|
|
|
|
int esc_mods_irq_handled(struct mods_client *client,
|
|
struct MODS_REGISTER_IRQ *p)
|
|
{
|
|
struct MODS_REGISTER_IRQ_2 register_irq = { {0} };
|
|
|
|
register_irq.dev.domain = 0;
|
|
register_irq.dev.bus = p->dev.bus;
|
|
register_irq.dev.device = p->dev.device;
|
|
register_irq.dev.function = p->dev.function;
|
|
register_irq.type = p->type;
|
|
|
|
return esc_mods_irq_handled_2(client, ®ister_irq);
|
|
}
|
|
|
|
#if defined(MODS_TEGRA) && defined(CONFIG_OF_IRQ) && defined(CONFIG_OF)
|
|
int esc_mods_map_irq(struct mods_client *client,
|
|
struct MODS_DT_INFO *p)
|
|
{
|
|
int err = 0;
|
|
/* the physical irq */
|
|
int hwirq;
|
|
/* platform device handle */
|
|
struct platform_device *pdev = NULL;
|
|
/* irq parameters */
|
|
struct of_phandle_args oirq;
|
|
/* Search for the node by device tree name */
|
|
struct device_node *np = of_find_node_by_name(NULL, p->dt_name);
|
|
|
|
if (!np) {
|
|
mods_error_printk("node %s is not valid\n", p->full_name);
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
/* Can be multiple nodes that share the same dt name, */
|
|
/* make sure you get the correct node matched by the device's full */
|
|
/* name in device tree (i.e. watchdog@30c0000 as opposed */
|
|
/* to watchdog) */
|
|
while (of_node_cmp(np->full_name, p->full_name)) {
|
|
np = of_find_node_by_name(np, p->dt_name);
|
|
if (!np) {
|
|
mods_error_printk("node %s is not valid\n",
|
|
p->full_name);
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
p->irq = irq_of_parse_and_map(np, p->index);
|
|
err = of_irq_parse_one(np, p->index, &oirq);
|
|
if (err) {
|
|
mods_error_printk("could not parse IRQ\n");
|
|
goto error;
|
|
}
|
|
|
|
hwirq = oirq.args[1];
|
|
/* Get the platform device handle */
|
|
pdev = of_find_device_by_node(np);
|
|
|
|
if (of_node_cmp(p->dt_name, "watchdog") == 0) {
|
|
/* Enable and unmask interrupt for watchdog */
|
|
struct resource *res_src = platform_get_resource(pdev,
|
|
IORESOURCE_MEM, 0);
|
|
struct resource *res_tke = platform_get_resource(pdev,
|
|
IORESOURCE_MEM, 2);
|
|
void __iomem *wdt_tke = devm_ioremap(&pdev->dev,
|
|
res_tke->start, resource_size(res_tke));
|
|
int wdt_index = ((res_src->start >> 16) & 0xF) - 0xc;
|
|
|
|
writel(TOP_TKE_TKEIE_WDT_MASK(wdt_index), wdt_tke +
|
|
TOP_TKE_TKEIE(hwirq));
|
|
}
|
|
|
|
error:
|
|
of_node_put(np);
|
|
/* enable the interrupt */
|
|
return err;
|
|
}
|
|
|
|
int esc_mods_map_irq_to_gpio(struct mods_client *client,
|
|
struct MODS_GPIO_INFO *p)
|
|
{
|
|
//TODO: Make sure you are allocating gpio properly
|
|
int gpio_handle;
|
|
int irq;
|
|
int err = 0;
|
|
|
|
struct device_node *np = of_find_node_by_name(NULL, p->dt_name);
|
|
|
|
if (!np) {
|
|
mods_error_printk("node %s is not valid\n", p->full_name);
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
while (of_node_cmp(np->full_name, p->full_name)) {
|
|
np = of_find_node_by_name(np, p->dt_name);
|
|
if (!np) {
|
|
mods_error_printk("node %s is not valid\n",
|
|
p->full_name);
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
gpio_handle = of_get_named_gpio(np, p->name, 0);
|
|
if (!gpio_is_valid(gpio_handle)) {
|
|
mods_error_printk("gpio %s is missing\n", p->name);
|
|
err = gpio_handle;
|
|
goto error;
|
|
}
|
|
|
|
err = gpio_direction_input(gpio_handle);
|
|
if (err < 0) {
|
|
mods_error_printk("pex_rst_gpio input direction change failed\n");
|
|
goto error;
|
|
}
|
|
|
|
irq = gpio_to_irq(gpio_handle);
|
|
if (irq < 0) {
|
|
mods_error_printk("Unable to get irq for pex_rst_gpio\n");
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
p->irq = irq;
|
|
|
|
error:
|
|
of_node_put(np);
|
|
return err;
|
|
}
|
|
#endif
|