Skip to content

Commit

Permalink
[SPARC64]: Add PCI MSI support on Niagara.
Browse files Browse the repository at this point in the history
This is kind of hokey, we could use the hardware provided facilities
much better.

MSIs are assosciated with MSI Queues.  MSI Queues generate interrupts
when any MSI assosciated with it is signalled.  This suggests a
two-tiered IRQ dispatch scheme:

	MSI Queue interrupt --> queue interrupt handler
		MSI dispatch --> driver interrupt handler

But we just get one-level under Linux currently.  What I'd like to do
is possibly stick the IRQ actions into a per-MSI-Queue data structure,
and dispatch them form there, but the generic IRQ layer doesn't
provide a way to do that right now.

So, the current kludge is to "ACK" the interrupt by processing the
MSI Queue data structures and ACK'ing them, then we run the actual
handler like normal.

We are wasting a lot of useful information, for example the MSI data
and address are provided with ever MSI, as well as a system tick if
available.  If we could pass this into the IRQ handler it could help
with certain things, in particular for PCI-Express error messages.

The MSI entries on sparc64 also tell you exactly which bus/device/fn
sent the MSI, which would be great for error handling when no
registered IRQ handler can service the interrupt.

We override the disable/enable IRQ chip methods in sun4v_msi, so we
have to call {mask,unmask}_msi_irq() directly from there.  This is
another ugly wart.

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Feb 11, 2007
1 parent 68c9218 commit 35a17eb
Show file tree
Hide file tree
Showing 8 changed files with 934 additions and 10 deletions.
104 changes: 95 additions & 9 deletions arch/sparc64/kernel/irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <linux/seq_file.h>
#include <linux/bootmem.h>
#include <linux/irq.h>
#include <linux/msi.h>

#include <asm/ptrace.h>
#include <asm/processor.h>
Expand Down Expand Up @@ -87,34 +88,39 @@ struct ino_bucket ivector_table[NUM_IVECS] __attribute__ ((aligned (SMP_CACHE_BY
#define irq_work(__cpu) &(trap_block[(__cpu)].irq_worklist)

static unsigned int virt_to_real_irq_table[NR_IRQS];
static unsigned char virt_irq_cur = 1;

static unsigned char virt_irq_alloc(unsigned int real_irq)
{
unsigned char ent;

BUILD_BUG_ON(NR_IRQS >= 256);

ent = virt_irq_cur;
for (ent = 1; ent < NR_IRQS; ent++) {
if (!virt_to_real_irq_table[ent])
break;
}
if (ent >= NR_IRQS) {
printk(KERN_ERR "IRQ: Out of virtual IRQs.\n");
return 0;
}

virt_irq_cur = ent + 1;
virt_to_real_irq_table[ent] = real_irq;

return ent;
}

#if 0 /* Currently unused. */
static unsigned char real_to_virt_irq(unsigned int real_irq)
static void virt_irq_free(unsigned int virt_irq)
{
struct ino_bucket *bucket = __bucket(real_irq);
unsigned int real_irq;

return bucket->virt_irq;
if (virt_irq >= NR_IRQS)
return;

real_irq = virt_to_real_irq_table[virt_irq];
virt_to_real_irq_table[virt_irq] = 0;

__bucket(real_irq)->virt_irq = 0;
}
#endif

static unsigned int virt_to_real_irq(unsigned char virt_irq)
{
Expand Down Expand Up @@ -341,6 +347,20 @@ static void sun4v_irq_disable(unsigned int virt_irq)
}
}

#ifdef CONFIG_PCI_MSI
static void sun4v_msi_enable(unsigned int virt_irq)
{
sun4v_irq_enable(virt_irq);
unmask_msi_irq(virt_irq);
}

static void sun4v_msi_disable(unsigned int virt_irq)
{
mask_msi_irq(virt_irq);
sun4v_irq_disable(virt_irq);
}
#endif

static void sun4v_irq_end(unsigned int virt_irq)
{
struct ino_bucket *bucket = virt_irq_to_bucket(virt_irq);
Expand Down Expand Up @@ -398,6 +418,18 @@ static struct irq_chip sun4v_irq_ack = {
.end = sun4v_irq_end,
};

#ifdef CONFIG_PCI_MSI
static struct irq_chip sun4v_msi = {
.typename = "sun4v+msi",
.mask = mask_msi_irq,
.unmask = unmask_msi_irq,
.enable = sun4v_msi_enable,
.disable = sun4v_msi_disable,
.ack = run_pre_handler,
.end = sun4v_irq_end,
};
#endif

void irq_install_pre_handler(int virt_irq,
void (*func)(unsigned int, void *, void *),
void *arg1, void *arg2)
Expand All @@ -411,7 +443,11 @@ void irq_install_pre_handler(int virt_irq,

chip = get_irq_chip(virt_irq);
if (chip == &sun4u_irq_ack ||
chip == &sun4v_irq_ack)
chip == &sun4v_irq_ack
#ifdef CONFIG_PCI_MSI
|| chip == &sun4v_msi
#endif
)
return;

chip = (chip == &sun4u_irq ?
Expand Down Expand Up @@ -489,6 +525,56 @@ unsigned int sun4v_build_irq(u32 devhandle, unsigned int devino)
return bucket->virt_irq;
}

#ifdef CONFIG_PCI_MSI
unsigned int sun4v_build_msi(u32 devhandle, unsigned int *virt_irq_p,
unsigned int msi_start, unsigned int msi_end)
{
struct ino_bucket *bucket;
struct irq_handler_data *data;
unsigned long sysino;
unsigned int devino;

BUG_ON(tlb_type != hypervisor);

/* Find a free devino in the given range. */
for (devino = msi_start; devino < msi_end; devino++) {
sysino = sun4v_devino_to_sysino(devhandle, devino);
bucket = &ivector_table[sysino];
if (!bucket->virt_irq)
break;
}
if (devino >= msi_end)
return 0;

sysino = sun4v_devino_to_sysino(devhandle, devino);
bucket = &ivector_table[sysino];
bucket->virt_irq = virt_irq_alloc(__irq(bucket));
*virt_irq_p = bucket->virt_irq;
set_irq_chip(bucket->virt_irq, &sun4v_msi);

data = get_irq_chip_data(bucket->virt_irq);
if (unlikely(data))
return devino;

data = kzalloc(sizeof(struct irq_handler_data), GFP_ATOMIC);
if (unlikely(!data)) {
prom_printf("IRQ: kzalloc(irq_handler_data) failed.\n");
prom_halt();
}
set_irq_chip_data(bucket->virt_irq, data);

data->imap = ~0UL;
data->iclr = ~0UL;

return devino;
}

void sun4v_destroy_msi(unsigned int virt_irq)
{
virt_irq_free(virt_irq);
}
#endif

void ack_bad_irq(unsigned int virt_irq)
{
struct ino_bucket *bucket = virt_irq_to_bucket(virt_irq);
Expand Down
35 changes: 35 additions & 0 deletions arch/sparc64/kernel/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/smp_lock.h>
#include <linux/msi.h>
#include <linux/irq.h>
#include <linux/init.h>

#include <asm/uaccess.h>
Expand Down Expand Up @@ -646,4 +648,37 @@ int pci_domain_nr(struct pci_bus *pbus)
}
EXPORT_SYMBOL(pci_domain_nr);

#ifdef CONFIG_PCI_MSI
int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc)
{
struct pcidev_cookie *pcp = pdev->sysdata;
struct pci_pbm_info *pbm = pcp->pbm;
struct pci_controller_info *p = pbm->parent;
int virt_irq, err;

if (!pbm->msi_num || !p->setup_msi_irq)
return -EINVAL;

err = p->setup_msi_irq(&virt_irq, pdev, desc);
if (err < 0)
return err;

return virt_irq;
}

void arch_teardown_msi_irq(unsigned int virt_irq)
{
struct msi_desc *entry = get_irq_data(virt_irq);
struct pci_dev *pdev = entry->dev;
struct pcidev_cookie *pcp = pdev->sysdata;
struct pci_pbm_info *pbm = pcp->pbm;
struct pci_controller_info *p = pbm->parent;

if (!pbm->msi_num || !p->setup_msi_irq)
return;

return p->teardown_msi_irq(virt_irq, pdev);
}
#endif /* !(CONFIG_PCI_MSI) */

#endif /* !(CONFIG_PCI) */
Loading

0 comments on commit 35a17eb

Please sign in to comment.