Commit 35a17eb6 authored by David S. Miller's avatar David S. Miller

[SPARC64]: Add PCI MSI support on Niagara.

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: default avatarDavid S. Miller <davem@davemloft.net>
parent 68c92186
......@@ -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>
......@@ -87,7 +88,6 @@ 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)
{
......@@ -95,26 +95,32 @@ static unsigned char virt_irq_alloc(unsigned int real_irq)
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)
{
......@@ -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);
......@@ -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)
......@@ -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 ?
......@@ -489,6 +525,56 @@ out:
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);
......
......@@ -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>
......@@ -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) */
This diff is collapsed.
......@@ -28,4 +28,65 @@ extern int pci_sun4v_config_put(unsigned long devhandle,
unsigned long size,
unsigned long data);
extern unsigned long pci_sun4v_msiq_conf(unsigned long devhandle,
unsigned long msiqid,
unsigned long msiq_paddr,
unsigned long num_entries);
extern unsigned long pci_sun4v_msiq_info(unsigned long devhandle,
unsigned long msiqid,
unsigned long *msiq_paddr,
unsigned long *num_entries);
extern unsigned long pci_sun4v_msiq_getvalid(unsigned long devhandle,
unsigned long msiqid,
unsigned long *valid);
extern unsigned long pci_sun4v_msiq_setvalid(unsigned long devhandle,
unsigned long msiqid,
unsigned long valid);
extern unsigned long pci_sun4v_msiq_getstate(unsigned long devhandle,
unsigned long msiqid,
unsigned long *state);
extern unsigned long pci_sun4v_msiq_setstate(unsigned long devhandle,
unsigned long msiqid,
unsigned long state);
extern unsigned long pci_sun4v_msiq_gethead(unsigned long devhandle,
unsigned long msiqid,
unsigned long *head);
extern unsigned long pci_sun4v_msiq_sethead(unsigned long devhandle,
unsigned long msiqid,
unsigned long head);
extern unsigned long pci_sun4v_msiq_gettail(unsigned long devhandle,
unsigned long msiqid,
unsigned long *head);
extern unsigned long pci_sun4v_msi_getvalid(unsigned long devhandle,
unsigned long msinum,
unsigned long *valid);
extern unsigned long pci_sun4v_msi_setvalid(unsigned long devhandle,
unsigned long msinum,
unsigned long valid);
extern unsigned long pci_sun4v_msi_getmsiq(unsigned long devhandle,
unsigned long msinum,
unsigned long *msiq);
extern unsigned long pci_sun4v_msi_setmsiq(unsigned long devhandle,
unsigned long msinum,
unsigned long msiq,
unsigned long msitype);
extern unsigned long pci_sun4v_msi_getstate(unsigned long devhandle,
unsigned long msinum,
unsigned long *state);
extern unsigned long pci_sun4v_msi_setstate(unsigned long devhandle,
unsigned long msinum,
unsigned long state);
extern unsigned long pci_sun4v_msg_getmsiq(unsigned long devhandle,
unsigned long msinum,
unsigned long *msiq);
extern unsigned long pci_sun4v_msg_setmsiq(unsigned long devhandle,
unsigned long msinum,
unsigned long msiq);
extern unsigned long pci_sun4v_msg_getvalid(unsigned long devhandle,
unsigned long msinum,
unsigned long *valid);
extern unsigned long pci_sun4v_msg_setvalid(unsigned long devhandle,
unsigned long msinum,
unsigned long valid);
#endif /* !(_PCI_SUN4V_H) */
......@@ -93,3 +93,269 @@ pci_sun4v_config_put:
mov -1, %o1
1: retl
mov %o1, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: msiq phys address
* %o3: num entries
*
* returns %o0: status
*
* status will be zero if the operation completed
* successfully, else -1 if not
*/
.globl pci_sun4v_msiq_conf
pci_sun4v_msiq_conf:
mov HV_FAST_PCI_MSIQ_CONF, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: &msiq_phys_addr
* %o3: &msiq_num_entries
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_info
pci_sun4v_msiq_info:
mov %o2, %o4
mov HV_FAST_PCI_MSIQ_INFO, %o5
ta HV_FAST_TRAP
stx %o1, [%o4]
stx %o2, [%o3]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: &valid
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_getvalid
pci_sun4v_msiq_getvalid:
mov HV_FAST_PCI_MSIQ_GETVALID, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: valid
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_setvalid
pci_sun4v_msiq_setvalid:
mov HV_FAST_PCI_MSIQ_SETVALID, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: &state
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_getstate
pci_sun4v_msiq_getstate:
mov HV_FAST_PCI_MSIQ_GETSTATE, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: state
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_setstate
pci_sun4v_msiq_setstate:
mov HV_FAST_PCI_MSIQ_SETSTATE, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: &head
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_gethead
pci_sun4v_msiq_gethead:
mov HV_FAST_PCI_MSIQ_GETHEAD, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: head
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_sethead
pci_sun4v_msiq_sethead:
mov HV_FAST_PCI_MSIQ_SETHEAD, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msiqid
* %o2: &tail
*
* returns %o0: status
*/
.globl pci_sun4v_msiq_gettail
pci_sun4v_msiq_gettail:
mov HV_FAST_PCI_MSIQ_GETTAIL, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: &valid
*
* returns %o0: status
*/
.globl pci_sun4v_msi_getvalid
pci_sun4v_msi_getvalid:
mov HV_FAST_PCI_MSI_GETVALID, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: valid
*
* returns %o0: status
*/
.globl pci_sun4v_msi_setvalid
pci_sun4v_msi_setvalid:
mov HV_FAST_PCI_MSI_SETVALID, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: &msiq
*
* returns %o0: status
*/
.globl pci_sun4v_msi_getmsiq
pci_sun4v_msi_getmsiq:
mov HV_FAST_PCI_MSI_GETMSIQ, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: msitype
* %o3: msiq
*
* returns %o0: status
*/
.globl pci_sun4v_msi_setmsiq
pci_sun4v_msi_setmsiq:
mov HV_FAST_PCI_MSI_SETMSIQ, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: &state
*
* returns %o0: status
*/
.globl pci_sun4v_msi_getstate
pci_sun4v_msi_getstate:
mov HV_FAST_PCI_MSI_GETSTATE, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: state
*
* returns %o0: status
*/
.globl pci_sun4v_msi_setstate
pci_sun4v_msi_setstate:
mov HV_FAST_PCI_MSI_SETSTATE, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: &msiq
*
* returns %o0: status
*/
.globl pci_sun4v_msg_getmsiq
pci_sun4v_msg_getmsiq:
mov HV_FAST_PCI_MSG_GETMSIQ, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: msiq
*
* returns %o0: status
*/
.globl pci_sun4v_msg_setmsiq
pci_sun4v_msg_setmsiq:
mov HV_FAST_PCI_MSG_SETMSIQ, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: &valid
*
* returns %o0: status
*/
.globl pci_sun4v_msg_getvalid
pci_sun4v_msg_getvalid:
mov HV_FAST_PCI_MSG_GETVALID, %o5
ta HV_FAST_TRAP
stx %o1, [%o2]
retl
mov %o0, %o0
/* %o0: devhandle
* %o1: msinum
* %o2: valid
*
* returns %o0: status
*/
.globl pci_sun4v_msg_setvalid
pci_sun4v_msg_setvalid:
mov HV_FAST_PCI_MSG_SETVALID, %o5
ta HV_FAST_TRAP
retl
mov %o0, %o0
......@@ -4,7 +4,7 @@
config PCI_MSI
bool "Message Signaled Interrupts (MSI and MSI-X)"
depends on PCI
depends on (X86_LOCAL_APIC && X86_IO_APIC) || IA64
depends on (X86_LOCAL_APIC && X86_IO_APIC) || IA64 || SPARC64
help
This allows device drivers to enable MSI (Message Signaled
Interrupts). Message Signaled Interrupts enable a device to
......
......@@ -46,6 +46,10 @@ extern void irq_install_pre_handler(int virt_irq,
#define irq_canonicalize(irq) (irq)
extern unsigned int build_irq(int inofixup, unsigned long iclr, unsigned long imap);
extern unsigned int sun4v_build_irq(u32 devhandle, unsigned int devino);
extern unsigned int sun4v_build_msi(u32 devhandle, unsigned int *virt_irq_p,
unsigned int msi_devino_start,
unsigned int msi_devino_end);
extern void sun4v_destroy_msi(unsigned int virt_irq);
extern unsigned int sbus_build_irq(void *sbus, unsigned int ino);
static __inline__ void set_softint(unsigned long bits)
......
......@@ -11,6 +11,7 @@
#include <linux/pci.h>
#include <linux/ioport.h>
#include <linux/spinlock.h>
#include <linux/msi.h>
#include <asm/io.h>
#include <asm/page.h>
......@@ -177,6 +178,24 @@ struct pci_pbm_info {
int is_66mhz_capable;
int all_devs_66mhz;
#ifdef CONFIG_PCI_MSI
/* MSI info. */
u32 msiq_num;
u32 msiq_ent_count;
u32 msiq_first;
u32 msiq_first_devino;
u32 msi_num;
u32 msi_first;
u32 msi_data_mask;
u32 msix_data_width;
u64 msi32_start;
u64 msi64_start;
u32 msi32_len;
u32 msi64_len;
void *msi_queues;
unsigned long *msi_bitmap;
#endif /* !(CONFIG_PCI_MSI) */
/* This PBM's streaming buffer. */
struct pci_strbuf stc;
......@@ -213,6 +232,12 @@ struct pci_controller_info {
void (*base_address_update)(struct pci_dev *, int);
void (*resource_adjust)(struct pci_dev *, struct resource *, struct resource *);
#ifdef CONFIG_PCI_MSI
int (*setup_msi_irq)(unsigned int *virt_irq_p, struct pci_dev *pdev,
struct msi_desc *entry);
void (*teardown_msi_irq)(unsigned int virt_irq, struct pci_dev *pdev);
#endif
/* Now things for the actual PCI bus probes. */
struct pci_ops *pci_ops;
unsigned int pci_first_busno;
......@@ -231,6 +256,9 @@ struct pcidev_cookie {
int num_prom_regs;
struct linux_prom_pci_registers prom_assignments[PROMREG_MAX];
int num_prom_assignments;
#ifdef CONFIG_PCI_MSI
unsigned int msi_num;
#endif
};
/* Currently these are the same across all PCI controllers
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment