[SPARC64]: Add PCI MSI support on Niagara.
authorDavid S. Miller <davem@sunset.davemloft.net>
Sun, 11 Feb 2007 01:41:02 +0000 (17:41 -0800)
committerDavid S. Miller <davem@sunset.davemloft.net>
Sun, 11 Feb 2007 07:50:37 +0000 (23:50 -0800)
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>
arch/sparc64/kernel/irq.c
arch/sparc64/kernel/pci.c
arch/sparc64/kernel/pci_sun4v.c
arch/sparc64/kernel/pci_sun4v.h
arch/sparc64/kernel/pci_sun4v_asm.S
drivers/pci/Kconfig
include/asm-sparc64/irq.h
include/asm-sparc64/pbm.h

index 2873835e2626af60633c6d9a116c32d901c760df..b5ff3ee5ace1cc23d3a86cd50878568b505ea7d8 100644 (file)
@@ -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);
index dfc41cd4bb5d06f690fa47080e8a6dcbecb3c65e..6b740eb6fe7e45915a0e4372cf496036b3d3fb23 100644 (file)
@@ -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) */
index 6b04794b7a971bab494b7837748c6bc741bc7e5b..ec22cd61ec8ce8eeab6689a6de6ff4d04efc948d 100644 (file)
@@ -10,6 +10,8 @@
 #include <linux/slab.h>
 #include <linux/interrupt.h>
 #include <linux/percpu.h>
+#include <linux/irq.h>
+#include <linux/msi.h>
 
 #include <asm/pbm.h>
 #include <asm/iommu.h>
@@ -1074,6 +1076,443 @@ static void pci_sun4v_get_bus_range(struct pci_pbm_info *pbm)
 
 }
 
+#ifdef CONFIG_PCI_MSI
+struct pci_sun4v_msiq_entry {
+       u64             version_type;
+#define MSIQ_VERSION_MASK              0xffffffff00000000UL
+#define MSIQ_VERSION_SHIFT             32
+#define MSIQ_TYPE_MASK                 0x00000000000000ffUL
+#define MSIQ_TYPE_SHIFT                        0
+#define MSIQ_TYPE_NONE                 0x00
+#define MSIQ_TYPE_MSG                  0x01
+#define MSIQ_TYPE_MSI32                        0x02
+#define MSIQ_TYPE_MSI64                        0x03
+#define MSIQ_TYPE_INTX                 0x08
+#define MSIQ_TYPE_NONE2                        0xff
+
+       u64             intx_sysino;
+       u64             reserved1;
+       u64             stick;
+       u64             req_id;  /* bus/device/func */
+#define MSIQ_REQID_BUS_MASK            0xff00UL
+#define MSIQ_REQID_BUS_SHIFT           8
+#define MSIQ_REQID_DEVICE_MASK         0x00f8UL
+#define MSIQ_REQID_DEVICE_SHIFT                3
+#define MSIQ_REQID_FUNC_MASK           0x0007UL
+#define MSIQ_REQID_FUNC_SHIFT          0
+
+       u64             msi_address;
+
+       /* The format of this value is message type dependant.
+        * For MSI bits 15:0 are the data from the MSI packet.
+        * For MSI-X bits 31:0 are the data from the MSI packet.
+        * For MSG, the message code and message routing code where:
+        *      bits 39:32 is the bus/device/fn of the msg target-id
+        *      bits 18:16 is the message routing code
+        *      bits 7:0 is the message code
+        * For INTx the low order 2-bits are:
+        *      00 - INTA
+        *      01 - INTB
+        *      10 - INTC
+        *      11 - INTD
+        */
+       u64             msi_data;
+
+       u64             reserved2;
+};
+
+/* For now this just runs as a pre-handler for the real interrupt handler.
+ * So we just walk through the queue and ACK all the entries, update the
+ * head pointer, and return.
+ *
+ * In the longer term it would be nice to do something more integrated
+ * wherein we can pass in some of this MSI info to the drivers.  This
+ * would be most useful for PCIe fabric error messages, although we could
+ * invoke those directly from the loop here in order to pass the info around.
+ */
+static void pci_sun4v_msi_prehandler(unsigned int ino, void *data1, void *data2)
+{
+       struct pci_pbm_info *pbm = data1;
+       struct pci_sun4v_msiq_entry *base, *ep;
+       unsigned long msiqid, orig_head, head, type, err;
+
+       msiqid = (unsigned long) data2;
+
+       head = 0xdeadbeef;
+       err = pci_sun4v_msiq_gethead(pbm->devhandle, msiqid, &head);
+       if (unlikely(err))
+               goto hv_error_get;
+
+       if (unlikely(head >= (pbm->msiq_ent_count * sizeof(struct pci_sun4v_msiq_entry))))
+               goto bad_offset;
+
+       head /= sizeof(struct pci_sun4v_msiq_entry);
+       orig_head = head;
+       base = (pbm->msi_queues + ((msiqid - pbm->msiq_first) *
+                                  (pbm->msiq_ent_count *
+                                   sizeof(struct pci_sun4v_msiq_entry))));
+       ep = &base[head];
+       while ((ep->version_type & MSIQ_TYPE_MASK) != 0) {
+               type = (ep->version_type & MSIQ_TYPE_MASK) >> MSIQ_TYPE_SHIFT;
+               if (unlikely(type != MSIQ_TYPE_MSI32 &&
+                            type != MSIQ_TYPE_MSI64))
+                       goto bad_type;
+
+               pci_sun4v_msi_setstate(pbm->devhandle,
+                                      ep->msi_data /* msi_num */,
+                                      HV_MSISTATE_IDLE);
+
+               /* Clear the entry.  */
+               ep->version_type &= ~MSIQ_TYPE_MASK;
+
+               /* Go to next entry in ring.  */
+               head++;
+               if (head >= pbm->msiq_ent_count)
+                       head = 0;
+               ep = &base[head];
+       }
+
+       if (likely(head != orig_head)) {
+               /* ACK entries by updating head pointer.  */
+               head *= sizeof(struct pci_sun4v_msiq_entry);
+               err = pci_sun4v_msiq_sethead(pbm->devhandle, msiqid, head);
+               if (unlikely(err))
+                       goto hv_error_set;
+       }
+       return;
+
+hv_error_set:
+       printk(KERN_EMERG "MSI: Hypervisor set head gives error %lu\n", err);
+       goto hv_error_cont;
+
+hv_error_get:
+       printk(KERN_EMERG "MSI: Hypervisor get head gives error %lu\n", err);
+
+hv_error_cont:
+       printk(KERN_EMERG "MSI: devhandle[%x] msiqid[%lx] head[%lu]\n",
+              pbm->devhandle, msiqid, head);
+       return;
+
+bad_offset:
+       printk(KERN_EMERG "MSI: Hypervisor gives bad offset %lx max(%lx)\n",
+              head, pbm->msiq_ent_count * sizeof(struct pci_sun4v_msiq_entry));
+       return;
+
+bad_type:
+       printk(KERN_EMERG "MSI: Entry has bad type %lx\n", type);
+       return;
+}
+
+static int msi_bitmap_alloc(struct pci_pbm_info *pbm)
+{
+       unsigned long size, bits_per_ulong;
+
+       bits_per_ulong = sizeof(unsigned long) * 8;
+       size = (pbm->msi_num + (bits_per_ulong - 1)) & ~(bits_per_ulong - 1);
+       size /= 8;
+       BUG_ON(size % sizeof(unsigned long));
+
+       pbm->msi_bitmap = kzalloc(size, GFP_KERNEL);
+       if (!pbm->msi_bitmap)
+               return -ENOMEM;
+
+       return 0;
+}
+
+static void msi_bitmap_free(struct pci_pbm_info *pbm)
+{
+       kfree(pbm->msi_bitmap);
+       pbm->msi_bitmap = NULL;
+}
+
+static int msi_queue_alloc(struct pci_pbm_info *pbm)
+{
+       unsigned long q_size, alloc_size, pages, order;
+       int i;
+
+       q_size = pbm->msiq_ent_count * sizeof(struct pci_sun4v_msiq_entry);
+       alloc_size = (pbm->msiq_num * q_size);
+       order = get_order(alloc_size);
+       pages = __get_free_pages(GFP_KERNEL | __GFP_COMP, order);
+       if (pages == 0UL) {
+               printk(KERN_ERR "MSI: Cannot allocate MSI queues (o=%lu).\n",
+                      order);
+               return -ENOMEM;
+       }
+       memset((char *)pages, 0, PAGE_SIZE << order);
+       pbm->msi_queues = (void *) pages;
+
+       for (i = 0; i < pbm->msiq_num; i++) {
+               unsigned long err, base = __pa(pages + (i * q_size));
+               unsigned long ret1, ret2;
+
+               err = pci_sun4v_msiq_conf(pbm->devhandle,
+                                         pbm->msiq_first + i,
+                                         base, pbm->msiq_ent_count);
+               if (err) {
+                       printk(KERN_ERR "MSI: msiq register fails (err=%lu)\n",
+                              err);
+                       goto h_error;
+               }
+
+               err = pci_sun4v_msiq_info(pbm->devhandle,
+                                         pbm->msiq_first + i,
+                                         &ret1, &ret2);
+               if (err) {
+                       printk(KERN_ERR "MSI: Cannot read msiq (err=%lu)\n",
+                              err);
+                       goto h_error;
+               }
+               if (ret1 != base || ret2 != pbm->msiq_ent_count) {
+                       printk(KERN_ERR "MSI: Bogus qconf "
+                              "expected[%lx:%x] got[%lx:%lx]\n",
+                              base, pbm->msiq_ent_count,
+                              ret1, ret2);
+                       goto h_error;
+               }
+       }
+
+       return 0;
+
+h_error:
+       free_pages(pages, order);
+       return -EINVAL;
+}
+
+static void pci_sun4v_msi_init(struct pci_pbm_info *pbm)
+{
+       u32 *val;
+       int len;
+
+       val = of_get_property(pbm->prom_node, "#msi-eqs", &len);
+       if (!val || len != 4)
+               goto no_msi;
+       pbm->msiq_num = *val;
+       if (pbm->msiq_num) {
+               struct msiq_prop {
+                       u32 first_msiq;
+                       u32 num_msiq;
+                       u32 first_devino;
+               } *mqp;
+               struct msi_range_prop {
+                       u32 first_msi;
+                       u32 num_msi;
+               } *mrng;
+               struct addr_range_prop {
+                       u32 msi32_high;
+                       u32 msi32_low;
+                       u32 msi32_len;
+                       u32 msi64_high;
+                       u32 msi64_low;
+                       u32 msi64_len;
+               } *arng;
+
+               val = of_get_property(pbm->prom_node, "msi-eq-size", &len);
+               if (!val || len != 4)
+                       goto no_msi;
+
+               pbm->msiq_ent_count = *val;
+
+               mqp = of_get_property(pbm->prom_node,
+                                     "msi-eq-to-devino", &len);
+               if (!mqp || len != sizeof(struct msiq_prop))
+                       goto no_msi;
+
+               pbm->msiq_first = mqp->first_msiq;
+               pbm->msiq_first_devino = mqp->first_devino;
+
+               val = of_get_property(pbm->prom_node, "#msi", &len);
+               if (!val || len != 4)
+                       goto no_msi;
+               pbm->msi_num = *val;
+
+               mrng = of_get_property(pbm->prom_node, "msi-ranges", &len);
+               if (!mrng || len != sizeof(struct msi_range_prop))
+                       goto no_msi;
+               pbm->msi_first = mrng->first_msi;
+
+               val = of_get_property(pbm->prom_node, "msi-data-mask", &len);
+               if (!val || len != 4)
+                       goto no_msi;
+               pbm->msi_data_mask = *val;
+
+               val = of_get_property(pbm->prom_node, "msix-data-width", &len);
+               if (!val || len != 4)
+                       goto no_msi;
+               pbm->msix_data_width = *val;
+
+               arng = of_get_property(pbm->prom_node, "msi-address-ranges",
+                                      &len);
+               if (!arng || len != sizeof(struct addr_range_prop))
+                       goto no_msi;
+               pbm->msi32_start = ((u64)arng->msi32_high << 32) |
+                       (u64) arng->msi32_low;
+               pbm->msi64_start = ((u64)arng->msi64_high << 32) |
+                       (u64) arng->msi64_low;
+               pbm->msi32_len = arng->msi32_len;
+               pbm->msi64_len = arng->msi64_len;
+
+               if (msi_bitmap_alloc(pbm))
+                       goto no_msi;
+
+               if (msi_queue_alloc(pbm)) {
+                       msi_bitmap_free(pbm);
+                       goto no_msi;
+               }
+
+               printk(KERN_INFO "%s: MSI Queue first[%u] num[%u] count[%u] "
+                      "devino[0x%x]\n",
+                      pbm->name,
+                      pbm->msiq_first, pbm->msiq_num,
+                      pbm->msiq_ent_count,
+                      pbm->msiq_first_devino);
+               printk(KERN_INFO "%s: MSI first[%u] num[%u] mask[0x%x] "
+                      "width[%u]\n",
+                      pbm->name,
+                      pbm->msi_first, pbm->msi_num, pbm->msi_data_mask,
+                      pbm->msix_data_width);
+               printk(KERN_INFO "%s: MSI addr32[0x%lx:0x%x] "
+                      "addr64[0x%lx:0x%x]\n",
+                      pbm->name,
+                      pbm->msi32_start, pbm->msi32_len,
+                      pbm->msi64_start, pbm->msi64_len);
+               printk(KERN_INFO "%s: MSI queues at RA [%p]\n",
+                      pbm->name,
+                      pbm->msi_queues);
+       }
+
+       return;
+
+no_msi:
+       pbm->msiq_num = 0;
+       printk(KERN_INFO "%s: No MSI support.\n", pbm->name);
+}
+
+static int alloc_msi(struct pci_pbm_info *pbm)
+{
+       int i;
+
+       for (i = 0; i < pbm->msi_num; i++) {
+               if (!test_and_set_bit(i, pbm->msi_bitmap))
+                       return i + pbm->msi_first;
+       }
+
+       return -ENOENT;
+}
+
+static void free_msi(struct pci_pbm_info *pbm, int msi_num)
+{
+       msi_num -= pbm->msi_first;
+       clear_bit(msi_num, pbm->msi_bitmap);
+}
+
+static int pci_sun4v_setup_msi_irq(unsigned int *virt_irq_p,
+                                  struct pci_dev *pdev,
+                                  struct msi_desc *entry)
+{
+       struct pcidev_cookie *pcp = pdev->sysdata;
+       struct pci_pbm_info *pbm = pcp->pbm;
+       unsigned long devino, msiqid;
+       struct msi_msg msg;
+       int msi_num, err;
+
+       *virt_irq_p = 0;
+
+       msi_num = alloc_msi(pbm);
+       if (msi_num < 0)
+               return msi_num;
+
+       devino = sun4v_build_msi(pbm->devhandle, virt_irq_p,
+                                pbm->msiq_first_devino,
+                                (pbm->msiq_first_devino +
+                                 pbm->msiq_num));
+       err = -ENOMEM;
+       if (!devino)
+               goto out_err;
+
+       set_irq_msi(*virt_irq_p, entry);
+
+       msiqid = ((devino - pbm->msiq_first_devino) +
+                 pbm->msiq_first);
+
+       err = -EINVAL;
+       if (pci_sun4v_msiq_setstate(pbm->devhandle, msiqid, HV_MSIQSTATE_IDLE))
+       if (err)
+               goto out_err;
+
+       if (pci_sun4v_msiq_setvalid(pbm->devhandle, msiqid, HV_MSIQ_VALID))
+               goto out_err;
+
+       if (pci_sun4v_msi_setmsiq(pbm->devhandle,
+                                 msi_num, msiqid,
+                                 (entry->msi_attrib.is_64 ?
+                                  HV_MSITYPE_MSI64 : HV_MSITYPE_MSI32)))
+               goto out_err;
+
+       if (pci_sun4v_msi_setstate(pbm->devhandle, msi_num, HV_MSISTATE_IDLE))
+               goto out_err;
+
+       if (pci_sun4v_msi_setvalid(pbm->devhandle, msi_num, HV_MSIVALID_VALID))
+               goto out_err;
+
+       pcp->msi_num = msi_num;
+
+       if (entry->msi_attrib.is_64) {
+               msg.address_hi = pbm->msi64_start >> 32;
+               msg.address_lo = pbm->msi64_start & 0xffffffff;
+       } else {
+               msg.address_hi = 0;
+               msg.address_lo = pbm->msi32_start;
+       }
+       msg.data = msi_num;
+       write_msi_msg(*virt_irq_p, &msg);
+
+       irq_install_pre_handler(*virt_irq_p,
+                               pci_sun4v_msi_prehandler,
+                               pbm, (void *) msiqid);
+
+       return 0;
+
+out_err:
+       free_msi(pbm, msi_num);
+       sun4v_destroy_msi(*virt_irq_p);
+       *virt_irq_p = 0;
+       return err;
+
+}
+
+static void pci_sun4v_teardown_msi_irq(unsigned int virt_irq,
+                                      struct pci_dev *pdev)
+{
+       struct pcidev_cookie *pcp = pdev->sysdata;
+       struct pci_pbm_info *pbm = pcp->pbm;
+       unsigned long msiqid, err;
+       unsigned int msi_num;
+
+       msi_num = pcp->msi_num;
+       err = pci_sun4v_msi_getmsiq(pbm->devhandle, msi_num, &msiqid);
+       if (err) {
+               printk(KERN_ERR "%s: getmsiq gives error %lu\n",
+                      pbm->name, err);
+               return;
+       }
+
+       pci_sun4v_msi_setvalid(pbm->devhandle, msi_num, HV_MSIVALID_INVALID);
+       pci_sun4v_msiq_setvalid(pbm->devhandle, msiqid, HV_MSIQ_INVALID);
+
+       free_msi(pbm, msi_num);
+
+       /* The sun4v_destroy_msi() will liberate the devino and thus the MSIQ
+        * allocation.
+        */
+       sun4v_destroy_msi(virt_irq);
+}
+#else /* CONFIG_PCI_MSI */
+static void pci_sun4v_msi_init(struct pci_pbm_info *pbm)
+{
+}
+#endif /* !(CONFIG_PCI_MSI) */
+
 static void pci_sun4v_pbm_init(struct pci_controller_info *p, struct device_node *dp, u32 devhandle)
 {
        struct pci_pbm_info *pbm;
@@ -1119,6 +1558,7 @@ static void pci_sun4v_pbm_init(struct pci_controller_info *p, struct device_node
 
        pci_sun4v_get_bus_range(pbm);
        pci_sun4v_iommu_init(pbm);
+       pci_sun4v_msi_init(pbm);
 
        pdev_htab_populate(pbm);
 }
@@ -1187,6 +1627,10 @@ void sun4v_pci_init(struct device_node *dp, char *model_name)
        p->scan_bus = pci_sun4v_scan_bus;
        p->base_address_update = pci_sun4v_base_address_update;
        p->resource_adjust = pci_sun4v_resource_adjust;
+#ifdef CONFIG_PCI_MSI
+       p->setup_msi_irq = pci_sun4v_setup_msi_irq;
+       p->teardown_msi_irq = pci_sun4v_teardown_msi_irq;
+#endif
        p->pci_ops = &pci_sun4v_ops;
 
        /* Like PSYCHO and SCHIZO we have a 2GB aligned area
index 884d25f6158dad36a58c6c4c30365c910530d7a1..8e9fc3a5b4f52d68120d360fa0d18ca6f0e9b2a7 100644 (file)
@@ -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) */
index 6604fdbf746cc44b2c410013563a87e27ae84ee3..ecb81f389b0689ff3518448813a1532e96e50af5 100644 (file)
@@ -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
+
index 3cfb0a3575e660938abc6070c9048563ddd7c287..5ea5bc70cb8215cf078bd38fe77ba82f34e80554 100644 (file)
@@ -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
index 905e59b4a737537bbfe835b5512aaadcdf0c6f4a..5d233b42fe135b87b51d9c2f3e1339554ce8e7f5 100644 (file)
@@ -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)
index dcfa7629358c396c9e977447ee24f56b31cd799e..7a246d8a1828b7f98cea9d9b10ff21e9e862c485 100644 (file)
@@ -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